C#编码规范

规范正文
前言
本文是一套面向C# programmer 和C# developer 进行开发所应遵循的开发规范。
按照此规范来开发C#程序可带来如下益处:
代码的编写保持一致性,
提升代码的可读性和可维护性,
在团队开发一个项目的状况下,程序员之间可代码共享
易于代码的回顾,
本规范是第一版,只适用于通常状况的通用规范,并不能覆盖全部的状况。
产品中全部的代码需遵循统一的标准。
    a. 指定统一编码风格文档。
    b. 重要的在于让每一个开发人员都遵照。
    c. 将不符合规范的代码看成错误处理。
第1章 格式说明
1.1 基本命名格式
1.1.1 Pascal
每一个单词首字母大写,如BaseClass。
1.1.2 camel
第一个单词首字母小写,其他单词首字母大写,如:baseClass。
1.1.3 Hungarian
类型缩写+Pascal,其中类型缩写通常使用类型中关键辅音字母,如:btnSubmit。
1.1.4 _camel
下划线 加 camel 格式,如:_wordDocument。
1.1.5 ACL(ALL_CAPITAL_LETTER)
全部字母大写,单词间用下划线链接,如:MESSAGE_MAX_SIZE。
1.1.6 其它原则
除字段外,尽可能不使用下划线“_”,若是须要使用,下划线应该表示语意上的分级。
第2章 通常规范
2.1 命名规则
一个好的名字应该要表达它的意义。
2.1.1 变量、参数
名词、名词短语。
格式:camel。
通常状况下应该取有意义的名字。可是计数变量当用在琐碎的计数循环式更适宜叫i, j, k, l, m, n。
2.1.2 常量
名词、名词短语。
格式:ACL。
必须使用限定符。(注:限定符为private,public,internal,protected等。)
2.1.3 字段
名词、名词短语。
格式:_camel或m_camel。如:private int _itemCount; private int m_itemCount;
这样的格式,能够在方法中把字段和参数、变量等区分开。
GUI控件字段的格式:Hungarian。
GUI控件字段不一样于通常的字段,这些控件字段中控件类型是重要的信息,因此这里使用Hungarian格式。
如:
frm Form
btn Buton 
cb  下拉列表框 
txt 文本输入框 
lbl 标签
pic 图像控件 
grd  DataGrid 
sb  滚动条
lst 列表框
cb  CheckBox
rb  RadioButton
pb  进度条
tv  TreeView
lv  ListView
除了常量及静态只读字段外,其它字段只能使用private、internal限定符。
必须使用限定符。
2.1.4 属性
名词、名词短语。
格式:Pascal。
必须使用限定符。
通常状况下,应该取有意义的名字。偶尔能够考虑使用与其类型相同的名字命名一个属性。如:
public TreeView TreeView
{
get
{
...
}
set
{
...
}
}
除非是十分简单的类或者纯数据类型(贫血数据模型),不然不要使用属性的缩写格式(public TreeView TreeView{get;set;})。
这样能够在复杂类中只有字段才能表示数据,因此能够很清楚的看到当前有哪些数据构成该类。
2.1.5 方法(函数)
动词、动词短语。
格式:Pascal。
必须使用限定符。
经常使用的介词有:Of、In、By、DependOn等。如:
User GetByUserName(string username);
2.1.6 事件
考虑用一个动词命名事件。用如今和过去时态命名有前缀和复制概念的事件名字。
格式:Pascal。
必须使用限定符。
多使用EventHandler<T>进行定义。
如:public event EventHandler<MouseMoveEventArgs> MouseMove;
2.1.7 类型
通常状况下,类型都使用Pascal格式来进行命名。
必须使用限定符。
2.1.7.1 接口
使用I字母打头,而后接Pascal形式,即格式为:IPascal。如ICollection。
2.1.7.2 枚举
格式:Pascal。
对于位枚举(Flags)用复数名字。
2.1.7.3 异常类
格式:Pascal + Exception
自定义异常类以Exception结尾,而且在类名中能清楚的描述出该异常的缘由。好比FileNotFoundException,描述出了某个实体(文件、内存区域等)没法被找到。
2.1.7.4 事件委托
格式:Pascal + EventHandler
如:好比public delegate void MouseEventHandler (object sender, MouseEventArgs e);    //用于处理与鼠标相关的事件或委托。
对于自定义的委托,其参数第一个建议仍然使用object sender,sender表明触发这个时间或委托的源对象。而第二个参数继承于EventArgs类,而且在派生类中实现本身的业务逻辑。
2.2 代码格式及逻辑
2.2.1 声明
2.2.1.1 类成员声明
全部类成员都应该明确写上限定符:private,protected,public,internal。
2.2.1.2 每行的声明数
每行只有一个声明,由于它能够方便注释。
int level; // indentation level
int size; // size of table
当声明变量时,不要把多个变量或不一样类型的变量放在同一行,例如: 
int a, b; //What is 'a'? What does 'b' stand for?
2.2.1.3 初始化
局部变量一旦被声明就要初始化。例如: 
string name = myObject.Name;
int val = time.Hours;
2.2.1.4 方法声明
若是能够的话,方法的参数尽可能不要使用out及ref。
2.2.2 语句
大括号的格式:大括号内表示一个语句块(block),前大括号和后大括号能够只占一行。
每行都应该只包含一条语句。
2.2.2.1 If语句
可使用单行if。
其他状况应该使用带括号的if语句。
如,使用:
If(argument <= 0) throw new ArgumentException(“argument必须是正整数!”);
If(value)
{
//do something
}
而不是
If(value)
This.Calculate();
推荐不要把逻辑上不是一类的判断组合在一块儿。
条件尽可能简单,不宜过长。
若是条件过长,可采用两种方式:
1.能够先把条件的值计算出,并存入一个临时的变量。
2.可对条件进行折行,折行的方式见:2.4.5.2换行。
2.2.2.2 For语句
带上两个括号,即采用如下格式:
for (int i = 0; i < array.Length; i++)
{
//do sth
}
当断定条件比较耗时时,应该采用缓存变量的形式,即如下格式:
for( int i = 0, c = list.Count; i < c; i++)
{
//do sth
}
2.2.2.3 While语句
采用如下格式:
while(value)
{
}
do
{
}
while(value)
2.2.2.4 Switch语句
switch应该处理全部的可能分支。若是不处理时,应该抛出异常。如:
switch(value)//value 是string类型
{
case “1”:
//case 1;
break;
case “2”: 
//case 2;
break;
default:
   throw new NotSupportedException();
}
2.2.2.5 Try语句
格式以下:
try
{
catch(NotSupportedException ex)//子异常
{
}
catch(Exception e)//基异常
{
}
finally//finally语句能够省略
{
}
2.2.2.6 小技巧
以上全部语句,在VS环境下,都已经默认携带了snippet方便快捷输入。如在书写if语句时,可输入“if”,而后按两下tab键便可。
另外,它的用处在于它能够进行自定义你常常输入的代码段。具体内容见:http://www.cnblogs.com/WilsonWu/archive/2010/02/25/1673745.html
2.2.3 属性
不要连续地对同一属性进行调用。
如:
var prop2 = this.Prop1.Prop2;
var prop3 = this.Prop1.Prop3;
而是应该用变量进行缓存,如:
var prop1 = this.Prop1;
var prop2 = prop1.Prop2;
var prop3 = prop1.Prop3;
这是由于属性其实就是方法。因此就算是最简单的属性,对它进行屡次调用都会浪费必定的资源。
属性的内部代码,不要设计得太过复杂。方便调试。
最好不要访问易中断的外部资源。由于极可能会致使没法对其调试。
2.2.4 事件
监听的事件处理方法取名按照“必定规则”,如btnSubmit_Click。监听的事件处理方法里面,不该该写比较复杂的代码。若是比较复杂,应该提取函数。
2.2.5 方法
原则上单个方法的长度不能太长,如不超过50行、不超过一屏。
程序编码力求简洁,结构清晰,避免太多的分支结构及太过于技巧性的程序。
程序代码要严格,要把全部状况都考虑到。抛出的异常,应该都是在乎料范围内的。如,如下代码是不严格的:
int[] array = this.GetIntArray();//这个方法可能会返回一个空数组。
int first = array[0];//因为数组可能没有元素,因此这里可能会抛出异常。
好的代码应该是:
if(array.Length > 0)
{
    int first = array[0];
}
或断言:
if(array.Length == 0) throw new InvalidException(“返回的数组没有元素。”)
若是是未完成的方法,必须抛出NotImplementException。
参数的检测,使用正常的代码进行检测。而调试期代码则使用Debug.Assert方法。
方法逻辑:
按照如下顺序编写:
1.参数检查。(private方法在参数传入时已经确定参数正确时,能够忽略此步。)
2.参数转换。(private方法在参数传入时已经确定参数正确时,能够忽略此步。)
3.核心逻辑。
4.返回值转换。
5.返回。
2.3 注释
注释尽可能使用中文。
添加注释的目的:使代码易读、易写、易维护 。
如何添加注释:
    a. 代码、数据、算法的解释
    b. 作标记(时间、所作的改动等)
    c. 标识代码的功能和目的
    d. 代码如何调用 
避免
    a. 对显而易见的内容进行注释
    b. 添加大段注释
    c. 注释的拼写错误
2.3.1 文件头
为了清晰的说明代码功能,跟踪版本纪录,应该在源代码文件中加入相应的描述信息,本文将列举相应的文件头格式。
须要说明的是,文件头中的历史纪录位置应该详细记录每次所做修改的内容。每次修改版本号都应该做相应的变动。
如对版本号无特殊要求,请遵守以下通用原则:
版本号形如 X.Y,其中X为大版本号,Y为小版本号。
bug修正,变动小版本号。
新增功能或重大bug修正,变动大版本号。
2.3.1.1 C#代码文件头
/*******************************************************
 * 
 * 做者:胡庆访
 * 建立日期:20150801
 * 说明:此文件只包含一个类,具体内容见类型注释。
 * 运行环境:.NET 4.0
 * 版本号:1.0.0
 * 
 * 历史记录:
 * 建立文件 胡庆访 20150801 17:13
 * 
*******************************************************/
2.3.1.2 小技巧
能够在 Visual Studio 中搜索 HxyUtils 插件扩展,以下图:
 
安装后,点击工具->添加文件头,便可为当前打开的代码文件加入可配置的文件头。如图:
 
生成:
 
2.3.2 类和函数
每一个类及函数必须写上注释。
格式:利用VS自带的注释块生成格式。在须要的地方键入"///"。
类型注释、文件头、复杂函数、复杂属性、接口所有内容必须写清楚注释。
2.3.3 块注释
块注释一般应该是被避免的。推荐使用//注释做为C#的标准声明。若是但愿用块注释时你应该用如下风格:
 

由于样能够为读者将注释块与代码块区分开。虽然并不提倡使用C风格的单行注释,但你仍然可使用。一旦用这种方式,那么在注释行后应有断行,由于很难看清在同一行中前面有注释的代码:
/* blah blah blah */
块注释在逻辑比较复杂时进行注释是很是有用的。一般块注释用于注释掉大的代码段。
2.3.4 单行注释
你应该用//注释风格“注释掉”代码(快捷键,Ctrl+K,Ctrl+C)。它也能够被用于代码的注释部分。
单行注释被用于代码说明时必须缩进到相应的编进层级。
(一条经验,注释的长度不该该超过被解释代码的长度太长,由于这表示代码过于复杂,有潜在的bug。)
2.4 代码组织
2.4.1 命名空间
命名空间的格式也是Pascal命名法。
逻辑上可分为子空间的,尽可能分子空间。
2.4.2 目录结构
为每个命名空间建立一个目录。(用MyProject/TestSuite/TestTier做为MyProject.TestSuite.TestTier的路径,而不用带点的命名空间名作路径)这样能够更容易地将命名空间映射到目录层次划分。
若是一个命名空间下的文件过多,也能够添加子目录进行管理。这时不须要和空间名保持一致。
2.4.3 C# 源文件
类名或文件名要尽可能简短,将代码分割开,使结构清晰。将每一个类尽可能放在一个单独的文件中,使用类名来命名文件名(固然扩展名是.cs)。这种约定会使你们工做更简单。
若是是很简单的一些同类型的类,也能够把多个类写在一个文件当中。这时,文件的名字使用最主要的类的类名,或者是为它们起一个类型名。例如,多个类能够放一个文件的状况:枚举、常量、命令、Attribute。
若是某个类是分部类,分别写在多个文件中。则须要其中一个文件是主文件,其它文件扩展名分级别。推荐让它们文件依赖于主文件。(这里的依赖是指project中的文件依赖关系,如1.aspx.cs依赖于1.aspx文件,这样vs就会把它们折叠起来。)如:
 
推荐使用文件依赖:
 
对应csproject代码:
    <Compile Include="Control.cs" />
    <Compile Include="Control.MouseEvent.cs" >
      <DependentUpon>Control.cs</DependentUpon>
    </Compile>
2.4.4 类成员排序
成员按如下次序排序:
常量 static readonly 或 const
静态私有字段 private static int _count
静态构造函数 static ClassName(){}
公共静态属性 public static int Count{get;set;}
公共静态方法 public static void AddCount(){}
私有字段 private int _count
构造函数 public ClassName(){}
公共属性 public int Count{get;set;}
公共方法 public void AddCount(){}
事件 public event EventHandler CountChanged;
保护方法 protect void OnCountChanged(){}
私有方法 private void AddCount();
保护静态方法 protected static void HelperMethod(){}
私有静态方法 private static void HelperMethod(){}
内部类 private class InnerClass{}。
不一样类型的成员,使用区域或空格进行分隔。
同种类型的成员,先按该成员的重要程度进行排序,同时兼顾它们之间的关系。
排序原理:
常量命名从类内部的第1行开始。
紧接着是字段,字段表示数据,可以一目了然地表现类的结构。
而后是:构造函数、属性、事件、方法。
保护的静态方法和私有静态方法通常被本类的实体方法看成公共方法调用,因此放在最后。
修饰符按可见度进行排序,即:public internal protected private。
2.4.5 空白与分隔
2.4.5.1 空白
2.4.5.1.1 缩进
缩进不使用制表符,而是使用空白符(这是由于制表符在不一样的编辑器下,显示的效果不同,因此都使用空白)。每次缩进的空白字符数是4。
2.4.5.1.2 内部空格
在一个逗号或一个分号以后应该由一个空格,例如:
TestMethod(a, b, c);
不要用:
TestMethod(a,b,c)
TestMethod( a, b, c );
单个空格包围操做符(除了像加的一元操做符和逻辑非),例: 
a = b; // don't use a=b;
for (int i = 0; i < 10; ++i) // don't use for (int i=0; i<10; ++i)
// or
// for(int i=0;i<10;++i)
2.4.5.1.3 小技巧
可使用VS IDE自带的格式化功能,(快捷键:Ctrl+K、Ctrl+D)便可自动自成缩进、空格。
2.4.5.2 换行
逻辑换行:
空行提升可读性。它们分开那些逻辑上自身相关联的代码块。因此,每个空行,都应该表示逻辑上的分隔关系,
类的成员之间,都必须空行。
过长换行:
当一个表达式超过一行时,根据这些通用原则进行处理: 
1. 在逗号后换行。
2. 在操做符后换行。 
3. 在高层换行而不要在低层处换行。
4. 一次或屡次折行后,都对应此行语句最上层的表达式起始位置,进行一次缩进。
方法调用换行示例: 
longMethodCall(expr1, expr2,
    expr3, expr4, expr5); 
算术表达式换行示例:
推荐:
var x = a * b / (c - g + f) +
      4 * z;
很差的格式——应避免:
var x = a * b / (c - g +
      f) + 4 * z; 
推荐使用第一种方法,由于是在括号表达式以外折行(高层次折行原则)。注意要用制表符到缩进的位置,而后用用空格到折行的位置。在咱们的例子中是: 
> var x = a * b / (c - g + f) +
> ......4 * z;
'>'表示是制表符,'.'表示是空格符。(制表符后是空白是用制表符缩进)。一个好的编码习惯就是在所用的编辑器中显示制表符和空格符。
2.4.5.3 Region
逻辑上的独立块,都推荐使用Region进行分隔。
方法内不要使用Region,若是太长,就提取方法。
空行可进行逻辑块的分隔。可是当逻辑块较大时,应该使用Region。
格式:#region 和 #endregion的先后都须要有一行空行。若是添加的空行是在block的开始和结尾,则能够省略。
如:
public class Class1
{
#region 私有字段

字段定义

#end region

#region 公共属性

属性的定义

#endregion
}
2.4.5.4 空代码块
大括号内的代码块为空时,应把两个大括号都和前一行代码放在一行。如:
protected virtual void OnMove(EventArgs e) { }
try
{
    //…
}
catch{ }//放在一行。
第3章 推荐基类编写规范
此规范较为严格,仅为推荐。适用于编写较好的框架基类,特殊状况下可不遵照部分规范。
3.1 代码格式及逻辑
3.1.1 事件
监听某个事件后,要记住释放监听。
3.1.1.1 通常编写模式(推荐使用)
为每一个事件都写一个保护的虚方法。虚方法名为On + 事件名,并只带一个对应事件的事件参数。
在虚方法中触发外部监听的事件。
如:
public event EventHandler<MouseMoveEventArgs> MouseMove;
protected virtual void OnMouseMove(MouseMoveEventArgs e)
{
var handler = this.MouseMove;
if(handler != null) handler(this, e);
}
子类在处理事件时,直接重写该虚方法。
如:
protected override void OnMouseMove(MouseMoveEventArgs e)
{
base.OnMouseMove(e);

//do sth.
}
3.1.2 异常
3.1.2.1 描述
异常信息应该有以下的描述
存在的问题。
违反的规律。
可能的解决方案。
如:Nod没法打开文件nod.config:请确认该文件存在而且当前用户拥有足够的权限
如:OPath解析查询语句错误:[和]必须匹配,请检查[]的完整性
3.1.2.2 处理
发生异常时只能向外抛出。
第4章 DBI应用编写规范
4.1 代码组织
4.1.1 命名空间
命名空间使用如下格式:
实体类:DBI.(可选子模块缩写).(可选命名空间);
服务:DBI.Services.(可选子模块缩写);
命令:DBI.WPF. (可选子模块缩写).Commands;
界面:DBI.WPF. (可选子模块缩写)。
例如,在用户模块中的实体类都声明在 DBI.Accounts 空间下,命令则声明在 DBI.WPF.Accounts.Commands 空间下,界面其它类型则声明 DBI.WPF.Accounts 中。
4.1.2 类型
命令类型:因为全部命令都已经放在 Commands 命名空间下,因此这些类型都不须要以 Command 结尾。
第5章 代码管理
5.1 版本控制
采用版本控制工具svn进行源代码管理。(将来将采用 Git 进行管理)
天天打开解决方案后,先获取最新版本。
每次提交都应该保证没有编译错误,同时在说明栏写上本次完成的内容。
提交流程:更新最新版本;编译无错;提交。
5.2 版本号
给客户发布的全部版本须要使用版本号进行管理。
版本号由四个部分组成:主版本号、次版本号、内部版本号和修订号。形式如:
主版本号.次版本号.编译版本号.修正版本号
全部定义的部分都必须是大于或等于 0 的整数。

主版本号:具备相同名称但不一样主版本号的程序集不可互换。例如,这适用于对产品的大量重写,这些重写使得没法实现向后兼容性。
次版本号:若是两个程序集的名称和主版本号相同,而次版本号不一样,这指示显著加强,但照顾到了向后兼容性。例如,这适用于产品的修正版或彻底向后兼容的新版本。
内部版本号:内部版本号的不一样表示对相同源所做的从新编译。这适合于更改处理器、平台或编译器的状况。
修正版本号:名称、主版本号和次版本号都相同但修订号不一样的程序集应是彻底可互换的。这适用于修复之前发布的程序集中的安全漏洞。

程序集的只有内部版本号或修订号不一样的后续版本被认为是先前版本的修补程序 (Hotfix) 更新。
5.3 Build策略
每日至少Build一次。
5.4 Code Review
暂无。



相关文章
相关标签/搜索