代码质量之基本编程规范

一个团队如何编写高质量的代码,编程规范是最基础的条件,这一条看似简单,实际工作中能够做到整个团队编程规范100%遵守的少之又少。究其原因,理由有很多:

  1. 团队成员背景各不相同:有多年带着自己风格的老兵,有刚踏出校门的新毕业生;
  2. 现有软件产品代码没有规范:近朱者赤,近墨者黑。即使有着良好编程规范习惯的程序员,在一堆乱如稻草的代码,也很难做到出淤泥而不染,要想写段良好的代码得需要大量的精力去重构已有的代码。
  3. 团队本身就没有定义编程规范,没有规范就无从谈起遵守规范;
  4. 团队定义了全面的编程规范但被束之高阁,实际日常工作中没有人按照定义的规范去写代码和检视代码。
既然如此,团队的编程规范就应该简单明了,大部分的编程风格问题由工具来统一调整。

1. 编程风格

编程风格包括代码缩进、换行、空格、括号位置、函数参数排版等等,这一类的问题需要团队一起讨论,确定大家认可的编程风格。在这里我推荐使用 AStyle,一款Open Source的简单编码格式化工具,可定义你需要的代码风格,命令行执行格式化项目里的代码文件。

下面是我在C++项目中使用的编码风格定义:
#Artistic Style options file for Time Series C++/C# code
#Bracket Style Options
#Bracket Style options define the bracket style to use.
#Allman style formatting/indenting uses broken brackets.
--style=allman

#Tab Options
#If no indentation option is set, the default option of 4 spaces will be used (e.g. -s4 --indent=spaces=4).
#Indent using # spaces per indent (e.g. -s6 --indent=spaces=6). # must be between 2 and 20. Not specifying # will result in a default of 4 spaces per indent.
--indent=spaces=4

#Indentation Options
#Indent 'switch' blocks so that the 'case X:' statements are indented in the switch block. The entire case block is indented.
--indent-switches
#Indent C++ comments beginning in column one. By default C++ comments beginning in column one are not indented. This option will allow the comments to be indented with the code. 
--indent-col1-comments
#Set the minimal indent that is added when a header is built of multiple lines. This indent helps to easily separate the header from the command statements that follow. The value for # indicates a number of indents and is a minimum value. The indent may be greater to align with the data on the previous line.
--min-conditional-indent=0
#Set the  maximum of # spaces to indent a continuation line. The  # indicates a number of columns and must not be greater than 120. If no # is set, the default value of 40 will be used. A maximum of less than two indent lengths will be ignored. This option will prevent continuation lines from extending too far to the right. Setting a larger value will allow the code to be extended further to the right.
--max-instatement-indent=80

#Padding Options
#Insert space padding around operators. Any end of line comments will remain in the original column, if possible. Note that there is no option to unpad. Once padded, they stay padded.
--pad-oper
#Don't insert space padding around parenthesis on the outside or the inside.
#Insert space padding after paren headers only (e.g. 'if', 'for', 'while'...). Any end of line comments will remain in the original column, if possible. This can be used with unpad-paren to remove unwanted spaces.
--pad-header
#Remove extra space padding around parenthesis on the inside and outside. Any end of line comments will remain in the original column, if possible. This option can be used in combination with the paren padding options pad?paren, pad?paren?out, pad?paren?in, and pad?header above. Only padding that has not been requested by other options will be removed.
--unpad-paren

#Formatting Options
#Add brackets to unbracketed one line conditional statements (e.g. 'if', 'for', 'while'...). The statement must be on a single line. The brackets will be added according to the currently requested predefined style or bracket type. If no style or bracket type is requested the brackets will be attached. If --add-one-line-brackets is also used the result will be one line brackets.
--add-brackets
#Attach a pointer or reference operator (* or &) to either the variable type (left) or variable name (right), or place it between the type and name (middle). The spacing between the type and name will be preserved, if possible. To format references separately use the following align-reference option.
--align-pointer=name
#This option will align references separate from pointers. Pointers are not changed by this option. If pointers and references are to be aligned the same, use the previous align-pointer option. The option align-reference=none will not change the reference alignment. The other options are the same as for align-pointer. In the case of a reference to a pointer (*&) with conflicting alignments, the align-pointer value will be used.
--align-reference=name
#Converts tabs into spaces in the non-indentation part of the line. The number of spaces inserted will maintain the spacing of the tab. The current setting for spaces per tab is used. It may not produce the expected results if convert-tabs is used when changing spaces per tab. Tabs are not replaced in quotes.
--convert-tabs

#Other Options
#Do not retain a backup of the original file. The original file is purged after it is formatted.
--suffix=none
#For each directory in the command line, process all subdirectories recursively. When using the recursive option the file name statement should contain a wildcard. Linux users should place the filepath and name in double quotes so the shell will not resolve the wildcards (e.g. "$HOME/src/*.cpp"). Windows users should place the filepath and name in double quotes if the path or name contains spaces.
--recursive
#Force use of the specified line end style. Valid options are windows (CRLF), linux (LF), and macold (CR). MacOld style is the format for OS 9 and earlier. Mac OS X uses the Linux style. If one of these options is not used the line ends will be determined automatically from the input file.
--lineend=windows

把上述的配置文件保存为astyle_options.txt,同时http://astyle.sourceforge.net/下载astyle.exe保存在同一目录下,如C:\Code\AStyle\。
下一步你就可以用下面的命令对你的C++文件统一编程风格格式化:

C:\Code\AStyle> astyle.exe --options=astyle_options.txt C:\Code\svn\source\*.cpp C:\Code\svn\source\*.h
这里假设你的C++文件存放在目录:C:\Code\svn\source\。对于上述的配置文件有疑问,或者想对配置进行进一步的修改,可以参考http://astyle.sourceforge.net/astyle.html

2. 编码命名规范

虽然有上述的编程风格格式化工具,到现在我还没有发现自动化重命名工具可以帮助程序员修改命名的。命名好坏,对于计算机来说没有任何影响,严重影响的是代码的可读性。如果有人在我系统中写下int superMan = ironMan + hulk; 这会让我抓狂,这个要不是无知无畏,要不是成心捣乱。无知无畏尚可教,成心捣乱就只能让他滚蛋。对于程序员,取个好名字是一件痛苦的事:一是英文不好,很多动词名词不知道对应的英文是什麽,即使在外企这种情况也很常见;二是程序员在语言表达上的欠缺,普遍认为程序就是逻辑,应该是算法+数据结构。
这里对于命名规范,只是列举最常用的部分:

2.1 变量命名

变量名一般以 匈牙利命名法为基础,分为属性部分,类型部分和描述部分,描述部分以大写字母开始,以大写字母作为间隔符,其余都是小写字母。不要纠结于 匈牙利命名法的弊端,每种命名法都有弊端,只要团队都认可,可以100%执行就是很好的方法。

属性部分

g_   全局变量

c_  常量

m_  成员变量

s_  静态变量

比如:

  1. unsigned int g_Count;  
  2.   
  3. class NetworkBufferManager
  4. {
  5.     ...
  6. private:
  7.     uint8 m_nBufferSize;
  8.     char8* m_pActualBuffer;
  9. };
  10.   
  11. int GetValue()
  12. {
  13.     int nStaffCount = 0;
  14.     return nStaffCount;
  15. }


2.2 类命名

类名以大写字母开头,大写字母分割,不要使用下划线‘_’。类名以名词或名词短语构成,比如:

  1. class NetworkBufferManager  
使用下划线分割也是一种方法,是另外一种风格。


2.3 函数命名

函数名以大写字母开头,大写字母分割,不要使用下划线‘_’。函数名以动宾短语构成,比如:

  1. Class NetworkBufferManager  
  2. {  
  3.     ...  
  4.     uint16 GetBufferSize() const;  
  5. };  


2.4 类型命名

类型名以大写字母开头,大写字母分割,不要使用下划线‘_’。对于长类型,可以使用typedef重新命名,比如:

  1. typedef std::map<std::string, std::list<uint16, std::string>> NameNodeMapType
  2.   
  3. typedef std::vector<NetworkNode>                              NodeVectorType
  4. typedef unsigned char     uint8
  5. typedef unsigned int      uint32


2.5 常量命名

常量名(宏定义)由大写字母组成,以下划线作为分隔符,比如:

  1. const int       MAX_CHAR_LENGTH = 255;
  2. #define MAX(a, b)    (a > b ? a : b)


2.6 枚举命名

枚举命由大写字母组成,以下划线作为分隔符,比如:

  1. enum DAY_TYPE  
  2. {  
  3.      SUN = 0,
  4.      MON, TUE, WED, THU, FRI, SAT
  5. };  


2.7 文件命名

文件名一般由文件中定义或实现的类名构成,如果文件中有多个类,选择其中最为重要的类作为文件名。

3. 代码注释

很多入门教材和文章中对于注释部分都是一笔带过,原因不言自明,写注释很简单,编译器不解析,代码行不统计,甚至老板都不算为工作量。但凡是流行的开源软件,或是语言自带的库代码,都有一个共同的特点,就是拥有可读性很好的注释。注释是大型系统(代码量超过50万行)必不可少的一部分,因为大型系统必然需要多人合作完成,而注释就是最好的参考文档。
这里让我想起了程序员最痛恨的几件事:
1. 程序没有文档;
2. 程序没有注释;
3. 自己要写注释;
4. 自己要写文档;

写注释很简单,但写出别人能看懂的注释就不简单了,关于遣词造句这里就先不谈了,你可以问候一下之前的语文或英语老师。给点注释的模板,方便大家在编程中快速的套用,和编程风格一样,团队使用同一套注释模板也很重要。

3.1 文件注释

在每个文件头中加入下面的注释块,并且在后续的维护中及时更新,添加修改历史:
  1. //*************************************************************************  
  2. // Copyright (C) 2012 Your Company
  3. // @file    $FILE_BASE$.$FILE_EXT$  
  4. // @brief   $end$  
  5. //  
  6. // @author  <your name>  
  7. // @version 1.0  
  8. //   
  9. // History:  
  10. // Date ------ Author ------ Description-----------------------------------  
  11. // $DATE$  <your name>  
  12. //   
  13. //*************************************************************************  

3.2 类注释

在每个类定义前加入下面的注释块,并且在后续的维护中及时更新:
  1. //*************************************************************************  
  2. // Class: $end$  
  3. // @brief Brief overview the class – What it does, how it is used  
  4. //  
  5. //*************************************************************************  

3.3 函数注释

在每个函数定义前加入下面的注释块,并且在后续的维护中及时更新:
  1. //*************************************************************************  
  2. // Function:   $end$  
  3. // @brief        
  4. //   
  5. // @param[in]  List of the input parameters along with a brief description of their use  
  6. // @param[out] List of the output parameters along with a brief description of their use  
  7. // @return     The return type of the function and description    
  8. //*************************************************************************  

3.4 代码注释

在代码块的上方或单条语句的右方添加注释,描述接口调用、算法、条件、遗留问题等等。

当然添加注释也需要合适的工具,比如Visual Assist的 Snippets,你可以定义快捷键快速的加入注释块,并添加时间日期等信息,以上注释块都可以定义为Snippets注释模板。

参考文档


猜你喜欢

转载自blog.csdn.net/u011798638/article/details/47904455