C#编程规范
烟台创迹软件——C#编程规范2006年11月3日最新版
1.方针
这份规范的制定方针是,对写出易读的代码提供支持。在实际的编码中,为使所有的C#项目开发成员依据该标准编写出易读性强的代码。请大家认真阅读,遵守此规范。
2.文件构成
(1)文件名
对于Public类,以它的类名做成一个文件。
例:把public class Customer放入Customer.cs类文件里面。
封装内的非公共类,可以包含在那个主要被使用的公共类的文件里。
Copyright (c) 2000,2001 Eiwa System Management, Inc. Object Club Kenji Hiranabe13/04/15
异常处理类,多个处理类可以放在一个文件里。
(2) 文件的位置
在确定了项目的根目录后,在各个目录层次连接的地方放入命名空间的“.”。但是,对应于solution/project命名空间的层次,在目录里使用solution名/project名。
例:
命名空间:
https://www.doczj.com/doc/f77660500.html,anizationName.TechnologyName.CoreFeatureName.SubFeatureName
对应于solution SolutionName 的命名空间:TechnologyName
对应于项目ProjectName 的命名空间:CoreFeatureName
上述命名空间所对应的代码存放的路径:
C:\CompanyName\OrganizationName\SolutionName\ProjectName\SubFeatureName
(3) 测试类名
类ClassName的单元测试类名为ClassNameTest。Solution的单元测试的名称命名为SolutionNameTests。
例:类Customer作成测试类CustomerTest.cs
例: Solution CsSample作成CsSampleTests.csproj
理由:可以保持命名的一致性。测试代码可以成为使用方法的样本和现成的例子。
(4) 测试类的位置
在和被测试类相同层次的目录及子目录里,配置测试类的位置。
例:
被测试类的位置:
C:\CompanyName\OrganizationName\SolutionName\ProjectName
测试类的位置:C:\CompanyName\OrganizationName\SolutionName\SolutionNameTests
C:\CompanyName\OrganizationName\SolutionName\SolutionNameTests\ProjectNameTes
ts
理由:如果不放在物理位置相近的地方,很容易忘掉。至于和成品代码分离的时候,使用其他工具(NAnt的build文件等)可能会有所调整。
3.命名规则
(5)命名空间
使用“公司名.组织名.技术名.功能名”的形式。另外,技术名和Solution名、功能名和项目 (Project) 名要一一对应。
using https://www.doczj.com/doc/f77660500.html,anizationName.TechnologyName.FeatureName
(6) 文件名
公共类名和文件名必须相同。(区分大小写)
(7) 类名
开头是大写。以后的单词首字母大写。
class PascalCasing
(8) 异常处理类的类名
在类名最后加上Exceptio n。
class ClassNameEndsWithException
(9) 接口名
与类名的命名方法相同。经常在开头添加“I”。
interface INameOfInterface
另外,在类里面使用到的功能,也加到名字里。功能用形容词来表示,用-able结尾。
例: IEnumerable, ICloneable, IXmlSerializable, …
(10)密封类名
要和Interface进行必要的区别,在末尾添加“Impl”。
class ClassNameEndsWithImpl
(11) 抽象类名
在没有恰当的名字来表示抽象类时,以Abstract开头,在其后跟上可以联想到的子类的名字。
abstract class AbstractBeforeSubClassName
(12) 常量(Const)
用大写字母或者是用下划线“_”连接大写字母来命名。
const int UPPERCASE= 0;
const int UPPERCASE_WITH_UNDERSCORES= 0;
(13) 枚举型(enum)
开头大写,其后单词首字母大写。
enum PascalCasing
用枚举型表示多个Bit Field时,加上FlagsAttribute。
[Flags] enum PascalCasings
(14) 枚举值
开头大写,其后单词首字母大写。
PascalCasing
(15) 事件(Event)名
开头大写,其后单词首字母大写。
event PascalCasing()
(16)方法(Method)名
开头大写,其后单词首字母大写。
void PascalCasing()
object PascalCasing()
(17) Factory方法(新建对象)
X NewX()
X CreateX()
(18) Convert方法(转换对象)
X ToX()
(19) 属性名
开头大写,其后单词首字母大写。
object PascalCasing()
(20) 返回Boolean变量的方法
Is +形容词、Can +动词、Has +过去分词、动词第三人称单数、动词第三人称单数+ 名词。
好的例子:
bool IsEmpty()
bool CanGet()
bool HasChanged()
bool Contains(object x)
bool ContainsKey(string key)
不好的例子:
bool Empty() //可以理解为“对空进行操作”的动词性意思。
bool CheckXXX() // 难以判断true代表的含义。
理由:使if, while语句的条件易读,并且true代表的意思容易理解。
(21) Bool变量
形容词、Is +形容词、Can +动词、Has +过去分词、动词第三人称单数、动词第三人称单数+ 名词。
bool isEmpty;
bool dirty;
bool containsMoreElements;
(22) 英语和日语
基本上所有关键字的名字都是英语,另外,在项目的整个生命周期内,可以做成日英对照
词典。
(23) 名称的对称性
在命名类和方法时,请留意以下的英语的对称性。
Add / Remove
Insert / Delete
Get / Set
Start / Stop
Begin / End
Send / Receive
First / Last
Get / Release
Put / Get
Up / Down
Show / Hide
Source / Target
Open / Close
Source / Destination
Increment / Decrement
Lock / Unlock
Old / New
Next / Previous
(24) 循环计数器(Loop Counter)
当作用域(通用范围)很小时,循环计数器就可以顺序地使用 i, j, k等名字。
(25) 小作用域的变量命名
当变量名的作用域很小时,可以使用类型名称的缩写。
例:DataSet ds = new DataSet();
(26) 名称要能表达变量的意思。
通过变量名就能明白变量的作用。
不好的例子: Copy(string s1, string s2)
好的例子: Copy(string source, string destination)
(27) 没有实际意思的名字
对于Info, Data, Temp, Str, Buf等这些名字,需要重新考虑换名字。
不好的例子:double temp = Math.Sqrt(b*b - 4*a*c);
好的例子:double determinant = Math.Sqrt(b*b - 4*a*c);
(28) private / protected / internal / protected internal作用域内的实例化变量
开始小写,以后单词首字母大写。使用前缀/ 后缀的场合,需要考虑变量名的易读性。在用户定义的对象类型里,不要使用匈牙利标记法,使用Camel标记法。其他的使用匈牙利标记法。
private object objCasing;
protected object objCasing;
internal People peopleCasing; //People是用户定义的对象
protected internal People peopleCasing;
(29) Public作用域内的实例化变量
开头字母大写,其后单词首字母大写。尽量不要使用。
public object PascalCasing;
(30) private / protected / internal / protected internal作用域内的公共变量
开始小写,其后单词首字母大写。使用前缀/后缀的场合,需要考虑变量名的易读性。在用户自定义的对象类型中,不要使用匈牙利标记法,应该使用Camel标记法。其他的使用匈牙利标记法。
private static object camelCasing;
protected static object camelCasing;
internal static People peopleCasing; //People是用户定义的对象
protected internal static People peopleCasing;
(31) public作用域内的公共变量
开头字母大写,其后单词首字母大写。尽量不要使用。
public static object PascalCasing;
(32) 局部(Local)变量
开始小写,其后单词首字母大写。在用户自定义的对象类型中,不要使用匈牙利标记法,应该使用Camel标记法。其他的使用匈牙利标记法。
object objCasing;
(33) 大小写
命名时虽然区分大写字母和小写字母,但不能只是依靠这样来区别不同的名称。
4.GuideLine
(34) #Region / #End Region 预处理指令
代码声明在#Region / #End Region directive的领域里,其中包含关于该领域的说明。
例:
#Region "实例化变量"
private string name;
#End Region
#Region "Constructor"
public MyClass(string name)
{
https://www.doczj.com/doc/f77660500.html, = name;
}
#End Region
(35) 方法/属性的声明
在方法/属性的声明里,要明确地指定作用域。
(36)长行
一行的字符数最多是100位,如果超过100位就要进行分割。分割的依据可以是:(1)利用局部变量,(2)在算术运算符/联结运算符处换行,(3)在逗号处换行(4)在优先级低的算符前换行。例如:
double length = Math.Sqrt(Math.Pow(new Random().NextDouble(), 2.0) + Math.Pow(new
Random().NextDouble(), 2.0));
' 方針(1)
double xSquared = Math.Pow(new Random().NextDouble(), 2.0);
double ySquared = Math.Pow(new Random().NextDouble(), 2.0);
double length = Math.Sqrt(xSquared + ySquared);
' 方針(2)
double length = Math.Sqrt(Math.Pow(new Random().NextDouble(), 2.0),
Math.Pow(n ew Random().NextDouble(), 2.0));
' 方針(3)
LongMethodSignature(value[0], value[1], value[2],
value[3], value[4], value[5]);
' 方針(4)
return (this is obj) _
|| (obj is Class1 _
and this.Field == obj.Field);
(37)长行的类声明
类的声明较长的情况下在逗号,冒号处换行。
例:如果是
public class LongNameClassImplemenation : AbstractImplementation, IXmlSerializable, Icloneable
那么:public class LongNameClassImplemenation :
AbstractImplementation,
IXmlSerializable, ICloneable
(38)长行的方法声明
方法的声明很长时在逗号处进行换行。
例:
public void LongMethodSignature(int a, int b, int c
, int d, int e, int f)
(39)抽象类和接口
尽量不使用抽象类(abstract Class)而是多使用接口(interface)。abstract Class只有在既有已密封方法又有抽象方法的情况下使用。
理由:接口(interface)允许多继承,而类(Class)只能是单继承。如果只是继承了一个类,那么可惜不能再从其他类进行继承。
(40)公共变量
实例变量,尽量不用public修饰,而是设置合适的属性。
理由:面向对象的标准。类的内部状态是不能被随意访问的。但是,如果全部满足下列条件,用public 修饰实例变量,使之被直接访问也可以:
●如果那个实例变量与其他的实例变量是相互独立的,单独地改变它不会影响内部的整合性。
●一定书写get/set访问器
●依据将来传递时不被改变的原则进行接口变量的实装
即使不适合上述条件,但在非常关注效率的情况下,也可以没有这个限制。(但是,请慎重对待)(41)初始化
参照不要初始化为空值,而且不要进行两次初始化。
不好的例子:
public class PoorInitialization
{
private string name = "initial_name";
public PoorInitialization()
{
name = "initial_name";
}
}
理由:尽量减少与初始化有关的错误。
(42)避开static变量
尽量不用static变量(类变量)。
理由:static变量,可以说是半全局变量。这会导致代码的相互关系复杂化,会产生一定的副作用。
(43)private 与protected
比起private,protected更常用。
理由:定义为private的成员对于所在类以外的成员而言是不能访问的。所以用户也就不能在子类中对它进行访问,作进一步的处理。
但是:更喜欢使用private。若用protected后,改变时会影响到整个类的继承关系。
(44)get/set访问器
不要胡乱地作成实例的属性(get/set访问器)并赋予public公共访问级别。必要时,要做成某种属性/方法。
理由:实例变量多是相互依存的,类的内部整合性不能破坏。
(45)变量隐藏
不要使用和基类的变量名相同的变量名。
理由:这种情况一般会出现错误。如果是有目的地使用可以这样。
(46)public 方法
类的公共方法,以自动贩卖机的接口为例,进行易于理解,即使使用方法有误也不会破坏内部整合性的设计。或者可能的情况下根据约定进行设计,用代码来表现类的不变条件,方法的执行前和执行后条件。
(47)状态取得和状态变更的分离
要设计的方法只能完成一项功能。
特别是不能在一个方法里面既进行状态变更又进行状态取得两种功能。
状态变更的方法的返回值为void.
因为:
1、1个方法完成一样功能简单易懂。
2、方便进行并行性的控制并易于确保程序不产生意外错误。
3、在子类的继承中,便于扩充。
(48)this中的return
尽管这是为用户使用的方便考虑,还是要尽量避免使用return返回this的方法。
理由:A.Meth1().Meth2().Meth3() 这样的连接,一般会产生同步上的问题。
(49)方法的多重定义
根据参数的类型尽可能的避免方法的重载(参数的个数可以不同),特别,在继承的复杂关系中。
例:×:override void Draw(Rectangle rectangle)
override void Draw(Point point)
○:void DrawRectangle(Rectangle rectangle)
void DrawPoint(Point point)
(50)Equals()和GetHashCode()
如果撤销Object.Equals()方法,同时GetHashCode()方法也将被撤销。反之亦然。
理由:
为了与哈希表(Hashtable)等相对应。
(51)Clone()
如果要使用Clone()方法,实例化ICloneable要明确写出来。
例:
using System;
public class Foo : ICloneable
{
public object ICloneable.Clone()
{
Foo myFoo = (Foo) this.Clone();
//...
}
}
理由:简单的拷贝存在很多不好的情况。
(52)缺省的构造器
最好建立缺省的构造器(没有参数)。
理由:在使用reflection时,可以利用Assembly.CreateInstance(TypeName)动态地生成对象。(53)抽象类中的抽象方法
在abstract(抽象)类中,不写no-op方法,而是明确声明abstract方法。对此设置为protected 访问级别,为可能需要的实装做准备,这样可以在子类中实现方法功能。
理由:因为.NET的IDE能查出没有实装的abstract方法,可以避免因为忘记实装而产生的错误。(54)对象的同值比较
对象进行比较时使用Equals()方法,不能使用“=”。特别是进行字符串的比较时,也不能使用“=”。
理由:如果编程者想实现等值比较的话,那就应该使用Equals()。并且,使用String进行比较时在Option Compare Text设定的是比较字符串不区分大小写。
原因:在单元测试中,AssertEquals可以使用Equals(),可以简单地书写等值类。
(55)声明和初始化
局部变量,在声明时就进行初始化。
理由:把有关变量的值假定为最小化。
不好的例子:
void f(int start)
{
int i, j;// 没有赋予初始化值
// 较多的代码
// ...
i = start + 1;
j = i + 1;
// 使用i,j
// ...
}
好的例子:
void f(int start)
{
// 较多的代码
// ...
int i = start + 1;
int j = i + 1;
// 使用i,j
// ...
}
(56)局部变量不能重复利用
如果要重复使用局部变力,一定要重新进行声明并初始化。
理由:把有关变量的值假定为最小化。可以使编译程序达到最优化。
不好的例子:
void f(int n, int delta)
{
int i;// 没有进行初始化
for (i = 0; i < n; i++)
{
// 使用i
}
for (i = 0; i < n; i++) //再次使用i
{
if (...)
{
break;
}
}
if (i != n - 1) //使用i进行循环的最后判断 {
// ...
}
i = n - delta * 2; //再次使用
//...
}
好的例子:
void f(int n, int delta)
{
for (int i = 0; i < n; i++)
{
// 使用i
}
bool found= false;
for (int j = 0; i < n; i++)
{
// 使用j
if (...)
{
found = true;
break;
}
}
if (found)
{
// ...
}
int total = n - delta * 2; // 利用其他的变量
//...
}
(57)大小比较运算符
最好使用“<”、”<=”,尽量不用”>“、”>=“
理由:统一大小方向,右侧的为大的方向,避免混乱。
(58)if/while 条件中的"="
if,while的条件式中,不能使用“=“
理由:几乎所有这样的场合,都会出现错误。
(59)cast
Cast形式转换时,尽量使用类型判断语句。
例(C为已知类,x是C的一个实例):
C cx = null;
if (x is C)
{
cx = (C) x;
}
else
{
evasiveAction();//动作方法
}
理由:这时,常常考虑的事情是“对象是不是类的实例呢?”。但是,cast不成功的场合是存在错误的。如果能够判断出来的话,就没有这个限制了。
(60)异常类
异常类具有范围广大的特点,如果多用的话很容易造成程序流程难以阅读的问题。
使用异常类,相对于重新编写新的异常类来说, 应尽可能利用.NET类库中已有的异常类
例如IOException, FileNotFoundException, ArgumentException等都是易使用的标准异常。
编写新的异常,也要研究这些现有的异常类的接口。
(61)改变方法的参数不好
原则上讲,方法中的参数是用来输入的,不能作为输出使用。即在方法的内部改变参数的值是不提倡的。不向输出参数代入新的对象。
不好的例子:
void MoveX(Point p, int dx)
{
p.X = p.X + dx; // 改变了参数(尽量避免)
}
好的例子:
void MoveX(Point p, int dx)
{
Point p = New Point(p.X + dx, p.Y);
// 这个变量不会传到调用方
}
特殊情况:考虑性能时。
(62)方法参数的命名
方法的参数应该具有易读性,特别是和实例变量重名的时候,要灵活运用this关键字,而不能牺牲参数的可读性。
不好的例子:
void Reset(int x_, int y_)
{
x = x_;
y = y_;
}
好的例子:
// 不把参数命名为 x_, y_
void Reset(int x, int y)
{
this.x = x;
this.y = y;
}
void Reset(int x, int y)
{
_x = x;
_y = y;
}
(63)ToString()
ToString()方法,能够使用的话,尽量使用
因为(调试时):
1.Console.Writeline(Object)总能够显示
2.在进行单元测试等时,容易明白错误。
(64)避免嵌套使用switch,if/else
当在switch语句中又出现了分支结构时,将被认为是不好的设计,重新考虑能否可以使用多态来实现。特别是,当同样的switch语句在两处以上出现时,就必须使用多态性、工厂方法(FactoryMethod)或者Prototype模式等重新设计。If/else的嵌套使用也是一样。并且,当进行Null检查用的同样的if语句多次出现时,也需要考虑使用NullObject模式实现。
5、注释
(65)有效利用文档注释
较多地使用///
公共的类、方法、属性须要加上///注释。
(66)长的注释
当注释较长需要在写在多行上时,先在注释的首行用简短的语句表达要说明的意思,然后附加上长的注释。另外,当有必要使用这种长注释时,需要考虑更简单的设计,积极地进行修整。
(67)Design by Contract(基于契约的设计)
//在这里,契约可以理解为系统的一种说明,比如堆栈的状态,系统异常描述等。在C#中称为契约。为了进行基于契约的设计,需要使用Debug对象的Assert方法。使用Assert方法,可以将契约表示出来。