本文挡详细描述了在Logiscope——Rulechecker中包含的所有编码规范。对每一条编码规范,分别给出了它的名称、规范的内容描述、参数(只针对可设置的规范)、遵守规范的好处以及示例。
Rulechecker共包含81条编码规范,其中有30条左右可以对其内容进行定制,对于可定制编码规范的具体定制方法,可参见《RuleChecker可定制规则》。
2 Rulechecker规则集
Rulechecker规则集分为两大部分:基本规则集和Scott Meyers规则集。我们先来逐条介绍基本规则集中的规则。
规则描述:
函数声明和定义的格式,要符合ANSI规定的格式要求。它要求满足以下两项中的一项:
• name:为函数参数列表中的参数指定数据类型和参数名称。
• void: 禁止函数参数列表为空。
在RuleChecker的默认情况下,以上两项同时生效。
参数:
可供选择的字符串,“name”和“void”。
理由:
提高代码的可读性,改善可移植性。
举例:
// 不要象下面这样写代码:
f(a, b)
int a;
char *b
{ ...}
f(int, char*);
f();
// 应该这样写:
f(int a, char *b)
{ ...}
f(int a, char *b);
f(void);
规则描述:
函数调用语句中,在函数的参数列表中不要使用赋值操作符。赋值操作符包括=, +=, -=, *=, /=, %=, >>=, <<=, &=, |=, ^=,++,--。
理由:
避免产生不明确的赋值顺序。
举例:
// 不要象下面这样写代码:
void fun1(int a);
void fun2(int b)
{
fun1( ++b );
}
规则描述:
不要在控制语句if, while, for 和 switch的条件表达式中使用赋值操作符。赋值操作符包括:=, +=, -=, *=, /=, %=, >>=, <<=, &=, |=, ^=,++,--。
理由:
一个类似于 if (x=y)这样的写法是不明确、不清晰的,代码的作者也许是想写成这样: if (x==y)。
举例:
//不要象下面这样写代码:
if (x -= dx) { ...
for (i=j=n; --i > 0; j--) {..
//应该这样写:
x -= dx;
if (x) { ...
for (i=j=n; i > 0; i--, j--)
{ ...
规则描述:
在一个赋值表达式中:
• 一个左值,在表达式中应该仅被赋值一次。
•对于多重赋值表达式,一个左值在表达式中仅应出现一次,不能重复出现。
理由:
避免产生不明确的赋值顺序。
举例:
//不要象下面这样写代码:
i = t[i++];
a=b=c+a;
i=t[i]=15;
规则描述:
变量的定义要出现在使用该变量的语句块的开头。
理由:
提高代码的可读性。
疑义:
这在C++中好象并不成立。相反,延缓变量的定义,可以改善程序的效率,增加程序的清晰程度。所以我对这条规则表示怀疑。
规则描述:
对于if, while, for等控制语句的布尔表达式,要使用正确的格式。
理由:
使代码更容易理解。
举例:
//不要象下面这样写代码:
while (1) {
if (test) {
for (i=1; function_call(i); i++) {
//应该这样写:
AlwaysTrue = true;
while (AlwaysTrue == true) {
if (test == true) {
for (i=1; function_call(i) == true; i++) {
规则描述:
在控制语句 (for, do, while) 块中,禁止使用Break和continue。不过,在switch语句块中,可以使用break。
理由:
和goto语句一样, 使用Break和continue会打乱代码结构化的流程。在循环语句块中禁用goto、Break和continue,会增加代码的可读性。
规则描述:
在代码中不要使用如下形式的表达式 :u.v.a, u.v.f(),u.g().a, u.g().f(),也不要有使用“->”操作符的类似形式的表达式。
理由:
防止类对象通过多级的“.”、“->”操作符,调用未知的成员函数、数据成员。类与类之间的接口应该清晰。
举例:
//不要象下面这样写代码:
myWindow.itsButton.push();
其中,对象myWindow的基类为类Window,itsButto是Window的一个公共数据成员,它也是一个类对象,itsButto有一个叫做push()的公共成员函数。我们应该清楚的是,类Window才是myWindow访问其的接口,myWindow 只应访问到itsButton,而不应该访问到itsButton.push()这一级,因为push()已经不属于基类Window对myWindow的接口。
再举一个例子:
Error.pos.line;
这和上面例子的道理一样,只是这一次由访问成员函数变为了访问数据成员line,这同样是不好的。
规则描述:
在一个源文件中定义的每一个函数,都应该属于同一个类,即对一个类的描述要独占一个文件。其中,源文件指以*.cc, *.cxx, *.cpp, *.C or *.c为后缀的代码文件。
参数:
可供选择的字符串,包括*.cc, *.cxx, *.cpp, *.C or *.c,用来设置检查什么类型的代码文件。
理由:
提高代码的可读性。
规则描述:
在一个源文件中不应该包含任何类的声明,而只应该是对类的实现,类声明应该统一放到头文件中去。其中,源文件指以*.cc, *.cxx, *.cpp, *.C or *.c为后缀的代码文件。
参数:
可供选择的字符串,包括*.cc, *.cxx, *.cpp, *.C or *.c,用来设置检查什么类型的代码文件。
理由:
提高代码的可读性。
规则描述:
程序中不要使用三元运算符“?… : …”。
理由:
提高代码的可读性。
规则描述:
程序中的数字和字符串,都要显示的声明、定义为常量。在RuleChecker默认的情况下,只允许程序中直接出现下面这四个数字和字符串量,它们分别是:""(空字符串), " "(只包含一个空格的字符串), "0"(数字0)和 "1"(数字1)。其它任何的数字和字符串,都要定义为常量。
参数:
可供选择的字符串,用来指出那些允许在程序中直接出现,而不必以常量定义的数字和字符串。
理由:
这样做可以避免数字和字符串零散分布在代码中,使代码修改起来相当费力。遵守这项规则可以提高代码的可维护性。
注意:
在对某些量的初始化列表中 (比如数组、结构体),RuleChecker仅会对其前5个数据成员依照该项规则进行检查。
举例:
//不要象下面这样写代码:
char tab[100];
int i;
...
if (i == 7) {
p = "Hello World.\n";
}
//不应出现 100、7、"Hello World.\n",应该这样写:
#define TAB_SIZE 100
enum i_val { ok =7; ko =11};
const char HelloWorld[] = "Hello World.\n";
char tab[TAB_SIZE];
i_val i;
...
if (i == ok) {
p = HelloWorld;
}
规则描述:
每一个类都应该显示的定义拷贝构造函数。
参数:
参数只有一个,要么为空,要么为字符串"dynalloc"。如果设置参数为"dynalloc",那么RuleChecker仅在当类中包含指针类型的数据成员时,才要求类要显示定义拷贝构造函数;如果设置参数为空,则不管类中包含什么样的成员,都要显示定义拷贝构造函数。
理由:
确保类的编写者考虑类对象在被拷贝时可能出现的各种情况。
举例:
class aClass {
...
aClass(const aClass &object); // "const" 并不是必须的
...
};
规则描述:
每一个类都应该显示的定义默认构造函数。
理由:
确保类的编写者考虑类对象初始化时可能出现的各种情况。
举例:
class aClass {
...
aClass();
...
};
规则描述:
控制语句(if , for , while , do...whule).的语句部分一定要用 ‘{ ’和‘ }’括起来,以划分出清晰的语句块。
理由:
这样做,能够使语句的归属明确,使代码更加容易阅读和修改。
举例:
//不要象下面这样写代码:
if (x == 0)
return;
else
while (x > min)
x--;
// 应该这样写
if (x == 0)
{
return;
}
else
{
while (x > min)
{
x--;
}
}
规则描述:
每一个类都应该显示的定义析构函数。
理由:
确保类的编写者考虑类对象在析构时,可能出现的各种情况。
举例:
class aClass {
...
~aClass(aClass &object);
...
};
规则描述:
类对外的接口应该是完全功能化的,也就是类中可以定义Public的成员函数,但不应该有Public的数据成员。
在RuleChecker默认情况下,会检查类中是否声明了Public数据成员,如果有,则视为违反了该条规则。
参数:
参数是public、protected、privatr这三个字符串。通过指定相应的字符串,RuleChecker就会禁止类中声明与之相对应的数据成员。
理由:
要想改变对象的当前状态,应该通过它的成员函数来实现,而不应该通过直接设置它的数据成员。一个类的数据成员应该声明为private的,最起码也应该是protected的。
规则描述:
对于表达式的复杂性,要有一定的限制。表达式的复杂性通过一个叫做关联语法树(associated syntactictree)的指标来衡量,它的计算方法为:表达式中操作符的数量加上操作数的数量再加1。
在RuleChecker默认情况下,表达式复杂性的上限被设定为13。
参数:
是一个数字,用来指定表达式复杂性的最大可接受程度。
理由:
提高代码的可读性。
举例:
对于下面这个表达式:
(b+c*d) + (b*f(c)*d)
它包含了8个操作符,7个操作数,因此该表达式关联语法树为16。如果你设定的参数的上限小于等于16,则RuleChecker会认为此表达式违反了该条规则。
规则描述:
对于一个表达式,在每一个二元、三元操作的开始和结束处,都要放置“(”和“)”。
在RuleChecker中,我们可以通过放置partpar参数来减少一些限制。放置partpar参数后:当在操作符"+" 或"*"右边的操作数的右边又使用了"+" 或"*"操作符时,可以不放置“(”和“)”;对赋值操作符右边的操作数,可以忽略“(”和“)”;在表达式的最外层也可以忽略“(”和“)”。除此之外的其它情况,都要放置“(”和“)”。
在RuleChecker默认情况下,参数partpar被设置。
参数:
参数为字符串"partpar",如果放置了该参数,则RuleChecker不按照最严格的要求检测代码,而是按我们上面提到的要求来检测代码。否则,按照最严格的要求检测代码。
理由:
避免出现不明确的运算、赋值顺序。
举例:
// 不要象下面这样写代码:
result = fact / 100 + rem;
//而应该写成这样
result = ((fact / 100) + rem);
//当放置了partpar参数时,也可以写成这样
result = (fact / 100) + rem;
//当放置了partpar参数时,可以用下面的写法
result = (fact * ind * 100) + rem + 10 + (coeff ** c);
// 代替如下的写法
result = ((fact * (ind * 100)) + (rem + (10 + (coeff ** c))));
规则描述:
要为每一个函数指定它的返回值类型。如果函数没有返回值,则要定义返回类型为void。
理由:
改善代码的可移植性。
规则描述:
如果将一个(或若干个)类声明为另一个类的友员,则必须将这个(这些)友员声明放在类所有数据成员声明的最前面。
规则描述:
禁止在程序中声明、定义、调用我们指定函数名的函数。在RuleChecker默认情况下,不禁用任何的函数名。
参数:
参数是一系列的字符串列表,其中的每个字符串就是在程序中要禁用的函数的名字。
理由:
通过禁用一些只与特定平台相关联的函数,可以提高程序的可移植性。除此之外,你还可以通过设定该规则来实现一些其它的目的,比如禁用某些极易导致程序发生错误的函数,等等。
举例:
//如果我们设置了在程序中禁用函数fun(int nCount),则在代码中出现下面的任何一种情//况, RuleChecker都会认定程序违反了该条规则。
Void fun (int nCount);//声明
Void fun (int nCount)//定义
{
...
}
fun ( 1 );//调用
规则描述:
程序中不要使用goto语句。在RuleChecker默认情况下,出现在程序任何地方的goto语句都是被禁止的。不过,我们可以通过设置,使得goto跳转到我们指定的语句行号的语句为合法。
参数:
参数是一个字符串的列表,每个字符串都代表一个语句行号,表示允许在程序中通过goto跳转到该语句行号。
理由:
这条规则的目的是为了确保程序的结构化,因为滥用goto语句会使程序流程无规则,可读性差。
Goto语句只在一种情况下有使用价值,就是当要从多重循环深处跳转到循环之外时,效率很高,但对于一般要求的软件,没有必要费劲心思追求多么高的效率,而且效率主要是取决于算法,而不在于个别的语句技巧。
规则描述:
在头文件、实现文件的首部一定要有文件注释。
在RuleChecker中,我们还可以规定这个文件注释的具体格式。
在RuleChecker默认情况下,头文件和实现文件的文件注释中必须包括:文件名、开发者、开发日期、功能简介这四部分,都是以英文表示的(见下面的示例),我们也可以设置成中文。
参数:
参数由两个字符串列表来表示:第一部分是对头文件的文件注释格式要求,第二部分是对实现文件的文件注释格式要求。每个列表都是以"HEADER" 或"CODE"开头,后面是对注释格式的具体描述。
理由:
提高代码的可读性。
举例:
下面是一个符合RuleChecker默认的对头文件文件注释格式要求的例子:
///////////////////////////////////////////
// Name: program
// Author: Andrieu
// Date: 08/07/96
// Remarks: example of comments
///////////////////////////////////////////
规则描述:
在全局、静态函数的声明、定义和类的声明、定义之前,要对该函数或类加以注释。
在RuleChecker中,我们还可以规定这个文件注释的格式,
RuleChecker默认情况下,在函数或类之前必须有以“//”开头的注释,注释内容不限。
参数:
参数是五个字符串列表,这五个字符串列表分别以“class”、 "func_glob_def" 、"func_glob_decl"、"func_stat_def"、"func_stat_decl"开头,分别代表声明类、定义全局函数、声明全局函数、定义静态函数、声明静态函数,后面跟着的就是对注释格式的具体要求。
理由:
提高代码的可读性。
规则描述:
在一个头文件中,只应该包含对一个类的声明(嵌套类的情况除外)。RuleChecker默认情况下,头文件是指以.h、.hh、.H、.hxx、.hpp为后缀的文件。
参数:
参数是可供选择的字符串,比如.h、.hh、.H等,用来设置检查什么类型的代码文件。
理由:
提高代码的可读性。
规则描述:
在头文件中不要定义全局变量和全局函数。
在RuleChecker默认情况下,头文件指以.h、 .hh、.H、 .hxx、.hpp为后缀的代码文件。
参数:
参数为可供选择的字符串,包括*.h, *.hh,*.H, *.hxx、*.hpp,用来设置检查什么类型的代码文件。
理由:
在头文件中只应该包含各种声明,而不应该包含具体的实现。
规则描述:
头文件的格式应该为:
#ifndef <IDENT>
#define <IDENT>
...
#endif
或者
#if !defined (<IDENT>)
#define <IDENT>
...
#endif
上面的<IDENT>是一个标识字符串。RuleChecker要求在该标识字符串中必须包含相应头文件的文件名,不区分大小写。
参数:
参数有两个,一个用于设置标识字符串的长度,另一个参数设置检查什么类型的代码文件。
理由:
避免对同一头文件的重复包含。
举例:
// 对于文件audit.h,它的文件结构应该为:
#ifndef AUDIT_H
#define AUDIT_H
...
#endif
规则描述:
在程序中声明、定义的函数、自定义数据类型、变量,在对其命名时都应该遵守一个统一的命名规范。
在RuleChecker默认情况下,只规定在常量、宏中不能使用小写的英文字母。
参数:
参数是一系列的字符串,单数编号字符串指出了你要设定的对象,双数字符串指出了你为其设定的具体格式。
理由:
提高代码的可读性。
规则描述:
在程序中声明、定义的函数、自定义数据类型、变量,它们的名称的长度要在设定的范围之内。
RuleChecker默认情况下规定:函数名长度在4到25个字符之间,自定义数据类型名、变量名、常量名、宏名、类名长度在5到25个字符之间,其它标识符的长度在1到25个字符之间。
参数:
参数分三列,第一列指出了你要设定的对象,第二列、第三类指出了命名的长度范围。
理由:
提高代码的可读性。
规则描述:
某些标识符在代码中应该被禁用。比如,类库中的某些数据成员的名字。
在RuleChecker默认情况下,不禁用任何标识符。
参数:
参数是一系列的字符串列表,其中的每个字符串就是在程序中要禁用的标识符。
理由:
改善代码的可移植性。
规则描述:
要明确指定函数、函数参数、类数据成员、变量的类型。
理由:
改善代码的可移植性。
举例:
// 不要这样写
aFunction();
// 应该这样写
void aFunction(void);
规则描述:
只允许在指定类型的代码文件中包含其它代码文件。
在RuleChecker默认情况下,只允许头文件被包含到其它代码文件中去。
参数:
参数为两个字符串列表,每个列表中的第一个字符串指定了目标文件的类型,第二个字符串指出了都有那些类型的文件可以被包含到该目标文件中去。
理由:
改善程序代码的组织结构。
规则描述:
内联函数的定义部分,要放到类的实现文件(.cpp)中去,而不要直接放在头文件中。
参数:
参数只有一个,字符串——“private”,该参数可以用也可以不用。
当放置了“private”这个字符串时,private型内联函数的定义要放到类的实现文件(.cpp)中去,其它内联函数的定义要放在头文件中;当没有放置这个字符串时,所有的内联函数的定义部分,都要放到实现文件(.cpp)中去。
理由:
提高代码的可读性。
该规则存在的问题:
放置参数“private”后并不起作用。不管是否放置了“private”字符串,该规则都会要求将所有内联函数的定义放到实现文件(.cpp)中去。
规则描述:
可以在宏中(这里的宏只包括宏函数和宏常量这两类宏,并且检查部分只是指宏的展开部分,而不包括宏的名字)禁用某些字符。
在RuleChecker默认情况下,不禁用任何字符。
参数:
参数为两对相互关联的字符串。第一对字符串是针对宏常量的,第二对字符串是针对宏函数的。要禁用哪些字符,就把这些字符添加到字符串中去。
理由:
改善代码的可移植性。
规则描述:
对于宏的展开部分,在宏的参数出现的地方要加括号“()”。
理由:
保证宏替换的安全,同时提高代码的可读性。
举例:
// 不要这样写
#define GET_NAME(obj,ind) obj->name[ind]
// 应该这样写
#define GET_NAME(obj,ind) (obj)->name[ind]
规则描述:
宏常量的使用要受到限制。
在RuleChecker中,有三个可选的设置:
• var:当设定该参数时,字符串不能用宏常量来表示,其它的数值不受限制。
举例:
//以下的写法是合法的
const char *string = "Hello world!\n";
#define value 3
// 不允许这样写
#define string "Hello world!\n"
• const:当设定该参数时,一律要求使用const来定义数值常量,禁止使用通过宏来替代数值的方法。
举例:
//以下的写法是合法的
const char *string = "Hello world!\n";
const int value = 3;
//不允许这样写
#define string "Hello world!\n"
#define value 3
• nodefine:当设定该参数时,程序中只允许使用宏函数和预编译指令。其它任何的宏均禁止使用。
举例:
// 以下的写法是合法的
#define VERBOSE
#define min(x,y) ((x)<(y)?(x):(y))
// 这样写是不允许的
#define value 3
#define current_value f(tab[0])
参数:
参数就是上面提到的那三个字符串——“var”、“const”、“nodefine”。
理由:
限制宏常量的使用。
规则描述:
用内联函数代替宏函数。
理由:
同宏函数相比,内联函数不但具有宏函数的效率,而且使用起来更安全。
规则描述:
代码文件的名字要与文件中声明、定义的类的名字相关联。RuleChecker提供的方法是将二者的名字作为字符串进行比较。
在RuleChecker 中可以设置文件名和类名要进行比较的字符的个数,比如我们设定为1到6,则RuleChecker首先会从文件名中取出其前6个字符(如果文件名总长度不足6个字符,比如文件名长度为5,则只取这5个字符),然后到该文件中包含的类的名字中去查找这个字符串(不考虑大小写,且文件的扩展名不在比较范围之内),找到则认为该规则通过,如果找不到则不通过。
参数:
参数就是上面提到的那个文件名和类名进行比较的字符个数。在RuleChecker默认情况下,文件名与类名比较的字符个数为1到5。
理由:
使应用程序容易理解。
举例:
如果我们设置了参数为2到8,对于类
class CGraphNode { ...}
包含它的文件的文件名为如下这些名字时是符合要求的:
CGraphNode.h //比较字符串为GraphNo,可以在类名中找到。
GraphNode.h //比较字符串为raphNod,可以在类名中找到。
Graph.h //比较字符串为raph,可以在类名中找到。
Node.h //比较字符串为ode,可以在类名中找到。
... ...
下面的文件名是不符合要求的:
NodeGraph.h//比较字符串为odeGrap,不能在类名中找到。
CfGraphNode//比较字符串为fGraphN,不能在类名中找到。
规则描述:
在一条程序语句中只应包含一个赋值操作符。赋值操作符包括:=, +=, -=, *=, /=, %=, >>=, <<=, &=, |=,^=, ++, --。
理由:
避免产生不明确的赋值顺序。
举例:
// 不要这样写
b = c = 5;
a = (b++ * c) + 5;
// 应该这样写
c = 5;
b = c;
b++;
a = (b * c) + 5;
规则描述:
在程序中要限制使用编译指令。
参数:
参数为一个字符串列表。
在RuleChecker 中,我们可以将允许在程序中出现的编译指令加入到这个字符串列表中,如果程序中使用了未出现在列表中的编译指令,则RuleChecker会报告错误。该参数列表也可以被设置为空,此时表示在程序中不允许使用任何的编译指令。可选择加入到参数列表中的字符串有:
“define”:表示允许使用#define;
“include”:表示允许使用#include;
“if”:表示允许使用#if, #ifdef 和 #ifndef语句块;
“undef”: 表示允许使用#undef;
“error”: 表示允许使用#error;
“pragma”:表示允许使用#pragma;
“line”: 表示允许使用#line;
“none”: 表示允许使用#操作符;
理由:
使得代码更加容易阅读和理解。
规则描述:
在C++中,不要再使用struct。
规则描述:
在程序中不要使用模板。
理由:
提高代码的效率。
规则描述:
在程序中不要使用throw这个关键字。
疑义:
使用异常处理有什么不好???
规则描述:
在C++中,不要再使用union。
规则描述:
在每一个类中都要显示重载“=”操作符。
参数:
参数只有一个,要么为空,要么为字符串"dynalloc"。如果设置参数为"dynalloc",那么RuleChecker仅在当类中包含指针类型的数据成员时,才要求类要显示重载“=”操作符;如果设置参数为空,则不管类中包含什么样的成员,都要显示重载“=”操作符。
理由:
确保类的编写者考虑将一个该类对象赋值给另一个该类的对象时,可能出现的各种情况。
举例:
// 应该这样写代码
class aClass {
...
operator = (const aClass &object);
...
};
规则描述:
在声明、定义一个函数时,要明确指出函数参数的使用类型。
参数:
参数为一个字符串列表,在列表中可以包含"IN", "OUT" 和"INOUT"这三个字符串,用来修饰函数参数在函数中的使用类型。其中
"IN":代表输入数据的参数;
"OUT" :代表输出数据的参数;
"INOUT":代表既输入数据,又输出数据的参数;
理由:
提高代码的可读性。
举例:
// 应该这样写代码
int Multiply(IN int nNumber, OUT float *pV);
... ...
int Multiply(IN int nNumber, OUT float *pV)
{
... ...
}
规则描述:
在该条规则中,RuleChecker列出了在程序中不能对其进行分析的代码。
规则描述:
在代码中用ptr->fld的形式代替(*ptr).fld的形式。
规则描述:
在定义指针变量的同时,就要对其进行初始化。
理由:
保证指针变量在被使用前已经被初始化。
举例:
// 不要这样写代码
int *y ;
*y=&x ;
...
// 应该这样写
int* y=&x;
...
规则描述:
在类声明中,要将处于不同访问控制级别的类成员,按照指定的顺序声明。
参数:
参数为一个字符串列表,其中可以包括:""、"private"、"protected"、"public"这四个字符串。
"private"、"protected"、"public"三个字符串分别代表private、protected、public访问控制级别的类成员,空字符串""表示未明确指出访问控制级别的类成员(未明确指出的访问控制级别被默认为private)。
在列表中出现的字符串,表示允许在类声明中出现的访问控制级别,列表中字符串的顺序,表示对声明不同访问控制级别的类成员的声明顺序要求。
RuleChecker默认情况下该参数为空。
理由:
提高代码的可读性。
举例:
// 如果我们这样设置了参数:"" "private" "protected" "public",则表示我们允许在类//中声明private、、protected、public访问控制级别的成员,且声明的顺序依次是:默认//访问控制级别成员、private成员、protected成员、public成员。对于声明的顺序,并不//要求必须完整包括这四部分,声明顺序可以只是:protected成员、public成员,或者是//private成员、public成员,等等。
// 下面的类声明是符合要求的
class aClass //顺序为:""、 "private" 、"protected"、 "public"。
{
int i ;
private:
void p1();
protected:
void p2();
public:
void p3();
};
class aClass //顺序为:"" 、"protected"。
{
int i ;
protected:
void p();
};
class aClass //顺序为: "protected" 。"public"。
{
protected:
int i ;
public:
void p();
};
// 下面的这个是不合格的
class aClass //颠倒了"private"和"protected"的顺序,不符合要求。
{
protected:
...;
private:
... ;
};
规则描述:
在整个系统中,应该有一个类作为所有其它类的直接或间接父类。
参数:
参数为一个字符串,来指定这个超级基类的名字。
疑义:
RuleChecker 在对这条规则的实现上有一些缺陷。比如我们在系统中使用的全部是MFC库中以CObject为基类的类,指定参数为“CObject”后, RuleChecker并不能识别出Cobject这个类,仍然认为你违反了该条规则。只有系统中使用的类不来自任何的第三方类库,全部由你编写, RuleChecker才可以识别出来。
规则描述:
一次(一条声明、定义语句)只声明、定义一个变量。
理由:
提高代码的可读性。
举例:
// 应该这样写
int width;
int length;
// 不要这样写
int width, length;
规则描述:
一个函数中应该只有一条return语句。
理由:
增加函数的可靠性;使代码更容易理解。
疑义:
RuleChecker在对这条规则的实现上不太完善,它只是机械的检查“return”的个数。比如对于如下的代码:
void CMyClass::Fun1( int* a)
{
if( a > 10 )
return;
a++;
}
实际是有两个出口,但RuleChecker却认为其合格。
2.1.55 slcom 注释使用“//”
规则描述:
对代码的注释一律使用“//”,禁止使用“/*...*/”。
理由:
提高代码的可读性。
规则描述:
一行只写一条程序语句。
理由:
提高代码的可读性。
举例:
// 不要这样写
x = x0; y = y0;
while (IsOk(x)) {x++;}
while (IsOk(x)) {x++;
}
// 应该这样写代码
x = x0;
y = y0;
while (IsOk(x)) {
x++;
}
规则描述:
在switch语句块中,一定要有default分支来处理其它的情况。
参数:
参数只有一个,要么为空,要么为字符串"last"。如果设置参数为"last",则要求default分支出现在switch语句块的最后。如果不设置这个参数,则switch语句块中只要有default分支即可。
RuleChecker默认情况下参数被设置为"last"。
理由:
用来处理switch语句中默认、特殊的情况。
规则描述:
switch语句中的每一个case分支,都要以break、continue、goto、return、exit中之一作为分支的结尾(几个连续的空case语句允许共用一个)。
参数:
参数只有一个,要么为空,要么为字符串"nolast"。如果设置参数为"nolast",则对最后一个分支不做要求。如果不设置"nolast",则对最后一个分支做同样的要求。
RuleChecker默认情况下参数被设置为"nolast"。
理由:
使代码更容易理解;减少代码发生错误的可能性。
规则描述:
在由基类派生子类时,要明确指明派生类对基类的访问控制(public, protected, private)。
理由:
明确子类、基类的继承关系。
举例:
// 不要这样写
class inherclass1 : public Base1, Base2 //等价于public Base1, private Base2
{...}
class inherclass2 : Base1 //等价于private Base1
{...}
// 应该这样写,
class inherclass : public Base1, private Base2
{...}
class inherclass2 : private Base1
{...}
规则描述:
我们可以禁止在程序中声明、定义某些类型的变量,禁止在程序中声明、定义某些返回类型的函数。
在RuleChecker默认情况下,不禁用任何类型。
注意:
当你用typedef为某一类型重新定义了一个名字时,则RuleChecker不再能够正确的识别出该类型。
参数:
参数是两个分别以“data”和“function”开头的字符串列表。以“data”开头的字符串列表表示禁止在程序中声明、定义哪些类型的变量,以“function”开头的字符串列表表示禁止在程序中声明、定义哪些返回类型的函数。
理由;
提高程序的可移植性。
规则描述:
定义、声明一个函数时,在其参数列表中禁止使用“…”。
在参数列表中使用“…”的一个例子:C函数printf(const char *, ...)
理由:
使代码更容易理解。
规则描述:
禁止在程序中直接定义结构体(struct)、联合体(union)变量。
要使用typedef为自定义的结构体、联合体重新命名后,再使用这个名字来定义变量。
理由:
使代码更容易理解。
举例:
// 不要这样写
struct {
...
} varName;
// 应该这样写
typedef struct {
...
} typeName;
typeName varName;
下面的19条编码规则取自Scott Meyers写的两本书:《Effective C++》、《More Effective C++》,所以我们称它们为Scott Meyers编码规则。象前面的基本规则一样,我们逐条来介绍每一条规则。
( 注:Scott Meyers,1993年获得布朗大学的计算机科学博士学位,是C++领域公认的权威。他是《Effective C++》和《More Effective C++》这两C++名著的作者、C++ Report的知名专栏作家、全球各技术研讨会上极具号召力的讲师。《Effective C++》、《More Effective C++》这两本书,提供了很多改善C++程序设计技术和设计思维的有效方法,非常值得一读。)
该规则参考自《Effective C++》中的条款17。
规则描述:
在类的赋值操作符的定义中,RuleChecker会检查:
- 程序是否对参数和this(或 *this)之间进行了“==”判断;
- 如果进行了这个判断,是否返回了*this;
理由:
确保在类的赋值操作符的定义中,避免发生自己赋值给自己的这种情况。
举例:
class CTest
{
public:
int m_1;
CTest& operator=(CTest& obj);
};
//这样写是不对的
CTest & CTest::operator=( CTest & obj)
{
m_1 = obj1.m_1;
return *this;
}
//应该这样写
CTest & CTest::operator=( CTest & obj)
{
if(this == &obj1)// 防止自己赋值给自己的情况发生
return *this;
m_1 = obj1.m_1;
return *this;
}
该规则目前存在的问题:
在RuleChecker中,该规则不能正确发挥作用。象上面那种存在缺陷的情况,RuleChecker并不能检测出来。
该规则参考自《More Effective C++》中的条款2。
规则描述:
用C++提供的类型转换操作符(static_cast,const_cast, dynamic_cast和reinterpret_cast)代替C风格的类型转换符。
理由:
C风格的类型转换符有两个缺点:
1 允许你在任何类型之间进行转换,即使在这些类型中存在着巨大的不同。
2 在程序语句中难以识别。
该规则参考自《More Effective C++》中的条款13。
规则描述:
让异常通过引用的方式传递到catch子句中。
理由:
改善代码的效率。
该规则参考自《Effective C++》中的条款12。
规则描述:
类中非静态数据成员的初始化操作,一律放到构造函数的初始化列表中进行。
理由:
改善代码的效率。
该规则参考自《Effective C++》中的条款8。
规则描述:
重载new操作符时应遵守的约定:
- 返回值的类型要为void *;
- 第一个参数的类型要为size_t。
重载delete操作符时应遵守的约定
-返回值的类型要为void;
- 第一个参数的类型要为void *;
- 如果有第二个参数,类型要为size_t。
参数:
参数只有一个,要么为空,要么为字符串" static "。如果设置参数为" static ",表示重载new、delete操作符为类的静态(static)成员。
理由:
使重载的new、delete操作符的行为与缺省new、delete的行为保持一致。
该规则参考自《More Effective C++》中的条款10。
规则描述:
类中尽量不要包含指向类对象的指针数据成员。
理由:
一是防止在执行构造函数过程中发生异常而导致内存泄露,二是为了简化析构函数。
该规则参考自《Effective C++》中的条款10。
规则描述:
如果你为一个类重载了操作符new,那你也应该为这个类重载操作符delete。
理由:
操作符new和操作符delete需要一起合作。
该规则参考自《More Effective C++》中的条款14。
规则描述:
不要使用异常处理。
理由:
异常处理的不当使用,经常会导致严重、隐蔽的错误。
该规则参考自《More Effective C++》中的条款24。
规则描述:
虚拟函数不要被声明为内联(inline)的。
理由:
改善代码的效率。
该规则参考自《More Effective C++》中的条款43。
规则描述:
尽量不要使用多继承。
RuleChecker仅在一种情况下允许使用多继承:多继承中的基类为抽象类,也就是说在基类中至少要包含一个纯虚函数。
理由:
减少复杂性,使代码更容易理解。
示例:
例一:
A和B不是抽象类,C多继承自A和B,则C违反此规则。
例二:
A和B都是抽象类,C多继承自A和B,则C不违反规则。
例三:
A是抽象类,B不是抽象类,C多继承自A和B,则C违反此规则。
该规则参考自《More Effective C++》中的条款33。
规则描述:
非末端类应该设计为抽象类(在类的继承图中,处于继承图叶子节点上的类,我们称之为末端类,其他类我们称之为非末端类)。
理由:
提高程序的可靠性、健壮性、可读性、可扩展性。
该规则参考自《Effective C++》中的条款 9。
规则描述:
如果为一个类重载了一个或几个操作符“new”,那么至少要有一个重载操作支持“new”的正规调用形式,下面是具体的方法:
- new的第一个参数类型必须为size_t;
- 为其它的参数提供默认参数值。
理由:
保证重载了“new”操作符的类,仍然可以正常使用“new”的正规形式。
该规则参考自《More Effective C++》中的条款 7。
规则描述:
一定不要重载"&&", "||" 和 ","操作符。
理由:
重载"&&", "||" 和 ","操作符时,不能很好的提供给程序员他们所期望和使用的行为特性。
该规则参考自《More Effective C++》中的条款 6。
规则描述:
当重载自增、自减操作符时,要对它们的前缀形式和后缀形式分别进行重载,象下面这样:
class Example
{
public:
Example& operator++(); // ++ 前缀
const Example operator++(int); // ++ 后缀
Example& operator--(); // --前缀
const Example operator--(int); // ++ 后缀
};
理由:
保证定义的类与C++内置数据类型操作上的一致性。
该规则目前存在的问题:
在RuleChecker中,该规则并不能正确发挥作用。象上面那样写代码时,RuleChecker仍会认为违反规则。
该规则参考自《Effective C++》中的条款 22。
规则描述:
对于类类型的参数,要以引用(by reference)的方式传递。
理由:
改善代码的效率。
该规则参考自《Effective C++》中的条款 15。
规则描述:
在为一个类重载“=”操作符时,必须使其返回*this。
理由:
如果不返回*this的话,会防碍我们进行连续赋值(a = b = c;)这种操作,或是导致类型转换不能正确的实现。
该规则参考自《More Effective C++》中的条款 15。
规则描述:
不要使用异常处理。
理由:
改善代码的效率。
该规则参考自《More Effective C++》中的条款 11。
规则描述:
在析构函数中要考虑发生异常的情况,要包含try、catch语句块。
理由:
防止terminate被调用,同时确保析构函数能被完整执行。
(terminate函数:当我们没有为产生的某一种异常情况提供处理方法时,terminate会被调用。这个函数的作用正如其名字所表示的——它终止你程序的运行,而且是立即终止,并且不会释放局部对象。)
该规则参考自《Effective C++》中的条款 14。
规则描述:
基类的析构函数一定要为虚拟函数(virtual Destructor)。
理由:
保证类对象内存被释放之前,基类和派生类的析构函数都被调用。
好了,现在Rulechecker包含的81条编码规范全部介绍完了。水平有限,有一些规则的解释可能不太流畅。如果你对某些编码规则的解释存在疑惑,可以参看Logiscope附带的电子文挡(安装Logiscope后,位置为“开始 | 程序 | Telelogic Tau Logiscope | Documentation | Logiscope RuleChecker | C++”,Rulechecker这81条编码规范的原文解释全部包含其中)。你也可以阅读Scott Meyers的《Effective C++》和《More Effective C++》这两本书,来获得更多的信息。