一、编码规范的设定
规则:编程语言和开发环境所要求必须遵守的。
约定:不是语言的一部分,而是一种编码风格,最重要的是要有统一的标准。
二、编码规范的作用
1、规范化(公司的角度)
企业文化的一种需要,统一的风格有助于提高公司产品质量,降低开发成本,而且提高了公司凝聚力,当所有人都接受并执行一个统一的规范时,也就相当于形成了一个统一的文化。
2、专业化(个人的角度)
提高工作效率的同时,统一的规范会提高代码质量,从而提高个人的专业素养。
3、高效 (工作的角度)
●开发:协同开发写程序的需要,在一个开发组里,执行统一规范会增加代码融合度,减少开发中的沟通成本。程序员可以了解任何代码,弄清程序的状况而无需去猜测某段代码在干什么。
●维护:编码规范使程序更易于维护,无论是本人还是同事都需要,统一的规范可以让每个人都能看懂别人的代码从而进行维护。
●减少bug:在一致的环境下,开发者可以减少出犯错的机会。
编码风格最重要的两点
⏹可读性
它是良好的编码风格最突出的表现。
⏹一致性
它是良好的编码风格最重要的准则。
三、编码规范详细设定
在我们公司的使用中,以Microsoft推荐的编码标准为基准,结合本公司的实际情况和通用习惯设定了如下的规范。
(一)命名规则
1.命名时的基准规则
●Pascal大写—所有单词第一个字母大写,其他字母小写。
RestoreDocumentForSharepoint
●Camel大写—除了第一个单词,后面所有单词第一个字母大写,其他字母小写。
restoreDocumentForSharepoint
2.总体规范
●标识符的长度应当符合“min-length && max-information”原则。
●名字要有意义,用描述性的词语来命名,能通过名字知道它的功能(用于循环迭代的变量例外)。
●避免缩写。如果这样造成命名过长的话可以改在注释中。但是常见的首字母缩写可以使用。例如:SP、IE等
●名字中不要加下划线,除非特殊情况。
●不要和关键字冲突。
●不要通过大小写的不同来区分名字。Count count
●在命名中禁止出现以个人名字命名的一切代码。
●禁止在代码的任何段落中出现脏话及不良用语。
●缩写词不要全部使用大写字母,无论如何,当遇到以下情况,你可以用首字母大写其余字母小写来代替全部使用大写字母的方法来表示缩写词。
使用: GetHtmlStatistic.
不使用: GetHTMLStatistic.
3.类的命名规范
●用名词或名词短语命名类。
●使用Pascal大写public class FileStream {}
●不加任何类前缀。
●文件名和类名要匹配。对于类HelloWorld,相应的类文件名也应为 HelloWorld.cs。
●当类只用作其他类的基类时,以Base结尾。Class CarBase
4.接口的命名规范
●使用名词或名词短语,或者描述行为的形容词来命名接口。
●使用Pascal大写。ICarComponent
●在接口名前加前缀I,以表示这个类型是一个接口。(注意例如,IdentityStore。)
●一对类/接口组合名称要相对应,两个名称的不同之处只是接口名前有一个I前缀。
public interface IComponent {}
public class Component : IComponent{}
5.方法的命名规范
通常每个方法和函数都是执行一个动作的,所以对它们的命名应该清楚的说明它们是做什么的:用CheckForErrors()代替ErrorCheck(),用DumpDataToFile()代替DataFile()。这么做也可以使功能和数据成为更可区分的物体。
●用动词或动词短语命名方法。
●使用Pascal大写。RemoveAll()。
6.变量的命名规范
●局部变量和方法参数:使用Camel 大小写形式。 int totalCount = 0;
●成员变量:沿用VC的规范,m开头,其后用Pascal大写。
private int mTotalCount = 0;
●静态变量:以s开头,其后用Pascal大写。static int sTotalCount;
7.常量的命名规范
常量的每一个字母都应该大写。
const int SYSTEMID = 100;
const string SYSTEMNAME = “TestM”;
8.属性的命名规范
●用名词或名词短语命名。
●用Pascal大写命名属性。成员变量与其属性的差别只在于第一个字母m。
成员变量mWebName
对应属性WebName
●属性与变量的类型要统一。
9.命名空间的命名规范
●用Pascal大写命名。
●使用有意义的名字定义命名空间,如产品名或者公司名 。把System系列命名空间组织到一起放上面,第三方提供的命名空间放到系统命名空间下面。
●为每一个命名空间创建一个目录,这样可以更容易地将命名空间映射到目录层次划分。用MyProject/TestSuite/TestTier作为MyProject.TestSuite.TestTier的路径。
●我们公司的命名空间以xxx开头,例如:xxx.Migrator
(二)格式化规范
这里大部分的格式规范都可以用VS2005的自动化格式功能实现。
●缩进用TAB,不用SPACES。
●对与最大缩进层数,并没有一个固定的规矩,假如缩进层数大于四或者五层的时候,你可以考虑着将其代码因数分解。
●在每个运算符前后都空一格。
●用一个空行来分开代码的逻辑分组。
●在一个类中,各个方法需用一空行,也只能是一空行分开。
●花括号{}需和括号外的代码对齐。每个花括号需一行。
●If、for、while、do等语句自占一行,不论执行语句多少都要加“{}”
●很长的程序行应该进行拆分。当一行被分为几行时,通过将串联运算符放在每一行的末尾而不是开头,清楚地表示没有后面的行是不完整的。每一行上放置的语句避免超过一条。
⏹在逗号后换行。
⏹在操作符后换行。
⏹在高层换行而不要在低层处换行。
⏹折行后对齐上一行语句同一层的表达式起始位置。
(三)结构设计规范
1、工程设计规范
●各个project的输出路径要一致,一般情况下是” ..\\..\\bin\Release\\”.
●在各个工程文件里把应用到的dll所属的project都应用进来,这样可以保证应用不会出错。
●所有的目录下都需要具有README文档,其中包括: 该目录的功能及其包含内容,一个对每一文件的在线说明(带有link),每一个说明通常还应该提取文件标头的一些属性名字。 包括设置、使用说明,指导人民如何连接相关资源:源文件索引、在线文档 、纸文档、设计文档、其他对读者有帮助的东西。
考虑一下,当每个原有的开发人员走了,在6个月之内来的一个新人,那个孤单的探险者通过阅读说明文件,源文件的标头说明等等,就应该有能力维护整个工程。
2、文件名命名规范
模块名称、输出文件名称、二进制文件名称应该由或组长制定,并且需要符合公司这方面的规定。(前缀带公司名缩写、不要空格等)
●产品名称.环境信息名称.主功能名称.[子功能名称.[子功能名称…]].exe,例如:xxx.yy.Backup.Browser.exe/dll/wsp,不允许使用下划线,空格等。单词首字母大写。
●避免出现 AaBbBb这样的命名方式。
3、类文件编写规范
●避免在同一个文件中放置多个类。
●避免使用大文件。如果一个文件里的代码超过1000行,必须考虑将代码分开到不同类中,并且要有效的利用好region。
●在最靠近一个局部变量被使用的地方声明该局部变量。
●在定义变量(或参数)时,要将修饰符 * 和 & 紧靠变量名。
●重要类结构的public, protected, private顺序在所有的程序中应保持一致。
●程序中不要出现名字相同的局部变量和成员变量。
●代码文件保存需要保存成utf-8格式。在保存新文件时选择File->Advanced Saved Options,再选择Unicode(UTF-8…).
4、方法编写规范
●避免写太长的方法。如果一个方法代码超过50行,应该考虑按不同操作将其分解为不同的方法,这样各个功能可以很容易被重用,而且各个方法内的代码改变也很容易理解。上限为200行。
●一个方法只完成一个任务。不要把多个任务组合到一个方法中,即使那些任务非常小。
●避免写超过5个参数的方法。如果要传递多个参数,要使用结构来完成。
●避免使用很多成员变量。应该声明局部变量,并传递给方法。不要在方法间共享成员变量。否则很难知道是哪个方法在什么时候修改了它的值。
●参数命名、顺序合理。
●如果使用类型和数目不确定的参数,需要特殊注意并在注释中写明白其用途。
●不要省略了函数返回值的类型。
●函数名字与返回值类型在语义上不要冲突。
●不要将正常值和错误标志混在一起返回,正常值应当用输出参数获得,而错误标志用return语句返回。
●重要的一行代码应该只做一件事,如只定义一个变量,只写一条语句。
●如果代码行中的运算符比较多,要用括号清楚地确定表达式的操作顺序。
●不要编写太复杂或者多用途的复合表达式,复杂的表达式不能表明设计者技术更好,只会带来维护上的困难。
5、常量
●使用含义直观的常量来表示那些将在程序中多次出现的数字或字符串。
●如果某一常量与其它常量密切相关,要定义中包含了这种关系。
6、变量
●变量的初始化或缺省值要准确,应该在任何情况下都不引起错误。
●变量使用时注意上溢或下溢。
●变量使用时注意精度。
●对一个数值变量采用的不是0,-1,1等常用的数值初始化时,给出选择该值的理由。
●别把成员变量声明为public或protected。一般声明为 private 而使用 public/protected 的Properties。
7、数据类型
●变量的数据类型应该符合其定义。
●减少并谨慎使用不同数据类型的之间的赋值,避免精度损失或者赋值后类型错误。
●不要进行不同数据类型的比较,除非特殊情况。
●尽量使用C# 特有类型,而不是System命名空间中定义的别名类型。
好: int age; string name; object contactInfo;
不好: Int16 age; String name; Object contactInfo;
8、逻辑判断
●要注意精度原因导致比较无效问题。
●要注意表达式中的优先级是否正确。
●逻辑判断结果要符合常规,不要颠倒。
●字符串比较时要注意大小写,如果不大小写敏感的时要使用, Name.Equals(excludeItem.Name, StringComparison.OrdinalIgnoreCase)
●不要用隐含错误的方式写if语句? 例如:
(1)将布尔变量直接与TRUE、FALSE或者1、0进行比较。
(2)将浮点变量用“==”或“!=”与任何数字比较。
(3)将指针变量用“==”或“!=”与NULL比较。
●总是将恒量放在等号/不等号的左边,例如:
if ( 6 == $errorNum ) ...
一个原因是假如你在等式中漏了一个等号,语法检查器会为你报错。第二个原因是你能立刻找到数值,而不是在你的表达式的末端找到它。需要一点时间来习惯这个格式,但是它确实很有用。
9、循环问题
●如果循环体内存在逻辑判断,并且循环次数很大,要将逻辑判断移到循环体的外面。
●要注意循环终止条件是否正确
●谨慎使用无法正常终止(死循环)的循环。
●在循环里不要错误地修改循环变量。
●要注意误差累积对结果的影响。
●Case语句的结尾要加break。
●写switch时要加default分支。default case总应该存在,它应该不被到达,然而如果到达了就会触发一个错误。
●Switch中如果你要创立一个变量,那就把所有的代码放在块中。
Case 0:
{
int i = 0;
}
●Continue 和 break 其实是变相的隐蔽的 goto方法,要谨慎使用。
10、Mutex的管理
1)在使用前充分认识Mutex,例如:
A.Mutex应该在 finally{}里释放 (否则异常情况会导致Mutex释放操作被跳过)
B.注意释放Mutex有时会引起异常。
C.请确认Mutex在线程或者进程结束前被释放。(在正常流程中,应该避免杀死线程和进程操作,这样操作可能会导致释放操作被忽略)
D.http://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx
2)使用Mutex前看是否必须,Mutex应该被用于多进程中同步,如果仅仅是同步一个进程中的线程,可以用lock{}代替。
3)如果必须使用Mutex,请确认是否恰当的控制好了相关code。
4)通常情况下,应该在短操作中使用同步对象(Mutex,lock)(在一个没有复杂操作的方法中 )
11、其他规范
●总是使用接口。避免只有一个成员的接口,努力保证一个接口有3~5个成员。当使用抽象类的时候,提供一个接口。
(四)内存管理
●SQL对象使用结束后一定要关闭:(例) Reader.Close();
●一些对象是否在list中保持应用,而实际上不再使用也要及时释放。
●申请大内存对象后,使用完毕要释放,如果频繁使用,可以考虑重用。
(五)异常处理
●加try-catch时注意,一定要在catch里加处理的语句,千万不要catch里啥也不做,那样的效果还不如不加try-catch了。如果你能确认在catch里不需要输出,那就加个注释,说明这里不需要输出。
try
{
sr.Close();
}
catch
{
// Does not require output
}
●处理异常的基本逻辑是:
对于严重问题(备份或者创建site失败、连接数据库失败等),应该在捕获异常后结束job,注意输出错误信息。
对于非严重问题(备份还原单项目失败、还原某个属性失败等),可以在捕获异常后输出错误信息,然后继续向下执行。
●捕获异常后,重新写自己的异常,这个时候一定要把原先的异常信息包括到新异常里。
●在异常中包含一些非常重要的信息。举一个例子,我们的代码经常要判断是否有足够的磁盘空间。当磁盘空间不足是,我们只输出磁盘空间不足,并没有写哪个地方的磁盘空间不足,现在多少空间,我们需要多少空间,等等。例子:
long (realSize < expectedSize)
{
throw new Exception("Not enough disk space");
}
比较好的代码是把这些信息都输出:
long (realSize < expectedSize)
{
throw new Exception("Not enough disk space. Location:{0}, realSize:{1}, expectedSize:{2}", location, realSize, expectedSize);
}
●只输出异常的message,没有把堆栈信息输出。这样只知道是什么错误,并不知道哪行代码抛出错误,具体是什么异常信息。
例子:
try
{
Do something...
}
catch (Exception e)
{
mLog.Error("Browse tree error:{0}", e.Message);
}
如果可能,我们应该把堆栈信息也一起输出,只要用ToString()方法代替Message属性就可以了:
try
{
Do something...
}
catch (Exception e)
{
mLog.Error("Browse tree error:{0}", e.ToString());
}
●关键的地方,不要忘记进行错误处理,例如线程处理方法等。
●要注意错误处理程序块是否能执行到。
●错误处理程序块本身不要错误,如报告的错误与实际错误不一致,处理方式不正确等等。Catch块里不要出现新的异常,否则很容易导致程序不正常结束。
(六)文件I/O问题:
●读写文件时要注意文件读写成功和失败的异常处理。除非你的操作不重要,而且出错能容过去,否则加上try-catch来处理IO异常是比较好的方式。
●不要对不存在的或者错误的文件进行操作,操作前应该检查文件是否可用。
●打开文件要使用正确的方式。
●要注意对于文件结束的判断。
●使用完毕后要正确地关闭文件。
●在使用File.Create(outReportFilePath);时注意,如果后边直接StreamReader outReport = new StreamReader(outReportFilePath);读这个文件,有很大可能会报错误文件被占用,也就是说这种方法创建的文件,释放不及时,使用的时候请注意。最好用File对象创建并主动释放资源。
●除非必须,否则永远不要向固定盘符的目录写文件,如果需要写临时文件,可以使用安装目录中的temp文件夹。
(七)高级特性
●重载函数不要有二义性。
●不要混淆了成员函数的重载、覆盖与隐藏。
●运算符的重载要符合制定的编程规范。
(八)注释和日志
●注释需和代码对齐。只在必需的地方注释。行数不多的注释会使代码看起来优雅。(前提是你的代码命名有意义并且逻辑结构清晰)
●如果因为某种原因使用了复杂晦涩的原理,应该为程序配备良好的文档和充分的注释。(养成写开发文档的习惯)
●写注释时注意不要有错误或者可能导致误解的地方,尽量写清晰易懂的注释。
●一定要让你的应用程序支持跟踪和日志。
●修改代码后在更改处加上注释
●在提交代码时,SVN的注释也要填写这次提交所涉及的功能信息。
●Log里提示的错误信息应该包含更多有用的信息,例如连接服务器失败这样的错误,应该在log里把连接的目标IP和Port都打出来。
●Log level的设定:
Info 程序执行的主要步骤,例如程序开始、结束等,由于这个level的log是默认情况下显示的,应该尽量少一些,不然做个job,日志就由于info信息太多冲掉了更有用的信息。
Debug 程序执行的详细步骤,包括一些诊断信息等,尽量详细,主要用来诊断问题。
Warning 当发生异常时,如果这个异常不影响程序 继续执行,可以把这个异常信息输出为Warn,例如:还原一个文件失败的错误;如果是可以预知并能容过去的错误,也可以不用Warning而选择Debug,例如:输出普通信息的文件不存在但是可以创建的log就可以是Debug的。
Error 当发生异常时,程序由于这个异常导致不能继续工作,则应该用这个标志。
四、编码中要注意的问题
1、同产品软件(项目组)内,最好使用相同的编辑器,并使用相同的设置选项。并设计、使用一套缩进宏及注释宏等,将缩进等问题交由编辑器处理。
2、谨慎使用块拷贝功能,当某段代码与另一段代码的处理功能相似时,许多开发人员都用编辑器提供的块拷贝功能来完成这段代码的编写。由于程序功能相近,故所使用的变量、采用的表达式等在功能及命名上可能都很相近,所以使用块拷贝时要注意,除了修改相应的程序外,一定要把使用的每个变量仔细查看一遍,以改成正确的。不应指望编译器能查出所有这种错误,比如当使用的是全局变量时,就有可能使某种错误隐藏下来。
3、在修改branch(主要是客户问题等情况)时,不要修改公共接口,否则可能会导致其他功能不好使;如果必须修改,可以使用方法重载等方式来做。
4、避免 string name=””; 正确 string name=String.Empty;(空间问题)
5、必要时使用枚举类型enum,别用数字或字符串来指示离散值。enum MailType {Html, PlainText,Attachment} 使用各个离散指时采用MailType.Html的形式,而不是逐个的“Html”。避免为枚举指定一个类型。
6、进程或者线程应该通过判断退出条件来进行安全结束,只有我们确认它肯定不能工作了(不能自己结束)才能强制的杀死线程或者进程. 否则,这种操作会造成某些资源不能回收以及出现脏数据。
Process or thread should check exit condition and quit peacefully. Do not forcefully kill a thread or process, unless we know it’s stuck. Otherwise, it may cause cleanup be skipped and may also cause data corruption
7、除了要在switch语句块中实现代码跳转,不要使用goto关键字。
五、能提高程序效率的方法
1、StringBuilder
代替String定义字符串,尤其是在多次修改string的时候,StringBuilder的效率会有惊人的提高。(可以通过通过设置时间点来分析程序的效率)
2、合理利用排序,提高程序效率。很多查找操作排序后会比排序前快许多倍。
3、在已知类型的情况下,尽量避免强制类型转换封箱拆箱操作,减少对资源的损耗。
4、对数据库操作时需要什么字段操作什么字段,不要操作多余的字段。
5、没有用的被注释掉的代码要及时删除,以免让后来人产生歧义。
六、代码重用。
跨工程的重用在没有一个通用结构的情况下几乎是不可能的。不同的工程有着不同的服务需求环境,这使对象重用变得很困难。 以下几种办法推荐使用来帮助开发者进行代码重用 :
1、请教!给team发Email求助
这个简单的方法很少被使用。因为有些程序员们觉得如果他向其他人求助,会显得自己水平低,这多傻啊! 做新的有趣的工作,不要一遍又一遍的做别人已经做过的东西。如果你需要某些事项的源代码,如果已经有某人做过的话,就向群组发email求助。结果会很惊喜哦!
在许多大的开发团队中,个人往往不知道其他人在干什么。你甚至可以发现某人在找一些东西做,并且自愿为你写代码,如果人们在一起工作,外面就总有一个金矿。
2、告诉!当你在做事的时候,把它告诉所有人
如果你做了什么可重用的东西的话,让其他人知道。别害羞,也不要为了保护自豪感而把你的工作成果藏起来。一旦养成共享工作成果的习惯,每个人都会获得更多。
对于代码重用,一个常见的问题就是人们不从他们做过的代码中做库。一个可重用的类可能正隐蔽在一个程序目录并且决不会有被分享的激动,因为程序员不会把类分拆出来加入库中。
这样的其中一个原因就是人们不喜欢做一个小库,对小库有一些不正确感觉。把这样的感觉克服掉吧,电脑才不关心你有多少个库呢。
如果你有一些代码可以重用,而且不能放入一个已经存在的库中,那么就做一个新的库吧。
七、编码中要严格禁止出现的问题
1、显示程序中给客户看的文字必须经过审核,禁止自己随便写一些文字就交给客户看。
2、工程添加引用时,不要使用绝对路径,要确保你的引用路径为正确的相对路径,否则问题很隐蔽和严重!
3、各个程序里的if debug不要涉及具体程序执行,防止debug时功能好使,非debug就不好使的情况出现。
4、循环中不要使用string+=的方式去组合字符串,那样效率太低,可以使用StringBuilder来做。
5、不在代码中使用具体的路径和驱动器名。要使用相对路径,并使路径可编程。且永远别设想你的代码是在“C:”盘运行。
6、不要把客户的密码明文保存在任何位置,对于必须保存的密码信息需要加密后保存在配置文件中。
如果涉及到输出到log中,请对log中的密码密文进行屏蔽。
这个规则将严格执行。
7、别在程序中使用固定数值(0、1有例外)或通用固定字符及字符串,请用常量代替。
public const int TIMEOUT = 3000;
Sleep(TIMEOUT);
public const char TYPE_WEB = 'W';
if(item.Type == RestoreItem.TYPE_WEB){...}
八、新增的一些注意事项
1、使用一些windows API时注意连续2次调用的结果可能不一致导致的问题。
例如:Marshal.GetLastWin32Error()这个是用来获取Error Code的api,下面这种使用方式就是错误的:
logger.Error(Marshal.GetLastWin32Error());
throw new Exception(“xxx”+Marshal.GetLastWin32Error());//结果不一致
正确的做法是:
String errorCode = Marshal.GetLastWin32Error();
logger.Error(errorCode);
throw new Exception(“xxx”+errorCode);
附注:标为橘红色的段落表示需要特别注意的。下载本文