C#编码规范

来源:互联网 发布:小米5怎么刷windows 编辑:程序博客网 时间:2024/04/29 16:15

1          关于C#编码规范

这个文档可以作为一个向导,以写强壮可靠的程序。虽然这里关注的是用C#写程序,但如果使用另外一种编程语言,很多规则和原理也是有用的。

2          文件组织

2.1      C#源文件

把每个类都放在单独的文件中,文件名字和类名一致(用.CS作为扩展名)。类文件不要太长,不要超过2000LOC。必要时,分割代码,使结构更清晰。

2.2      目录安排

为每个命名空间创建一个目录(如,对于MyProject.TestSuite.TestTier使用MyProject/TestSuite/TestTier作为路径,不要使用带“.”的命名空间)。这样更易于映射命名空间到目录。

3          缩进

3.1      分行

如果表达式不适合单行显示,应根据下面通常的原则分行:

n         在一个逗号后换行

n         在一个操作符后换行

n         在表达式的高层次处换行

n         新行与前一行在同一层次,并与表达式的起始对齐

方法分行的例子:

long MethodCall(expr1, expr2,

                        expr3, expr4, expr5);

算术表达式分行的例子:

好的:

       var = a * b / (c – g + f) +

               4 * z;

坏的风格,要避免:

       var = a * b / (c – g +

               f) + 4 * z;

第一个是好的,因为分行符合高层次规则。

3.2      空白

不要使用空格缩进 使用tabs

4          注释

4.1      块注释

通常要避免块注释,而使用C#标准的///注释来描述。如果希望使用块注释,应该使用下面的风格:

/ * Line 1

  * Line 2

  * Line 3

  */

块注释很少使用,通常是用来注释掉大块的代码。

4.2      单行注释

应该使用//注释掉一行代码,也可以用它注释掉代码块。当单行注释用来做代码解释时,必须要缩进到与代码对齐。

4.3      文档注释

单行XML注释的形式如下:

/// <summary>

/// This class…

/// </summary>

多行XML注释的形式如下:

/// <exception cref=”BogusException”>

/// This exception gets thrown as soon as a

/// Bogus flag gets set.

/// </exception>

5          声明

5.1      每行声明的数量

建议每行只有一个声明,还方便注释,如:

int level;  // indentation level

int size;   // size of table

变量的命名意义要明确。如果能够自解释,如indentLevel,就不用注释。

不好的:

int a, b; // What is ‘a’? What does ‘b’ stand for?

5.2      初始化

尽量在局部变量声明时进行初始化,例如:

string name = myObject.Name;

int val = time.Hours;

注意:初始化对话框时,尽量使用语句:

Using (OpenFileDialog openFileDialog = new OpenFileDialog())

{

       ……

}

5.3      类和接口的声明

当写C#类和接口时,应按照下面的格式规则:

n         在方法名字和参数列表的起始括号“(”之间没有空格

n         开括号“{”应出现在声明语句之后的下一行

n         闭括号“}”自己占一行,并缩进到对应的开括号位置

例如:

class MySample : MyClass, IMyInterface

{

int myInt;

 

public MySample(int myInt)

{

       this.myInt = myInt;

}

 

void Inc()

{

       ++myInt;

}

 

void EmptyMethod()

{

}

}

6          语句

6.1      简单语句

每行应该只包含一个语句。

6.2      返回语句

返回语句不应该带有最外面的括号。

不应该使用:

return (n * (n + 1) / 2);

应该使用:

return n * (n + 1) / 2;

6.3      If, if - else, if else - if else语句

if, if – elseif else – if else语句应该按照下面格式:

if (condition)

{

       ……

}

 

if (condition)

{

       ……

}

else

{

       ……

}

 

if (condition)

{

    ……

}

else if (condition)

{

    ……

}

else

{

    ……

}

注意:即使某条件下只有一个语句,也要使用大括号“{”“}”。后面的循环等语句也一样。

6.4      for / foreach语句

for语句形式如下:

for (int i = 0; i < 5; ++i)

{

    ……

}

或者使用单行形式:

for (initialization; condition; updat);

单行形式可考虑使用while语句代替。

foreach语句如下:

foreach (int i in intList)

{

    ……

}

6.5      while / do – while语句

while语句如下:

while (condition)

{

    ……

}

空的while语句形式如下:

while (condition);

do – while语句如下:

do

{

    ……

}

while (condition);

6.6      switch语句

switch语句形式如下:

switch (condition)

{

    case A :

           ……

           break;

    case B :

           ……

           break;

    default :

           ……

           break;

}

6.7      try – catch语句

try – catch语句形式如下:

try

{

    ……

}

catch (Exception e)

{

    ……

}

或者

try

{

    ……

}

catch (Exception e)

{

    ……

}

finally

{

    ……

}

6.8      属性

属性形式如下:

public string Name

{

    get

      {

              ……

      }

      set

      {

              ……

      }

}

对于抽象属性:

public string Name

{

    get;

    set;

}

6.9      枚举

枚举形式如下:

public enum Color

{

    Red,

    Green,

    Blue

}

7          空白

7.1      空行

使用空行按照逻辑关系分隔代码,能提高可读性。

在下面元素之间要使用一个空行:

n         构造函数

n         属性

n         方法

n         方法内的逻辑块

7.2      内部空格

在逗号或分号之后应该有一个空格,例如:

应该使用:

       TestMethod(a, b, c);

不应该使用:

       TestMethod(a,b,c);或者TestMethod( a, b, c );

操作符两边要有一个空格(递增和逻辑否等一元操作符除外),例如:

应该使用:

       a = b;

不应该使用:

       a=b;

应该使用:

       for (int i = 0; i < 10; ++i)

不应该使用:

       for (int i=0; i<10; ++i)或者for(int i=0;i<10;++i)

7.3      制表格式

行逻辑块应该写成类似表格格式:

string   name         = “Mr. Ed”;

int       myValue     = 5;

Test     aTest         = Test.TestYou;

8          命名约定

8.1      大写风格

8.1.1        Pascal风格

大写每一个单词的第一个字符,如TestCounter

8.1.2        Camel风格

除了第一个单词,大写其它单词的第一个字符,如testCounter

8.1.3        全部大写

如果一个标志符是只包含123个字符的缩写,可以全部大写,例如:

public class Math

{

    public const double PI = ......

}

8.2      命名规则

.Net中,通常认为使用下划线和匈牙利符号命名是不好的。

匈牙利符号定义了一组名字的前缀和后缀,来表示变量的类型。这种命名风格在早期的windows编程中广泛使用。在MFC编程中,还习惯使用m_作为成员变量的前缀。这些风格在.Net中不应该使用。记住:一个好的变量名字应该描述语义,而不是类型。

GUI代码是个例外。所有GUI元素类型(如Button)的域和变量名字,应该以它的类型全名作为后缀,例如:

System.Windows.Forms.Button       cancelButton;

System.Windows.Forms.TextBox      nameTextBox;

8.2.1        classstructnamespace命名规则

n         名字必须是名词或名词短语

n         异常类要以Exception作为后缀

n         不要使用任何前缀

n         使用Pascal风格

8.2.2        interface命名规则

n         用名词,名词短语或者描述行为的形容词命名接口。(如IComponentIEnumbetable

n         使用Pascal风格

n         使用I作为名字的前缀,I后面的字符(接口名字的第一个字符)要大写

8.2.3        enum命名规则

n         枚举类型名和值的名字都使用Pascal风格

n         枚举类型和值都没有前缀和后缀

8.2.4        域命名规则

n         使用描述性的名字,能充分表示出变量的含义

n         对于静态的readonlyconst域,用名词,名词短语或名词缩写命名

n         public域使用Pascal风格

n         protectedprivate域使用Camel风格

8.2.5        参数命名规则

n         使用描述性的名字,能充分表示出变量的含义

n         使用Camel风格

8.2.6        变量命名规则

n         尽量使用含义明确的名字

n         如果变量仅用来在循环中计数,应优先使用ijklmn

n         使用Camel风格

8.2.7        方法命名规则

n         用动词或动词短语命名

n         使用Pascal风格

8.2.8        属性命名规则

n         使用名词或名词短语命名

n         使用Pascal风格

8.2.9        事件命名规则

n         事件控制器要带有EventHandler后缀

n         使用sendere命名两个参数

n         事件参数类要带有EventArgs后缀

n         考虑使用动词命名事件

n         对于有“之前”或“之后”概念的事件,要使用现在时或过去时命名

n         使用Pascal风格

9          编程实践

9.1      书写顺序

书写类时,按照从上到下的顺序,类成员应该是域,构造函数,属性,方法。

9.2      成员可视性

类的域都应该是private,如果需要被外部访问,使用public属性进行访问。但也有例外:

n         如果类的域仅作为一种数据集合,可以将域设定为public,这样的类不要有任何方法

n         常量域,静态域或静态常量域可以设定为public

private是默认的类成员访问修饰符,可以省略,但为了明确表达,应该书写出来。

9.3      功能单一

类的功能要单一,不要组合没有直接关联的功能。方法也一样。一个方法只完成一个任务,不要把多个任务组合进一个方法,即使那些任务很小。就是说,应该以逻辑功能来界分类或方法。

9.4      使用枚举

不要使用数字或字符串来指示离散值,应该使用枚举。

不好的:

void SendMail(string message, string mailType)

{

switch (mailType)

{

case "Html" :

        ……

        break;

case "PlainText" :

        ……

        break;

case "Attachment" :

        ……

        break;

default :

        ……

        break;

}

}

好的:

enum MailType

{

        Html,

        PlainText,

        Attachment

}

 

void SendMail(string message, MailType mailType)

{

switch (mailType)

{

case MailType.Html :

        ……

        break;

case MailType.PlainText :

        ……

        break;

case MailType.Attachment :

        ……

        break;

default :

        ……

        break;

}

}

9.5      捕获异常

只捕获特定的异常,如读取外部文件,而不要捕获一般异常,有利于调试排错。

不要捕获了异常却什么也不错,应该做适当处理,并把结果反馈到界面。如果隐藏了一个异常,将很难知道异常到底发生了没有,或者在哪里发生的。

 

注意事项

 

1.  避免将多个类放在一个文件里面。

2.  一个文件应该只有一个命名空间,避免将多个命名空间放在同一个文件里面。

3.  一个文件最好不要超过500行的代码(不包括IDE产生的代码)。

4.  一个方法的代码长度最好不要超过25行。

5.  避免方法中有超过5个参数的情况。如果超过了,则应使用 struct 来传递多个参数。

6.  每行代码不要超过80个字符。

7.  原则上,尽量不要手工的修改机器产生的代码。

a)  如果需要编辑机器(IDE)产生的代码,编辑格式和风格要符合该编码标准。

b)  尽可能地使用片断类来把被保持的部分分解为各个因素

    注:这里的翻译参考了灵感之源老兄的说法,在Visual c#2005中,C#的语法已经支持partial修饰符,它的作用是可以将一个完整的类分解各个部分类,在编译时,编译器会将它们构造为一个类。

具体请参考

http://blog.joycode.com/zhanbos/archive/2004/05/25/22402.aspx 

http://weblogs.asp.net/jaybaz_ms/archive/2004/04/28/122392.aspx

8.  避免利用注释解释显而易见的代码。

a)  代码应该可以自解释。好的代码本身就应具体良好的可读性,所使用的变量和方法命名一般情况下不需要注释。

9.  文档应该仅用于assumptions, algorithm insights等等.

10.  避免使用方法级的文档。

a)  使用扩展的API文档进行说明。

b)  只有在该方法需要被其他的开发者使用的时候才使用方法级的注释。(在C#中就是///)

11.  不要硬编码数字的值,总是使用构造函数设定其值。

12.  只有是自然结构才能直接使用const(常量),比如一个星期的天数。

13.  区别只读变量及常量的使用方法,如果想实现只读变量,可以直接使用readonly 修饰符。

public class MyClass

{

   public readonly int Number;

   public MyClass(int  someValue)

   {

      Number = someValue;

   }

   public  const int  DaysInWeek = 7;

}

14.  每个假设必须使用Assert检查

a)  平均每15行要有一次检查(Assert)

using System.Diagnostics;

    object GetObject()

{…}

    object obj = GetObject();

Debug.Assert(obj != null);

15.  代码的每一行都应该通过白盒方式的测试。

16.  只抛出已经显示处理的异常。

17.  在捕获(catch)语句的抛出异常子句中(throw),总是抛出原始异常,用以维护原始错误的堆栈分配。
  catch(Exception exception)

{   

      MessageBox.Show(exception.Message);

      throw ;  //和throw exception一样。

}


  注:同理,不推荐在循环语句中,进行直接的return操作。
    for (int i=0;i<100;i++)
    {
        if (i==10)
        {
            return; //不推荐的方式
        }
    }

18.  避免方法的返回值是错误代码。

19.  尽量避免定义自定义异常类。

20.  当需要定义自定义的异常时:

a)  自定义异常要继承于ApplicationException。

b)  提供自定义的序列化功能。

21.  避免在单个程序集里使用多个Main方法。

22.  只对外公布必要的操作,其他的则为internal。

23.  避免使用友元程序集,因为它会增加程序集间的耦合度。

24.  避免编写从指定的位置加载的程序集的代码。

25.  使应用程序集尽量为最小化代码(EXE客户程序)。使用类库来替换包含的商务逻辑。

26.  避免给枚举变量提供显式的值。

//正确方法 

public enum Color

{   

   Red,Green,Blue

}

//避免

public enum Color

{   

   Red = 1,Green =  2,Blue = 3

}

27.  避免指定特殊类型的枚举变量。

//避免 

public enum Color  : long

{   

   Red,Green,Blue

}

28.  即使if语句只有一句,也要将if语句的内容用大括号扩起来。

29.  避免使用trinary条件操作符。

30.  避免在条件语句中调用返回bool值的函数。可以使用局部变量并检查这些局部变量。

bool IsEverythingOK()

{…}

//避免

if (IsEverythingOK ())

{…}

//替换方案 

bool ok = IsEverythingOK();

if (ok)

{…}

31.  总是使用基于0开始的数组。

32.  在循环中总是显式的初始化引用类型的数组。

public class MyClass

{}

MyClass[] array = new  MyClass[100];

for(int index = 0; index < array.Length;  index++)

{

   array[index] = new  MyClass();

}

33.  尽量不要提供public 和 protected的成员变量,使用属性代替他们。

34.  避免在继承中使用new而使用override来进行替换。

35.  在不是sealed的类中总是将public 和 protected的方法标记成virtual的。

36.  除非使用interop(COM+ 或其他的dll)代码否则不要使用不安全的代码(unsafe code)。

37.  避免显式的转换,使用as操作符进行兼容类型的转换。

Dog dog = new GermanShepherd();

GermanShepherd shepherd = dog  as  GermanShepherd;

if (shepherd != null )

{…}

38.  当类成员包括委托的时候

a)  在调用委托前,将它拷贝到一个本地变量中,用以避免并发争用条件。

b)  在调用委托之前一定要检查它是否为null

public class MySource

{

   public event EventHandler  MyEvent;

   public void FireEvent()

   {
   //将委托拷到一个本地变量中。
      EventHandler temp = MyEvent;

      //确定它是否为空
      if(temp != null )

      {

         temp(this,EventArgs.Empty);

      }

   }

}  

39.  不要提供公共的事件成员变量,使用事件访问器替换这些变量。

public class MySource

{

   MyDelegate m_SomeEvent ;

   public event MyDelegate SomeEvent

   {

      add

      {

         m_SomeEvent += value;

      }

      remove

      {

         m_SomeEvent -= value;

      }

   }

}

40.  使用一个事件帮助类来公布事件的定义。

41.  总是使用接口。

42.  类和接口中的方法和属性至少为2:1的比例。

43.  避免一个接口中只有一个成员。

44.  尽量使每个接口中包含3-5个成员。

45.  接口中的成员不应该超过20个。

a) 根据实际情况可能限制为12个

46.  避免接口成员中包含事件。

47.  避免使用抽象方法而使用接口替换。

48.  在类层次中显示接口。

49.  推荐使用显式的接口实现。

50.  从不假设一个类型兼容一个接口,并应防止查询那些接口。

SomeType obj1;

IMyInterface obj2;

 

/* 假设已有代码初始化过obj1,接下来 */

obj2 = obj1 as IMyInterface;

if (obj2 != null)

{

   obj2.Method1();

}

else

{

   //处理错误

}  

 

51. 表现给最终用户的字符串(一般指UI界面中的部分)不要使用直接编码,而应该要使用资源文件来替换。

          注:这样做的目的是方便软件的本地化。

 

52.  不要直接编写可能会更改的基于配置的字符串,比如连接字符串。


53.  当需要构建较长的字符串的时候,应该考虑使用StringBuilder不要使用string来处理。

注:string每次要创建一个新的实例,较占用空间,并产生了相对StringBuilder更大的性能消耗。对于过于频繁的字符串操作,采用StringBuilder是一个良好的习惯。


54.  避免在结构里面提供方法。

a)  建议使用参数化构造函数

b)  可以重载操作符


55.  总是要给静态变量提供静态构造函数。


56.  在能够使用早期绑定的情况下,尽量避免使用后期绑定。

   注:后期绑定虽然灵活,但带来的不仅仅是性能上的消耗,更多的是编码上的复杂性和混乱的逻辑。

57.  使用应用程序的日志和跟踪。


58.  除非在不完全的switch语句中否则不要使用goto语句。

     注:原则上不应使用goto语句,除非在能够大大减轻编码的复杂性,并不影响可读性的前提下才允许使用。


59.  在switch语句中总是要有default子句来显示信息(Assert)。

int number  = SomeMethod();

switch(number)

{

   case 1:

      Trace.WriteLine("Case 1:");

      break;

   case 2:

      Trace.WriteLine("Case 2:");

      break;

   default :

      Debug.Assert(false);

      break;

}

60.  除非在构造函数中调用其他构造函数否则不要使用this指针。

// 正确使用this的例子

public class MyClass

{

   public MyClass(string message )

   {}

   public MyClass()  : this("hello")

   {}

}

61.  除非你想重写子类中存在名称冲突的成员或者调用基类的构造函数否则不要使用base来访问基类的成员。

// 正确使用base的例子

public class Dog

{

   public Dog(string name)

   {}

   virtual public void Bark( int howLong)

   {}

}

public class GermanShepherd : Dog

{

   public GermanShe pherd(string name): base (name)

   {}

   override public void Bark(int  howLong) 

   {

      base .Bark(howLong);  

   }

}

62.  基于模板的时候要实现Dispose()和Finalize()两个方法。

63.  通常情况下避免有从System.Object转换来和由System.Object转换去的代码,而使用强制转换或者as操作符替换。

class SomeClass

{}

//避免:

class MyClass<T> 

{   

   void SomeMethod(T t)   

   {

      object temp = t;      

      SomeClass obj = (SomeClass)temp;    

   }

}

// 正确:

class MyClass<T> where T : SomeClass

{   

   void SomeMethod(T t)   

   {

      SomeClass obj = t;   

   }

}

64.  在一般情况下不要定义有限制符的接口。接口的限制级别通常可以用强类型来替换之。

public class Customer

{…}

//避免:

public interface IList<T> where T : Customer 

{…}

//正确:

public interface ICustomerList : IList<Customer> 

{…}

65.  不确定在接口内的具体方法的限制条件。

66.  总是选择使用C#内置(一般的generics)的数据结构

67、初始化类的实例时,除非十分必要,否则不要赋null值。
68、使用后的实例,尽量不要将该实例的引用赋值和为nul,尤其是采用public来修饰的类成员l。
1) 如果该实例是临时引用,请使用using语句,然后在程序块中使用。
2) 如果需要释放资源,应可能地使用Dispose,采用null值的方法,该引用在指向下一个实例前,不会被回收。

原创粉丝点击