C#编码规范(4):编码中的一些建议
1. 避免将多个类放在一个文件里面。
避免使用大文件。如果一个文件里的代码超过300~400行,必须考虑将代码分开到不同类中。
避免写太长的方法。一个典型的方法代码在1~25行之间。如果一个方法发代码超过25行,应该考虑将其分解为不同的方法。
方法名需能看出它作什么。别使用会引起误解的名字。如果名字一目了然,就无需用文档来解释方法的功能了。
好:
void SavePhoneNumber ( string phoneNumber ) { // Save the phone number. }
不好:
// This method will save the phone number. void SaveData ( string phoneNumber ) { // Save the phone number. }
一个方法只完成一个任务。不要把多个任务组合到一个方法中,即使那些任务非常小。
好:
// Save the address.
SaveAddress ( address );
// Send an email to the supervisor to inform that 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.
// ...
}
2. 一个文件应该只有一个命名空间,避免将多个命名空间放在同一个文件里面。
使用C# 或 VB.NET的特有类型,而不是System命名空间中定义的别名类型。
好:
int age;
string name;
object contactInfo;
不好:
Int16 age;
String name;
Object contactInfo;
别在程序中使用固定数值,用常量代替。
别用字符串常数。用资源文件。
避免使用很多成员变量。声明局部变量,并传递给方法。不要在方法间共享成员变量。如果在几个方法间共享一个成员变量,那就很难知道是哪个方法在什么时候修改了它的值。
必要时使用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; } }
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. 只抛出已经显示处理的异常。
注释
别每行代码,每个声明的变量都做注释。
在需要的地方注释。可读性强的代码需要很少的注释。如果所有的变量和方法的命名都很有意义,会使代码可读性很强并无需太多注释。
行数不多的注释会使代码看起来优雅。但如果代码不清晰,可读性差,那就糟糕。
如果应为某种原因使用了复杂艰涩的原理,为程序配备良好的文档和重分的注释。
对一个数值变量采用不是0,-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 ""; } }
不必在所有方法中捕捉一般异常。不管它,让程序崩溃。这将帮助你在开发周期发现大多数的错误。
你可以用应用程序级(线程级)错误处理器处理所有一般的异常。遇到”以外的一般性错误“时,此错误处理器应该捕捉异常,给用户提示消息,在应用程序关闭或 用户选择”忽略并继续“之前记录错误信息。
不必每个方法都用try-catch。当特定的异常可能发生时才使用。比如,当你写文件时,处理异常FileIOException.
别写太大的 try-catch 模块。如果需要,为每个执行的任务编写单独的 try-catch 模块。 这将帮你找出哪一段代码产生异常,并给用户发出特定的错误消息
如果应用程序需要,可以编写自己的异常类。自定义异常不应从基类SystemException派生,而要继承于. IApplicationException。
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. 避免方法的返回值是错误代码。
别把成员变量声明为 public 或 protected。都声明为 private 而使用 public/protected 的Properties.
不在代码中使用具体的路径和驱动器名。 使用相对路径,并使路径可编程。
永远别设想你的代码是在“C:”盘运行。你不会知道,一些用户在网络或“Z:”盘运行程序。
应用程序启动时作些“自检”并确保所需文件和附件在指定的位置。必要时检查数据库连接。出现任何问题给用户一个友好的提示。
如果需要的配置文件找不到,应用程序需能自己创建使用默认值的一份。
如果在配置文件中发现错误值,应用程序要抛出错误,给出提示消息告诉用户正确值。
错误消息需能帮助用户解决问题。永远别用象"应用程序出错", "发现一个错误" 等错误消息。而应给出象 "更新数据库失败。请确保登陆id和密码正确。" 的具体消息。
显示错误消息时,除了说哪里错了,还应提示用户如何解决问题。不要用 象 "更新数据库失败。"这样的,要提示用户怎么做:"更新数据库失败。请确保登陆id和密码正确。"
显示给用户的消息要简短而友好。但要把所有可能的信息都记录下来,以助诊断问题。
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值的方法,该引用在指向下一个实例前,不会被回收。