Instruction Sheet for Coding Standards
(软件开发编码规范)
目录
目录... 1
系统构架... 2
系统架构总体说明... 2
表示层... 2
业务层... 2
业务数据访问层... 9
数据层... 2
各层间的依赖关系... 2
关于Service(服务)层的讨论... 9
数据库编码规范... 2
数据库涉及字符规范... 2
数据库对象命名规范... 2
数据库表命名规范... 2
字段命名规范... 9
视图命名规范... 9
字段命名规范... 9
存储过程命名规范... 9
字段命名规范... 9
sql语句规范... 9
C#编码规范... 2
命名约定... 2
变量命名... 2
控件命名规则... 2
类数据成员/属性命名... 9
命名空间命名... 9
方法(Method)命名规范... 9
委托缩写... 9
自定义异常类... 9
枚举... 9
接口(Interface)命名规范... 9
常量命名... 9
命名缩写... 9
注释规范... 2
模块(类)注释规范... 2
类属性注释规范... 2
方法注释规范... 2
代码间注释规范... 9
逻辑点注释... 9
系统架构
系统架构总体说明 |
架构在一个系统中占举足轻重的角色.一个系统架构的好坏,决定了系统开发的质量,性能,成本及时间。在一个好的架构下编程,不仅对于开发人员是一件赏心悦目的事情,更重要的是软件能够表现出一个健康的姿态;而架构设计的不合理,不仅让开发人员受苦受难,软件本身的生命周期更是受到严重威胁。这里我将针对在微软dotNet平台上做应用开发的系统架构设计做一些的讨论。
总体设计图
表示层 |
表示层由UI(User Interface)和UI控制逻辑组成。
l UI(User Interface)
UI是客户端的用户界面,负责从用户方接收命令,请求,数据,传递给业务层处理,然后将结果呈现出来。根据客户端的不同我们大体将应用程序分为BS(Browser-Server) 浏览器结构,CS(Client-Server)桌面客户端结构
l UI控制逻辑
UI控制逻辑负责处理UI和业务层之间的数据交互,UI之间状态流程的控制,同时负责简单的数据验证和格式化等功能。具体的说在dotNet事件驱动的编程模型下,UI控制逻辑被自然的实现在了事件函数中,例如PageLoad事件函数,ButtonClick事件函数。在这些事件函数中,主要任务就是做UI控件与业务实体的数据交换与业务调用,但面对大量的数据交换工作量与维护量就成了最大的问题。
业务层 |
业务层封装了实际业务逻辑,包含数据验证,事物处理,权限处理等业务相关操作,是整个应用系统的核心。因此设计一个能够真实反映实际需要的业务层是非常必要的,我们将实际业务具体分为业务数据与业务操作两部分。
l 业务数据又是业务逻辑的核心,最终业务数据将以一种固定的格式表现于内存中,在系统的各个层次间传输,充当DTO角色。表达业务数据的方式一般分为两种Table Model和Domain Model
l 业务操作负责对业务数据进行各种业务相关的处理,例如验证,流向,整合,事物,权限等,但它不负责有关对数据源的操作
业务数据访问层 |
业务数据访问层是一个针对具体应用系统的专属层,它为业务层提供与数据源交互的最小操作方式,仅仅是业务层需要的数据访问接口,业务层完全依赖业务数据访问层所提供的服务。这些服务负责从业务层接收数据或返回业务实体,它屏蔽了实际业务数据与机器存储方式的差别。当然,数据层选用抽象的解决方案同样可以达到这个效果,但业务数据访问层最大的特点就是针对具体业务做抽象,而抽象的数据层访问方案是针对通用做抽象。往往业务中针对具体的设计生命力会变的更强,这样我们可以最大限度的保持了上层代码的复用性,当需要更换存储策略如果数据层访问差别太大,通过更换数据层无法解决问题的时候我们最多只需要更换业务数据访问层,而无需改变业务层
业务数据访问层由DAO(Data Access Object)层和系统服务层两部分组成。DAO层为每个业务实体提供最基本的数据访问服务,系统服务层为系统全局提供与业务关系不大的通用数据访问服务,这两层处于系统中的同一个层次位置。
业务层与业务数据访问层关系图
数据层 |
数据层的宗旨就是为数据源提供一个可供外界访问的接口,我们应该选用一种能够提供数据源无关的抽象数据访问接口并通过在其下挂接各种不同的DataProviador来访问数据源的数据层组件,这样做便于移植到不同的数据源上。
各层间的依赖关系 |
依赖关系图:
在对各层的讨论之后,我们来总结一下各层间的依赖关系。说到依赖就离不开复用这个词,复用对软件开发流程的几乎每个阶段都有着重要的意义。在设计阶段它代表着更清晰的设计,在开发阶段它代表着更高的工作效率和代码质量,在测试阶段它代表着更轻易的捕获bug,在维护和再开发阶段它代表着更小的工作量。更好的复用需要设计更好的依赖关系。
从图中可以看出表示层与业务数据访问层都依赖于业务层,而业务层是相对独立的,这样设计的优点就是最大限度的减少了变动对整个系统所带来的影响。最坏的情况就是业务的改变,业务改变其他依赖业务层的地方必须改变(在这里我们忽略了一些针对多种业务而设计的其他层组件,这些组件是可以适应有限的业务变更而本身不用变更的。),这个我们没有办法控制。但像表示层与业务数据访问层等其他非业务方面的改动不会影响到其他地方。
关于Service(服务)层的讨论 |
Service层是一个构建于表示层于业务层之间的层,它是对业务层的一个浅封装而不应该封装过多的业务逻辑,否则会造成不必要的麻烦。然而它并不是任何时候都有必要存在。一种情况下当你的UI所要展现的东西和你的业务实体并不是那么完全吻合的时候,例如你的界面需要显示若干个业务实体的一部分,但这并不是业务本身,只是一种展现方式。这时候你需要Service层来做一个展现逻辑的转换。或者当你在做分布式系统的时候可以利用Service层来实现一个粗粒度的服务接口。也可以以SOA(Service-oriented Architecture)的方式来理解Service层,我们最终使用的是系统所提供的服务而不是业务对象,所以需要将将业务对象以清晰的方式组织起来形成清晰的服务暴露在外。更多的情况在于你结合实际该如何使用。
数据库编码规范
数据库涉及字符规范 |
我们约定:采用26个英文字母(区分大小写)和0-9这十个自然数,加上下划线_组成,共63个字符。不能出现其他字符(注释除外)。
数据库对象命名规范 |
我们约定,数据库对象包括表、视图(查询)、存储过程(参数查询)、函数、约束。对象名字由前缀和实际名字组成,长度不超过30。
前缀:使用小写字母
表 tb
视图 vi
存储过程 sp
函数 fn
实际名字:实际名字尽量描述实体的内容,由单词或单词组合,每个单词的首字母大写,其他字母小写,不以数字和_开头。如
表 User_Info
视图 User_List
存储过程 User_Delete
因此,合法的对象名字类似如下。
表 User_Info Message_Detail
视图 vi_Message_List
存储过程 sp_Message_Add
数据库表命名规范 |
我们约定,表名由前缀和实际名字组成。
前缀:使用小写字母tb,代表表。实际名字中,一个系统尽量采取同一单词,多个后面加_来连接区分。
因此,合法的表名类似如下。
Member
Member_Info
Forum_Board
Blog_Comment1
字段命名规范 |
我们约定,字段由表的简称,实际名字组组成。如果此字段关联另外的字段,那么加下划线_连接关联表字段的字段名。
因此,合法的字段名类似如下。
UserID_MeID
UserName
UserRegDate
视图命名规范 |
我们约定,字段由前缀和实际名字组成,中间用下划线连接。
前缀:使用小写字母vi,表示视图。
因此,合法的视图名类似如下。
vi_User
vi_UserInfo
存储过程命名规范 |
我们约定,字段由前缀和实际名字加操作名字组成,中间用下划线连接。
前缀:使用小写字母sp,表示存储过程。
操作名字:Insert|Delelte|Update|Caculate|Confirm
例如:
sp_User_Insert
数据库设计文档规范 |
所有数据库设计要写成文档,文档以模块化形式表达。大致格式如下:
'-------------------------------------------
' 表名: User_Info
' 作者: ChenJie(陈杰)
' 日期: 2007-4-20
' 版本: 1.0
' 描述: 保存用户资料
' 具体内容:
' UserId int,自动增量 用户代码
' UserName char(12) 用户名字
' ... ...
'--------------------------------------------
sql语句规范 |
我们约定,所有sql关键词全部大写,比如SELECT,UPDATE,FROM,ORDER,BY等。
C#编码规则
命名约定 |
Pascal和Camel命名约定
编程的命名方式主要有Pascal和Camel两种
Pascal:每个单词的首字母大写,例如ProductType;
Camel:首个单词的首字母小写,其余单词的首字母大写,例如productType
以下是一些常用的C#成员及其推荐命名方法:
标志符 |
规则 |
实例与描述 |
类class |
Pascal |
Application |
枚举类型enum |
Pascal |
记住,是以Pascal命名,切勿包含Enum,否则FXCop会抛出Issue |
委托delegate |
Pascal |
以Pascal命名,不以任何特殊字符串区别于类名、函数名 |
常量const |
全部大写 |
全部大写,单词间以下划线隔开 |
接口interface |
Pascal |
IDisposable 注:总是以 I 前缀开始,后接Pascal命名 |
方法function |
Pascal |
ToString |
命名空间namespace |
Pascal |
以.分隔,当每一个限定词均为Pascal命名方式,比如: using ExcelQuicker.Framework |
参数 |
Camel |
首字母小写 |
局部变量 |
Camel |
也可以加入类型标识符,比如对于System.String类型,声明变量是以str开头,string strSQL = string.Empty; |
数据成员 |
Camel |
以m开头+Pascal命名规则,如mProductType(m意味member) |
属性 |
Pascal |
|
变量命名 |
在primitive的局部变量命名时,使用Camel命名规则,
比如:int type = 0;
double count = 0;
…
对于string类型定义,通常使用str前缀+Pascal命名的方式,
比如string strSql = ""; //这是一种典型的命名SQL语句字符串的方式。
而对于此外的类型对象定义,通常的做法是使用obj前缀+Pascal命名的方式,来告知我们这个变量是一个对象。或者也可以直接使用类名的Camel命名规则。
比如:Application objApplication = new Application();
Application application = new Application();
类模块级的变量请用“m_”作前缀
public class hello
{
private string m_Name;
private DateTime m_Date;
}
类的属性所对应的变量,采用属性名前加“m_”前缀的形式
public class hello
{
private string m_Name;
public string Name
{
get
{
return m_Name;
}
}
}
过程级的变量不使用前缀
public class hello
{
void say()
{
string SayWord;
}
}
过程的参数使用“p_”作为参数
public class hello
{
void say(string p_SayWord)
{
}
}
控件命名规则 |
控件命名=Web控件缩写前缀 + “_” +变量名
控件 |
缩写 |
Label |
lbl |
TextBox |
txt |
CheckBox |
chk |
Button |
cmd |
ListBox |
lst |
DropDownList |
drp |
等等 |
|
类数据成员/属性命名 |
数据成员命名以Camel命名方式,而属性以Pascal命名。1. 名字应该能够标识事物的特性。
名字尽量不使用缩写,除非它是众所周知的。
名字可以有两个或三个单词组成,但通常不应多于三个。
在名字中,所有单词第一个字母大写。
例如 IsSuperUser,包含ID的,ID全部大写,如CustomerID。
使用名词或名词短语命名类。
少用缩写。
不要使用下划线字符 (_)。
比如
public class FileStream
public class Button
public class String
… …
class Appcalition
{
private ArrayList worksheetCollection = new ArrayList();
public ArrayList WorksheetCollection
{
get
{
return this.worksheetCollection;
}
}
}
另外,类的成员数据/方法调用时,应该加上this限定符,this在编辑环境中是蓝色的,更利于我们区分局部变量、参数或静态变量,并且利于FXCop检测区分。(如果使用FxCop扫描和检测代码的话)
命名空间命名 |
和类命名规范相同。
方法(Method)命名规范 |
和类命名规范相同。
委托缩写 |
委托的命名方式我常常以Pascal命名,并且在命名的后面加EventHandler
比如:
public delegate void MouseEventHandler (object sender, MouseEventArgs e); //用于处理与鼠标相关的事件或委托
对于自定义的委托,其参数第一个建议仍然使用object sender,sender代表触发这个时间或委托的源对象。而第二个参数继承于EventArgs类,并且在派生类中实现自己的业务逻辑。
自定义异常类 |
自定义异常类以Exception结尾,并且在类名中能清楚的描述出该异常的原因。比如NotFoundFileException,描述出了某个实体(文件、内存区域等)无法被找到。
枚举 |
枚举的命名是Pascal命名,不需要在枚举中加入Enum,枚举的名称能清楚的表明该枚举的用途。
接口(Interface)命名规范 |
和类命名规范相同,唯一区别是 接口在名字前加上“I”前缀
例:
interface IDBCommand;
interface IButton;
常量命名 |
全部大写,单词间并且以下划线间隔,如public const int LOCK_SECONDS = 3000; 虽然在MSDN中常量的命名推荐使用Pascal,但是从C++沿袭的命名规则来看,将常量全部大写更加能清楚的表示常量与普通变量之间的区别。
命名缩写 |
在一般情况下,不推荐缩写命名,不要担心变量命名长,长的变量名能使变量的意义更加清晰,其实从长变量名的负面作用三,因为Ctrl+C和Ctrl +V加上在VS中的智能感知,其负面追用已经很小。变量命名的原则是,尽最大努力让其他人在看到我们的变量/函数/…等的第一时间,大概能猜出它是做什么 的。
比如:int productTypeCount = 0; //我们在第一时间就能知道它是记录产品的数量的变量
而对于糟糕的命名方式:int prodTypeCount = 0; //它是productTypeCount的简写,我们一部分人也许知道prod是product的缩写,但是每人能保证所有的人都知道它。我个人认为:最优秀的代码它本身就是注释。作为一流的程序员。并不仅仅实现功能,而是要让我们的代码更加优美,具备让他人维护或今后扩充的能力。作为现在的业务系统, 其门槛的准入水平已大大降低,实现功能上的需求已没有什么难度,但是高手和菜鸟的区别在于,高手的代码通俗易懂,在整个编码的过程中,不仅能考虑到性能、还会考虑代码可读性和维护性。
注释规范
模块(类)注释规范 |
全部大写,单词间并且以下划线间隔,如public const int LOCK_SECONDS = 3000; 虽然在MSDN中常量的命名推荐使用Pascal,但是从C++沿袭的命名规则来看,将常量全部大写更加能清楚的表示常量与普通变量之间的区别。
1) 模块开始必须以以下形式书写模块注释:
///
///模块编号:<模块编号,可以引用系统设计中的模块编号>
///作用:<对此类的描述,可以引用系统设计中的描述>
///作者:作者中文名
///编写日期:<模块创建日期,格式:YYYY-MM-DD>
///
2) 如果模块有修改,则每次修改必须添加以下注释:
///
///Log编号:
///修改描述:<对此修改的描述>
///作者:修改者中文名
///修改日期:<模块修改日期,格式:YYYY-MM-DD>
///
类属性注释规范 |
在类的属性必须以以下格式编写属性注释:
///
///属性说明
///
方法注释规范 |
在类的方法声明前必须以以下格式编写注释
///
/// 说明:<对该方法的说明>
///
/// 参数名称>"><参数说明>
///
///<对方法返回值的说明,该说明必须明确说明返回的值代表什么含义>
///
///
代码间注释规范 |
在类的方法声明前必须以以下格式编写注释
代码间注释分为单行注释和多行注释:
单行注释:
//<单行注释>
多行注释:
/*多行注释1
多行注释2
多行注释3*/
代码中遇到语句块时必须添加注释(if,for,foreach,……),添加的注释必须能够说明此语句块的作用和实现手段(所用算法等等)。
逻辑点注释 |
在我们认为逻辑性较强的地方加入注释,说明这段程序的逻辑是怎样的,以方便我们自己后来的理解以及其他人的理解,并且这样还可以在一定程度上排除 BUG。在注释中写明我们的逻辑思想,对照程序,判断程序是否符合我们的初衷,如果不是,则我们应该仔细思考耀修改的是注释还是程序了…