Skip to content

Latest commit

 

History

History
503 lines (403 loc) · 23.3 KB

编码规范.md

File metadata and controls

503 lines (403 loc) · 23.3 KB

C#编码规范

1.概述

2.命名规范

3.函数编码

4.异常处理

5.注释规范

6.日志规范

1.概述

本文档在《C#程序设计与编码规范》2005年版(Old_Backup/编码规范目录)内的基础上缩编修订而成。更多的.net设计知识,请参阅微软出版的相关指南。

2.命名规范

2.1.大小写约定

2.1.1.大小写样式

  • Pascal大小写

    将标识符的首字母和后面连接的每个单词的首字母都大写。例如:BlackColor。

  • Camel大小写

    标识符的首字母小写,而每个后面连接的单词的首字母都大写。例如:blackColor。

  • 大写

    标识符中的所有字母都大写。仅对于由两个或者更少字母组成的标识符使用该约定。例如:IO。

  • Private Camel大小写

    标识符首字母为下划线”_”,后面单词采用Camel大小写。例如:_blackColor。

2.1.2.标识符大小写规则

当标识符由多个单词组成的时候,不要在词间使用诸如”_”(Unix C程序样式 )和”-“之类的分隔符。而要通过大小写来分隔单词。

下面给出一般标识符的大小写规则:

1.名字空间、类型、属性、方法、事件、只读字段及所有public成员的名称采用Pascal大小写。

2.参数、局部变量名称采用Camel大小写。

3.私有字段,Private Camel大小写。

下表汇总了大写规则,并提供了不同类型的标识符的示例:

标识符 大小写 例子
名字空间 Pascal System.Drawing
Pascal AppDomain
接口 Pascal IDisposable
枚举类型 Pascal ErrorLevel
枚举值 Pascal FatalError
方法 Pascal ToString
属性 Pascal BackColor
事件 Pascal ValueChanged
只读静态字段 Pascal RedValue
参数 Camel typeName
局部变量 Camel targetCount
私有字段 Private Camel _brushColor

2.1.3.首字母缩写大小写规则

首字母缩写(Acronyms)是由短语内各单词的字母构成。例如:HTML是Hypertext Markup Lanugage的首字母缩写。只有众所周知的首字母缩写才可以包含在标识符里。首字母缩写(Acronyms)与缩写词(Abbreviation)不同,缩写词(Abbreviation)仅简化一个单词。例如:ID是identifier的缩写词。一般情况下,不要使用缩写词。

特例:ID及OK两个缩写词可被用于标识符。但是应该依据使用环境,遵循Pascal或Camel大小写。

首字母缩写的大小写依赖于它的长度。所有的首字母缩写至少两个字母长。如果正好是两个字母长,被称作短首字母缩写(short acronyms)。三个或三个以上长度的首字母缩写,被称作长首字母缩写(long acronyms)。

下面给出长短首字母缩写的规则,标识符的大小写优先级更高。

4.短首字母缩写的两个字母均应大写,除非是Camel大小写标识符的第一个词。

例如:属性名称DBRate中首字母缩写DB,是Pascal大小写标识符首单词。

例如:参数名称ioChannel中首字母缩写io,是Camel大小写标识符首单词。

5.长首字母缩的第一个字母大写,除非是Camel大小写的标识符的第一个词。

例如:类名称XmlWriter中的首字母缩写Xml,是Pascal小写标识符首单词。

例如:参数名称htmlWriter中的首字母缩写html,是Camel大小写标识符首单词。

2.1.4.复合词与通用术语大小写规则

6.不要将所谓封闭形式的复合词内的单词大写。他们是被作为单独的词的复合词,比如”endpoint”。确定一个词是否为封闭形式的复合词,查最新字典即可。

例如:hashtable是一个封闭形式的,从而应当作为单独的词来对待及处理大小写。采用Pascal大小写,它是Hashtable;采用Camel大小写,它是hashtable。

下面一些通用术语不是封闭形式的复合词,采用Pacal及Camel两种方式展现。

  • BitFlag (bitFlag)
  • FileName (fileName)
  • LogOff (logOff)
  • LogOn (logOn)
  • SignIn (signIn)
  • SignOut (signOut)
  • UserName (userName)
  • WhiteSpace (whiteSpace)

2.1.5.大小写敏感

7.有些语言大小写不敏感,因此不要仅通过大小写来区分不同的标识符。

8.Url使用aspx文件名的地方,与aspx文件名大小写一致。

2.2.常规命名约定

此部分是用于所有标识符命名,后面的章节将讨论特殊的元素,如名字空间及属性。

2.2.1.单词选择

9.标识符命名均采用英文,注意拼写正确。

10.选择可读的标识符名称。

比如:属性名称HorizontalAlignment比AlignmentHorizontal在英语中更可读。

11.可读性比缩写简单性更重要。

比如:属性名称CanScrollHorizontally比ScrollableX更好。

12.请不要使用任何下划线”_”、连字符”-“等非字母字符。私有字段首字符除外(Private Camel样式)。

13.避免使用与C#、VB、C++语言关键字冲突的标识符名称。

2.2.2.缩写

一般情况下不应该使用缩写,这样会使程序不容易读,另外也很难确定缩写是否广为人知。

14.不要将缩写作为标识符中间的部分。

例如:使用OnButtonClick而不是OnBtnClick

15.在必要的时候,为提高程序的简洁及可读性,使用广为人知的缩写。

2.2.3.特定语言命名

16.用语意命名标识符,而不要用C#关键字。

例如:使用GetLength而不是GetInt

17.当标识符没有特定语意的时候,采用CLR类型名,而不要用C#关键字。

例如:将数据转换成Int16类型的函数,应该被命名为ToInt16,而不是ToShort,因为short仅仅是Int16在C#语言中的对应类型名。

18.当标识符即没有特定语意,其类型也并不重要的时候,应采用一般的名称,诸如value或item。

2.3.文件与目录命名

19.每个类型(包括类、结构、接口、枚举)存放在一个文件,文件名与类型名称一致。自动生成程序除外。

20.特殊的名字空间,放在单独的目录里,目录名字与名字空间相一致。

2.4.程序集命名

21.为程序集命名,以提示其主要功能,程序集的名称最好和名字空间名称相一致。

采用如下方式命名:

WanFangData.<Component>.dll

其中Component包含一个或多个点分隔的字句。

例如:WanFangData.WebContorls.dll

2.5.名字空间命名

2.5.1.一般规则

名字空间的名称,应能提示在此名字空间内的所有类型的主要功能。比如System.Socket名字空间所包含的类型,能够让程序员通过Socket方式进行网路通讯编程。

一般的命名规则是:

WanFangData.(<ProductName>|<Technology>)[.<Feature>][.<Subnamespace>]

例如:Micorsoft.WindowsMobile.DirectX

22.名字空间的名字应该有很长的生命期,因此不要在里面加版本号、机构名称等易变的名称。

23.采用Pascal大小写。

24.在适当的时候使用复数名称,缩写除外。

例如:应写System.Collections而不是System.Collection。

例如:应写System.IO而不是System.IOs。(缩写)

25.不要让名字空间及类具有相同的名称。

例如:不要在一个名为Debug的名字空间中同时提供一个名为Debug的类。

2.5.2.名字空间及类型的名字冲突

应尽量避免与已经存在的类库有名字冲突,否则用户将不得不改变程序,并使用限定名称。下面的讨论将与四类名字空间有关:

  • 应用模型名字空间

    诸如System.Windows.Forms,System.Web等与应用密切相关的名字空间。不同的应用很少同时在一个程序内使用,因此不容易出现冲突。

  • 基础名字空间

    基础应用提供提供特殊支持,并很少在代码中引用。比如:.Designer,.Permissions名字空间。很难出现冲突。

  • 核心名字空间

    核心名字空间是System.*名字空间。开发人员应该尽力,不产生名字冲突。(可以查Document,以识别此名称是否已经存在)

  • 技术名字空间

    技术名字空间一般为Company.Technology.*,应避免与其冲突。

26.不要引入一般类型名称,应该使用限定的一般类型名称。

例如:Element,Node,Log及Message等。这样很容易产生冲突。

限定后:FormElement,XmlNode,EventLog,SoapMessage。

27.不要引入与程序模型名字空间冲突的类型名。

例如:为Windows Form应用的用户写的控件库,请不要将类型命名为CheckBox,因为此名称已经存在于Windows Form应用中。

28.不要与核心名字空间名字冲突。

例如:不要使用Dictionary类型名称,因为它在核心名字空间里存在。

29.不要在统一的技术名字空间内,使用相同的类名。

2.6.类、结构、接口命名

2.6.1.一般规则

30.类型使用名词、名词短语或形容词短语,名称应反映使用场景。

31.类名称不要加前缀(比如C、T)。(注:Delphi等语言的约定)

32.考虑子类采用基类名称作为后缀。

例如:从Stream继承的类,采用Stream作为后缀。

例如:从Exception继承的类,采用Exception作为后缀。

33.接口前面都加字母“I”前缀

34.当定义接口/类对时,接口的默认实现类名称与接口名称只有前缀I有差别。

例如:IAsynResult的默认实现名称为AsynResult。

2.6.2.范型参数命名

35.范型参数应使用有描述性名称,除非单个字母足够且描述性名称并不增加什么价值。

例如:IDictionary<TKey,TValue>。

36.需要使用单字母命名类型参数的时候,使用字母“T”。

37.类型参数前缀加字母“T”。

例如:TKey。

38.考虑在范型参数名称中加入类型约束。

例如:参数限制为ISession的,应被命名为TSession。

2.6.3.一般类型命名

下面规则,通过前后缀,帮助开发人员识别类的特定使用情景。

39.自定义属性,需要加后缀Attribute。

例如:ObsoleteAttribute。

40.在事件(event)中使用的类型,需要加后缀EventHandler。

例如:AssemblyLoadEventHandler

41.不用于事件的委托(delegate)类型,需要加后缀Callback。

42.继承System.EventArgs的类,需要加后缀EventArgs。

43.继承System.Exception的类,需要加后缀Exception。

44.实现System.Collecions.IDictionary接口的类,需要加后缀Dictionary。

45.实现System.Collections.(ICollection|IList|IEnumerable)接口的类,需要加后缀Collection。

46.继承System.IO.Stream的类,需要加后缀Stream。

47.继承System.Security.CodeAccessPermission的类,需要加后缀Permission。

2.6.4.枚举命名

48.不要在枚举值前加前缀。

例如:不要写成如下形式

public enum Teams
{
    TeamsAlpha,
    TeamsBeta,
    TeamsDelta
}

应该写为:

public enum Teams
{
    Alpha,
    Beta,
    Delta
}

49.不要在枚举类型加后缀Enum。

50.不要在Flags枚举类型加后缀Flags。

51.用单数词作为枚举名称,除非它是Flags枚举类型。

52.用复数词作为Flags枚举类型。

2.7.类成员命名

2.7.1.方法命名

53.方法名称应为动词或动词短语。

应从使用者的角度命名,选择描述函数做什么的动词,而不是怎么做的动词。

2.7.2.属性命名

54.属性采用名词、名词短语或形容词命名。

55.不要让属性名称与Get方法的名称相匹配。

例如:属性名为EmployeeRecord,同时有个方法名为GetEmployeeRecord。使用者将无从选择。

56.布尔类型属性,采用肯定语气命名。使用Is,Can或Has作前缀,如果他们能增加清晰性。

例如:使用CanSeek而不是CantSeek。

57.考虑属性使用与其类型相同的名称。

例如:属性类型为CacheLevel,其名称也为CacheLevel。

2.7.3.事件命名

58.事件采用动词或动词短语命名。

59.让事件名称拥有前后的事件概念,采用现在进行时或过去时。

例如:在窗口关闭之前的事件,被称为Closing。

例如:在窗口关闭之后的事件,被称为Closed。

60.不要在事件名称中使用Before、After前缀或后缀,来标示前后事件。

61.使用两个参数Sender,e作为事件处理参数。Sender类型为object,e类型为EventArs或其继承类型。

62.EventHandler,EventArgs相关命名见一般类型命名。

2.7.4.字段命名

63.采用名词或名词短语命名。

2.8.参数命名

64.使用描述性的名称。在大多数情形下,参数类型及其名称,应能确定参数的用途。

65.命名应该基于参数的含义而不是类型。

3.函数编码

为了增强程序清晰新、可读性,应遵循以下规定。

3.1.空格、空行与缩进的使用

66.函数参数在”,”后需要空格。

例如:Function(param1, param2, param3);

67.各种双目操作符,比如“=”、“<“等,前后需要有空格。

68.if, while等关键字后面需要有空格。

69.每次使用嵌套,嵌套体的所有内容必须缩进,使用Tab作为缩进符号。

70.每个函数之间空两行。

71.适当的增加空行,以区分方法内的大块逻辑。

有时候在同一个方法中有明显的程序功能归组情况,而又不需要划分到单独的方法,此时应当用空行加以区分,并加注释。

3.2.一般编码约定

72.代码行数不应超过80行。(不超出两屏长度)

行数过多的代码不易于阅读,这也说明了模块化分可能存在问题。应该考虑将一个大的方法体拆分为更小的方法,以增加代码的清晰性。

73.一个代码行长度不应超过80个字符。(不超出一屏宽度)

过长的代码行不易于阅读,当一行的确需要写很长时,应当折行书写。

74. 一般情况下不可以使用goto语句,除非goto能够加强程序的清晰性。

有时需要一次跳出多层循环,使用goto更为清晰。

75.诸如for、if、foreach、while等嵌套层数不要超过3层,try-catch除外。

主体逻辑代码嵌套过深,不易于阅读及理解。这也说明了逻辑或模块化分可能存在问题。应考虑修改逻辑,或将嵌套内的程序划分到一个更小的方法中。以增加清晰性。

76.变量在离其最近使用的地方声明。

不要采用C或Pascal等语言在函数开头声明参数的方式。应该在使用时声明。

77.同一个类型的不同方法中不要出现重复代码。

同一个类型的不同方法中出现大块的重复性代码,说明模块划分出现了问题,应当将重复性的代码划分到一个更小的方法中。以增加清晰性。

4.异常处理

78.在框架代码中,不要通过捕获非特定异常的方式(如:Exception、SystemException),淹没(swallow)错误。

你可以捕获上述的异常,如果捕获的目的是重新抛出异常,或将异常交给其他线程处理。下面展示了一个错误处理的例子:

public class BadExceptionHandlingExample1
{
    public void DoWork()
    {
        // 做一些可能抛出异常的工作
    }
    public void MethodWithBadHandler()
    {
        try 
        {
            DoWork();
        }
        catch (Exception e)
        {
            // 淹没了异常并继续执行
        }
    }
}

79.在应用代码中,避免通过捕获非特定异常的方式(如:Exception、SystemException),淹没(swallow)错误。有些情况下在应用中淹没错误是可以接受的,但是这种情况很少见。

应用程序不应淹没可能造成意外状态的异常,如果你不能确认所有异常造成的可能结果,你应当让程序终止而不是淹没异常。

80.捕获特定的异常,如果你理解为什么它会从给定的上下文中抛出。

你应该只捕获那些你可以恢复的异常。例如:打开不存在文件造成的FileNotFoundException可以被应用程序处理,因为它可以将问题告知用户,并让用户设置其他文件名。而打开文件请求抛出的ExcutionEngineException不应当被处理,因为低层发生的问题无法被知晓,并且应用程序也不能确认继续运行是否安全。

81.不要过度使用catch。异常应当尽可能向调用堆栈上层传递。

捕获无法正确处理的异常,会隐藏重要的调试信息。

82.将清理代码放在finally块中,而不是catch块中。对于书写优良的代码,try-finally比try-catch更为常见。

catch的目的是为了让你处理异常(例如,记录非致命错误)。finally的目的是为了让你执行清理代码而不管异常是否抛出。如果你分配了有限的资源,比如数据库联接或stream,你应当将释放它们的代码放在finally块中。

83.当捕获并重新抛出异常时,使用空的throw。这是保存调用堆栈的最好的方法。

例如:

public void MethodWithBadCatch(Object anObject)
{
    try 
    {
	    // 引起异常的方法
        DoWork(anObject);
    }
    catch (ArgumentNullException e)
    {
        System.Diagnostics.Debug.Write(e.Message);
        // 这样是错误的
        // 会造成跟踪堆栈指向此处为错误地址,而非指向DoWork。
        throw e; 
        // 应该直接写成
        // throw;
    }
}

5.注释规范

5.1.C#标准///注释的使用

84.每个类必须注释,采用C#标准///注释,并在Summary中写明此类的主要功能,根据需要在Remark中进行详细描述,如关键设计思路。

85.每个属性必须注释,采用C#标准///注释,并在Summary中写明此属性的含义。

86.每个方法必须注释,采用C#标准///注释,并在Summary中写明此函数主要功能。根据需要在Remark中进行详细描述。

5.2.一般注释

87.当代码无法通过直接阅读而清晰获理解它的功能的时候,需要加注释。

例如:

if (password ==12345) // 这是超级密码,通过后拥有管理员权限

88.当一段代码有单一目的,而没有分隔为一个函数的时候,用注释标明分段功能。

例如:

// 添加用户
Line1;
Line2;

// 添加用户组
Line1;
Line2;

89.当一个嵌套很长,或者有多个嵌套时,需要在结尾加上注释。

例如:

if (a > b)
{
    ......
} // end if (a > b)

5.3.其他注释

90.在准备添加代码的地方加入//TODO 注释。

91.类似功能的一组函数,需要用#region来包围起来,并做说明。

例如:一组函数都是和用户帐务操作有关的。

#region 用户帐务操作相关函数
Function1;
......
#endregion

6.日志规范

92.对于包含方法成员的类,尽可能使用log4net,以方便程序调试。Log成员定义写在类代码的第一行,内容如下:

private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

93.需要加条件判断是否需要记录log,以增加程序性能,除非log直接输出简单字符串。

例如:

log.Debug(“简单字符串”); // 不需要判断

例如:

if (log.IsDebugEnabled) // 有字符串等操作,需要判断
    log.Debug("成功添加用户:" + user.UserID); 

94.日志级别:

  • Fatal级:程序重大错误,以致无法启动或继续运行。
  • Error级:程序本身出错,或加载资源错误。注意此错误不是用户造成的错误,比如输入密码错。
  • Warning级:用户的一些错误可以记录到此级别,比如用户名密码输入错误。
  • Info级:程序运行时,会将log开到这个级别。此级别记录让用户看到的日志信息。比如程序启动,重要配置文件加载,访问某个页面等较大的事件。
  • Debug级:琐碎的方法调用过程记录。诸如方法进入、返回,方法内的重要事件。

95.重要函数(比如Page_Load)记录用时,可使用StopWatch。

96.公共(public)方法或属性内的日志,需要记录方法或属性名。

例如:log.Debug(“MethodName 开始准备数据表格”);

97.公共(public)方法,在方法代码第一行记录日志,标明此方法被调用并记下传入参数。(即进入时记录。)

例如:

    public void AddUser( string userName, string password)
    {
        if (log.IsDebugEnabled)
            log.Debug(“AddUser调用 username:+ userName  +, password:+ password);
        ......
    }

98.公共(public)方法返回前,记录日志,标明此方法结束并记下返回结果。如果返回内容过于复杂,比如为DataSet,可以简单记录其特征(记录数)。如果没有返回结果,比如void方法,直接标明返回即可。(即返回时记录。)

例如:

log.Debug(“AddUser 成功返回”);

99.在捕获异常时,需要记录日志,以及异常内容。(即截获异常时记录。)

例如:

catch(Exception ex)
    log.Debug(“AddUser出错”, ex);

100.在方法内有重要逻辑过程需要记录时,记录日志。(即重要逻辑记录。)

101.在对外提供接口,而接口实现组件可由第三方开发时。需要在接口调用前后记录日志。(即三方组件调用时记录。)

此条规则可以帮助我们迅速定位第三方错误,而不会因为两方都未正确记录日志而纠缠不清。

102.最外层代码(如Page_Load)要在最外层写try,catch(Excpetion)的代码,并记录异常。如果不知道如何处理,直接throw。