C# 编码规范
C# 编码规范
1. 目的
良好的编程风格是提高程序可靠性非常重要的手段,也是大型项目多人合作开发的技术基础。本规范的目的在于通过规范定义来避免不好的编程风格,增强程序的易读性,便于自己和其它项目成员理解,便于程序后期的维护和功能修改。
2. 参考文档
《C# 编码规范和编程好习惯》http://www.huihoo.com/one_and_net/C#-code.html
《Visual Studio 编码方法》
《.NET Framework General Reference Design Guidelines for Class Library Developers》
ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.2052/vsent7/html/vxconcodingtechniques.htm
3. 规范内容
3.1. 命名规范
名称应该说明“什么”而不是“如何”。通过避免使用公开基础实现(它们会发生改变)的名称,可以保留简化复杂性的抽象层。例如,可以使用 GetNextStudent()
,而不是 GetNextArrayElement()
。
不要使用拼音进行命名,特别是公共函数。
3.1.1. 命名原则
选择正确名称时的困难可能表明需要进一步分析或定义项的目的。使名称足够长以便有一定的意义,并且足够短以避免冗长。唯一名称在编程上仅用于将各项区分开。表现力强的名称是为了帮助人们阅读;因此,提供人们可以理解的名称是有意义的。不过,请确保选择的名称符合适用语言的规则和标准。
Pascal Case : 所有单词第一个字母大写,其他字母小写。
Camel Case : 除了第一个单词,所有单词第一个字母大写,其他字母小写。
3.1.2. Namespace 命名规范
1) 使用 公司名.产品名 这样的格式。
2) Namespace中类的依赖关系应该体现在命名上,比如System.Web.UI.Design中的类以来于System.Web.UI。
3) 使用 Pascal大小写形式命名。
4) 当商标(产品名)的命名风格和Pascal风格不符时,以商标(产品名)为准。
5) 在语意合适的情况下使用复数,比如System.Collections。例外是缩写和商标的情况。
6) Namespace的名字不一定和Assembly一一对应。
3.1.3. Class 命名规范
1) 使用名词或者名词性词组命名class。
2) 使用 Pascal case。
3) 文件名应和类名相同。
4) 保守地使用缩写。
5) 不使用type前缀,例如C来标识Class。比如,使用FileStream而不是CFileStream。
6) 不使用下划线。
7) 偶尔的在Class名称组成中需要使用I开头的时候,比如IdentityStore,just use it。
8) 在合适的时候,使用单词复合来标识从某个基类继承而来。比如xxxException。
3.1.4. Interface的命名
1) 使用名词或者名词性词组命名Interface。
2) 使用 Pascal case。
3) 保守地使用缩写。
4) 在interface 名称前加上字母I来表示type是interface。
5) 在某个class是某个interface地标准实现地时候,用类似的名字来命名它们,仅仅在interface的名称前面多个I。
6) 不要使用下划线。
7) 文件名应和类名相同。
3.1.5. Attribute 命名规范
1) 总是给Attribute类加上Attribute后缀。
2) 文件名应和类名相同。
3.1.6. Enumeration Type 命名规范
1) 对Enum类型和值使用Pascal case。
2) 不要在Enum类型名称后面加上Enum后缀。
3) 对于大多数Enum类型使用单数名称,仅仅在这个Enum类型是bit fields地时候使用复数形式。
4) 总是给bit field Enum 类型添加FlagsAttribute。
3.1.7. Static Field 命名规范
1) 使用名词、名词性词组或者名词地缩写来命名static fields。
2) 使用Pascal case。
3) 不要在static field名称中使用匈牙利命名法。
4) 在任何可能的情况下推荐你使用静态properties而不是public static fields。
3.1.8. Private Field 命名规范
1) 使用名词、名词性词组或者名词地缩写来命名。
2) 使用Camel Case。
3) 变量名中不使用下划线 (_)。
4) 成员变量不要使用匈牙利命名法。
5) 只要合适,在变量名的末尾追加计算限定符(Avg、Sum、Min、Max、Index)。
6) 在变量名中使用互补对,如 min/max、begin/end 和 open/close。
7) 布尔变量包含 Is,这意味着 Yes/No 或 True/False 值,如 fileIsFound。
8) 状态变量命名时,避免使用诸如 Flag 的术语。状态变量不同于布尔变量的地方是它可以具有两个以上的可能值。不是使用 documentFlag,而是使用更具描述性的名称,如 documentFormatType。
3.1.9. 局部变量命名规范
1) 遵循
2) 除去for循环,不要轻易使用单字母变量i,j,k等。
3.1.10. Parameter 命名规范
1) 对于Parameter名称使用Camel大小写形式。
2) 使用描述性的名称。参数名字应该在大多数场合下它的名字加上类型足够描述它的意义。
3) 使用描述参数的意义的名字而不是描述参数类型的名字。开发工具应该提供有关参数类型的有意义的信息。因而,参数的名字可以用于更好的描述意义。
4) 保守地使用基于类型的参数名字,仅仅在它们是合适的场合下使用。
5) 不要使用保留的参数。
6) 不要使用匈牙利命名法。
3.1.11. Method 命名规范
1) 使用动词或者动词性词组命名。
2) 使用Pascal case。
3.1.12. Property 命名规范
1) 使用名词或者名词性词组命名。
2) 使用Pascal case。
3) 不要使用匈牙利命名法。
4) 考虑使用和property type名字相同的名字作为property name。
3.1.13. Event 命名规范
1) 使用Pascal case。
2) 不要使用匈牙利命名法。
3) 在event handler名字中使用EventHandler后缀。
4) 指定两个名字分别为sender和e的参数。sender参数代表了发出事件的对象。sender参数总是类型object,即使可能使用一个更加精确的类型。和事件相关的状态封装在名字为e的event class的实体之中。给e指定恰当而且明确的event class。
5) 使用EventArgs后缀命名事件参数class。
6) 考虑使用动词命名事件。使用进行时态来标识事件正在进行之中,使用完成时态标识事件已经完成,不要使用BeforeXxx/AfterXxx命名法。
7) 不要在事件声明中使用前缀和后缀,比如,用Close而不是OnClose。
8) 一般应同时提供一个名字为OnXxx的protected method供派生类来改写。
3.1.14. 数据库表命名
1) 用单数形式表示名称。例如,使用 Employee
,而不是 Employees
。
2) 在命名表的列时,不要重复表的名称;例如,在名为 Employee
的表中避免使用名为 EmployeeLastName
的字段。
3) 列的名称中不要包含数据类型。如果后来有必要更改数据类型,这将减少工作量。
3.2. 缩进和间隔
注意:使用vs.net的格式来格式化代码,位置为“编辑”->“高级”->“格式化选定内容。
1) 建议缩进用 TAB . 不用 SPACES
2) 串联运算符(+)放在行尾而不是开头,清楚地表示没有后面的行是不完整的。
3) 注释需和代码对齐。
4) 花括弧 ( {} ) 需和括号外的代码对齐。
5) 用一个空行来分开代码的逻辑分组(视情况使用)。
6) 在一个类中,各个方法需用一空行,也只能是一行分开。
7) 花括弧需独立一行,而不象if, for 等可以跟括号在同一行。
好:
if ( ... )
{
// Do something
}
不好(不是说这个风格不好,只是为了统一风格,决定采用上面的形式,故摒弃):
if ( ... ) {
// Do something
}//( ... )
8) 在每个运算符和括号的前后都空一格。
好:
if ( showResult == true )
{
for ( int i = 0; i < 10; i++ )
{
//
}
}
不好:
if(showResult==true)
{
for(int i= 0;i<10;i++)
{
//
}
}
9) 编写 HTML 时,建立标准的标记和属性格式,如所有标记都大写。
10) 创建 Web 页时应使用带引号的属性值和结束标记以方便维护。
11) 编写 SQL 语句时,对于关键字使用全部大写,对于数据库元素(如表、列和视图)使用大小写混合。
3.3. 良好的编程习惯
遵从以下良好的习惯以写出好程序。
3.3.1. 编程方法
1) 避免使用大文件。如果一个文件里的代码超过300~400行,必须考虑将代码分开到不同类中。
2) 避免写太长的方法。一个典型的方法代码在1~25行之间。如果一个方法发代码超过25行,应该考虑将其分解为不同的方法。
3) 方法名需能看出它作什么。别使用会引起误解的名字。如果名字一目了然,就无需用文档来解释方法的功能了。
4) 方法名需能看出它作什么。别使用会引起误解的名字。如果名字一目了然,就无需用文档来解释方法的功能了。
好:
void SavePhoneNumber ( string phoneNumber )
{
// Save the phone number.
}
不好:
// This method will save the phone number.
void SaveData ( string phoneNumber )
{
// Save the phone number.
}
5)一个方法只完成一个任务。不要把多个任务组合到一个方法中,即使那些任务非常小。
好:
// Save the address.
SaveAddress (address );
// Send an email to the supervisor to inform the address is // updated.
SendEmail ( address, email );
void SaveAddress ( string address )
{
// Save the address.
// ...
}
void SendEmail ( string address, string email )
{
// Send an email to inform the supervisor that the address //is changed.
// ...
}
不好:
// Save address and send an email to the supervisor to inform //that the address is updated.
SaveAddress ( address, email );
void SaveAddress ( string address, string email )
{
// Job 1.
// Save the address.
// ...
// Job 2.
// Send an email to inform the supervisor that the address is changed.
// ...
}
3.3.2. 变量的使用
1) 使用C# 特有类型,而不是System命名空间中定义的别名类型。
好:
int age;
string name;
object contactInfo;
不好:
Int16 age;
String name;
Object contactInfo;
2) 程序中不要使用固定数值,用常量代替。
3) 程序中不要使用字符串常数,用资源文件。
4) 避免使用很多成员变量。声明局部变量,并传递给方法。不要在方法间共享成员变量。如果在几个方法间共享一个成员变量,那就很难知道是哪个方法在什么时候修改了它的值。
5) 必要时使用enum 。别用数字或字符串来指示离散值。
好:
enum MailType
{
Html,
PlainText,
Attachment
}
void SendMail (string message, MailType mailType)
{
switch ( mailType )
{
case MailType.Html:
// Do something
break;
case MailType.PlainText:
// Do something
break;
case MailType.Attachment:
// Do something
break;
default:
// Do something
break;
}
}
不好:
void SendMail (string message, string mailType)
{
switch ( mailType )
{
case "Html":
// Do something
break;
case "PlainText":
// Do something
break;
case "Attachment":
// Do something
break;
default:
// Do something
break;
}
}
6) 别把成员变量声明为 public 或 protected。都声明为 private 而使用 public/protected 的Properties。
3.3.3. 配置文件
1) 使用相对路径,代码中不要使用绝对路径,并使路径可编程。
2) 应用程序启动时作些“自检”并确保所需文件和附件在指定的位置。必要时检查数据库连接。出现任何问题给用户一个友好的提示。
3) 如果需要的配置文件找不到,应用程序需能自己创建使用默认值的一份。
4) 如果在配置文件中发现错误值,应用程序要抛出错误,给出提示消息告诉用户正确值。
3.3.4. 错误提示
1) 错误消息需能帮助用户解决问题。永远别用象"应用程序出错", "发现一个错误" 等错误消息。而应给出象 "更新数据库失败。请确保登陆id和密码正确。" 的具体消息。
2) 显示错误消息时,除了说哪里错了,还应提示用户如何解决问题。不要用 象 "更新数据库失败。"这样的,要提示用户怎么做:"更新数据库失败。请确保登陆id和密码正确。"
3) 显示给用户的消息要简短而友好。但要把所有可能的信息都记录下来,以助诊断问题。
3.4. 注释
软件文档以两种形式存在:外部的和内部的。外部文档(如规范、帮助文件和设计文档)在源代码的外部维护。内部文档由开发人员在开发时在源代码中编写的注释组成。
不考虑外部文档的可用性,由于硬拷贝文档可能会放错地方,源代码清单应该能够独立存在。外部文档应该由规范、设计文档、更改请求、错误历史记录和使用的编码标准组成。
内部软件文档的一个难题是确保注释的维护与更新与源代码同时进行。尽管正确注释源代码在运行时没有任何用途,但这对于必须维护特别复杂或麻烦的软件片段的开发人员来说却是无价的。
3.4.1. 文件注释
在每个文件头必须包含以下注释说明
///<summary>
///<para>Copyright (C) 2010 北京天元网络股份有限公司版权所有</para>
/// <para>文 件 名: </para>
/// <para>文件功能: </para>
/// <para>开发部门: </para>
/// <para>创 建 人: </para>
/// <para>电子邮件: </para>
/// <para>创建日期:</para>
/// <para>修 改 人:</para>
/// <para>修改日期:</para>
/// <para>备 注:</para>
/// </summary>
3.4.2. 注释规范
1) 使用vs.net中的“///”添加注释。
2) 每个声明的变量都做注释,使用行尾注释在公共制表位处对齐。
3) 语句注释避免在代码行的末尾添加注释。
4) 对一个数值变量采用不是0,-1等的数值初始化,给出选择该值的理由。
5) 如果应为某种原因使用了复杂艰涩的原理,为程序配备良好的文档和充分的注释。
6) 避免注释的二义性。
7) 在每个函数的开始,提供标准的注释样本以指示函数的用途、假设和限制很有帮助。注释样本应该是解释它为什么存在和可以做什么的简短介绍,以及该函数版本信息。 注释要求如下:
节标题 |
注释描述 |
函数名称 |
|
功能说明 |
该过程完成什么(而不是怎么完成)。 |
参数说明 |
每一个可能不明显的参数。参数分别在单独的行上,并嵌入注释。 |
返回值说明 |
函数返回值的说明。 |
全程变量说明 |
列出每个被影响的外部变量、控件、或文件及其作用 |
创建人 |
|
创建时间 |
2010/1/17 |
修改人 |
|
修改时间 |
2010/1/17 |
修改内容概要 |
哪个功能不能满足需求,或是BUG修正 |
3.4.3. 注释维护
1) 如果用 C# 进行开发,请使用 XML 文档功能。有关更多信息,请参见:MSDN中《XML 文档》。
2) 在需要的地方注释。可读性强的代码需要很少的注释。如果所有的变量和方法的命名都很有意义,会使代码可读性很强并无需太多注释。
3) 在编写代码时就注释,因为以后很可能没有时间这样做。另外,如果有机会复查已编写的代码,在今天看来很明显的东西六周以后或许就不明显了。
4) 为了防止问题反复出现,对错误修复和解决方法代码总是使用注释,尤其是在团队环境中。
5) 对由循环和逻辑分支组成的代码使用注释。这些是帮助源代码读者的主要方面。
6) 用空白将注释同注释分隔符分开。在没有颜色提示的情况下查看注释时,这样做会使注释很明显且容易被找到。
7) 移除所有临时或无关的注释,以避免在日后的维护工作中产生混乱。
8) 避免杂乱的注释,如一整行星号。而是应该使用空白将注释同代码分开
9) 避免在块注释的周围加上印刷框。这样看起来可能很漂亮,但是难于维护。
10) 建议对注释做拼写检查,保证语法和标点符号的正确使用。
11) 使用#Region和#endregion分段,并把界面会直接调用的方法分开。这样可以方便的进行统一的异常处理,即非界面直接调用的方法可以只处理特定异常,而界面直接调用的方法可以处理一般异常,方便代码检查。
3.5. 异常处理
3.5.1. 不要"捕捉了异常却什么也不做"。如果隐藏了一个异常,你将永远不知道异常到底发生了没有。
好: void ReadFromFile ( string fileName )
{
try
{
// read from file.
}
catch (FileIOException ex)
{
// log error.
// re-throw exception depending on your case.
throw;
}
}
不好: void ReadFromFile ( string fileName )
{
try
{
// read from file.
}
catch (Exception ex)
{
// Catching general exception is bad... we will never know whether it was a file error or some other error.
// Here you are hiding an exception.
// In this case no one will ever know that an exception happened.
return "";
}
}
3.5.2. 发生异常时,给出友好的消息给用户,但要精确记录错误的所有可能细节,包括发生的时间,和相关方法,类名等。
3.5.3. 只捕捉特定的异常,而不是一般的异常。
3.5.4. 不必在所有方法中捕捉一般异常。不管它,让程序崩溃。这将帮助你在开发周期发现大多数的错误。
3.5.5. 你可以用应用程序级(线程级)错误处理器处理所有一般的异常。遇到一般性错误时,此错误处理器应该捕捉异常,给用户提示消息,在应用程序关闭或 用户选择”忽略并继续“之前记录错误信息。
3.5.6. 不必每个方法都用try-catch。当特定的异常可能发生时才使用。比如,当你写文件时,处理异常FileIOException。
3.5.7. 别写太大的 try-catch 模块。如果需要,为每个执行的任务编写单独的 try-catch 模块。 这将帮你找出哪一段代码产生异常,并给用户发出特定的错误消息。
3.5.8. 如果应用程序需要,可以编写自己的异常类。自定义异常不应从基类SystemException派生,而要继承于IApplicationException。
3.5.9. 抛出了异常的方法应该在方法名注释中添加该异常,以方便在代码时直观显示并处理,如图:
编写代码时添加注释:
使用该成员时自动显示异常列表,方便捕获:
3.6. 统一操作
对于需要统一的操作,如界面、异常、公共接口需要由专门的人员进行维护,并放在专门的统一库中,开发人员负责使用这些统一库,如果有类似的需求但统一库中尚未涉及的,开发人员需向负责人员申请添加该接口,不得自行添加或修改。负责人员在修改了统一库后需要进行通知。
目前必须要对一些元素进行封装以实现统一化操作,其中包括窗体、部分按钮(保存、编辑、取消、删除、添加、搜索等)、提示窗口(错误提示、信息提示。。。。。。)、其它常用控件、异常类等,此列表将不断进行维护更新。
4. 代码检查
组内应该经常进行代码检查,将某一个或多成员的代码共享出来,大家一起针对该代码提出自己的意见,代码检查时从以下方面进行:
4.1. 界面元素统一
请检查以下各界面元素是否使用了统一封装。
4.1.1. 窗体
请参见《客服支撑系统程序设计指南》中界面设计规范,以及系统通用控件库。
4.1.2. 按钮
请参见《客服支撑系统程序设计指南》中界面设计规范,以及系统通用控件库。
4.1.3. 提示框
请参见《客服支撑系统程序设计指南》中界面设计规范,以及系统通用控件库。
4.1.4. 标点的全角与半角
4.1.5. 其它控件
4.2. 是否有硬编码
4.3. 是否有重复代码?需要重构。
4.4. 是否考虑并发操作
4.5. 注释
- 关于异常的注释参考“异常处理”节。
- 返回值有非正常类型时必须在注释的<returns>块中写上。
- 必须有<summary>注释。
- 自定义异常必须被包含在主框架的同一项目中,若该自定义异常不会抛出到所在项目之外可以例外。
- 是否使用了判断确定对象的空引用?
- 如果方法内使用throw抛出了异常,注释中<exception>块必须包含该异常。
- 方法内必须捕获所有可能出现的非一般异常,可以直接抛出或抛出自定义异常,但必须参考上条添加注释:注释中<exception>块必须包含该异常。
- 不允许在thorw时抛出一般异常(Exception),必须为自定义异常。