C/C++代码检查方法

C++代码检查方法
 

译自 An Abbreviated C++ Code Inspection Checklist
       John T. Baldwin October 27, 1992
       by realfun, 2003-7-20
    
 
 
 
(sloc/hr : source lines of code per hour,每小时代码行数)
 
一、    2-5名检查人员,不包括作者
 
二、    分块打印代码,一定要打印行数,每人次不超过250行代码,包括注释,不包括空行

 
三、    概观:代码作者花上20-40分钟解释代码的编排。检查人员不得问问题(从代码中找
答案)代码作者最好拣精要的地方说,时间尽量少。
 
四、    单独检查:每一名检查者用一个用于检查的列表来促进检查。这个过程应当单独进行。每小时大约70-120行,不要太快,也不要太慢。(统计发现122行以上效果就会迅速降低)一行行读代码,尽量理解它。对每行、每块代码都用检查列表来对照。对每个可行的问题,找到答案是“是”的地方,这基本上代表着一个错误,记下来。你会发现有些代码错误是句法级别的,有些则是理解级别的,做好经常切换注意力的思想准备。
 
五、    开会:对某一个模块执行检查的所有小组成员进行开会。如果作者出席,记住其目的是收集反馈信息,而不是“捍卫”、“解释”自己的代码。记住:一个主要的检查任务是保证代码的自解释性。 每个会议严格控制在2个小时(包括中场休息)。因为效率随时间加长而降低。尽量不要打断会议进程。一次会议中不同的检查者会涉及不同的代码区域。因此,单个会议上可以进行(5检查者)×(120sloc/hr)×(2hrs) = 1200行代码。实际上有些人员的代码会重复,检查量减小。如果两个小时还进行不完,结束会议。记录者应该提交当前存在的问题给作者或维护人员。残留问题放在下一次会议解决。
 
六、    重写:污点代码提交给作者或维护者来重写。可以改动代码、添加删除注释、改变?br> ?br> 构等等。但是,不能在会议上讨论解决方案(这样既不高效也没必要)。改动后,应该召
集部分或所有检查人员开一个短会。会议由作者主持,他可以决定接不接受与会者的建议。
 
七、    选一个仲裁者,其责任是监视所有的污点代码的改正。其正确性鉴定可以在一个短?br> ?br> 上或者在后来代码检查过程中。
 
八、    记录:为了客观地跟踪污点代码的检测以及改正,会议上的记录非常必要。为了消?br> ?br> 记录被用来评价代码作者的可能性,可以不记录作者的名字和模块名称(用模块代号来代替)。文档在这段工作完成后销毁。
 
------------------------------
 
常用的检查用列表:
 
1.      变量声明:
1.1     数组
1.1.1   数组维度是否是写死在代码里的?
        int     intarray[13];
        应为:
        int intarray[TOT_MONTHS + 1];

                                        ...
                        private:
                                        enum { MAX_FOO_BUFFERS = 40; }
                                        ...
                        }
        用static可以限制一个类只有一个常量实例。
        静态成员变量有个不足之处:不能用它来做静态数组的size。
 
1.3     标量变量
1.3.1   是否定义了一个有符号的变量,而它只在非负情况下有意义?
                int age;
        应为:
                unsigned int age;
        (注:此处我觉得不太合理,C++编译器一般不对unsigned 和 signed报警,如果这样用,不小心赋了一个-1给unsigned int, 会自动转化,变成一个很大正整数,掩盖了错误,不利于改正。 如果用int的话,还可以检测是否为负,使用断言assert也可以。realfun)
 
1.3.2   代码是否假设char施signed 或者是 unsigned?
                typedef char SmallInt;
                smallInt mumble = 254;
        应为:

                typedef unsigned char smallUInt;
                typedef signed char smallInt;
1.3.3   程序是否用了不必要的float或者double?
                Double  acct_balance;
        应为
                unsigned long acct_balance;
 
1.4     类
1.4.1   类是否有虚函数?如果有,是否是虚析构函数?
1.4.2   类是否有:拷贝构造函数、赋值操作符、析构函数?如果有,一般情况下要一起出现。
 
2.      数据使用
2.1     字符串
2.1.1   是否不以null(‘/0’)结尾?
2.1.2   是否将strXXX()一类的函数用于没有结尾的char array?
 
2.2     缓冲区
2.2.1   每次向缓冲区写入时是否总有一个大小检查?
2.2.2   缓冲区大小是否会不够用?
 
2.3     位域(bit field)

2.3.1   真的需要使用位域吗?
2.3.2   有没有顺序问题(考虑到代码移植性)?
 
3.      初始化
3.1     局部变量
3.1.1   局部变量使用前是否未经初始化?
3.1.2   C++是否初始化生成以后,再初始化?(浪费时间而且不清晰)
 
3.2     丢失重初始化
3.2.1   可否将一个变量的原来的值代入下一个运算(不重新初始化)?
 
4.      宏
4.1     如果宏变量被多次使用,变量是否有副作用?
        例如:
        #define max(a,b) ( (a) > (b) ? (a) : (b) )
        max(i++, j);    //i 被加了两次
 
4.2     宏里的表达式是否未加上括号?
        例如:
        #define max(a, b) (a) > (b) ? (a) : (b)
        result = max(i, j) + 3;
        被扩展为:result = (i) > (j) ? (i) : (j)+3;


4.3     宏中的变量是否未加上括号?
        例如:
        #define IsXBitSet(var) (var && bitmask)
        result = IsXBitSet( i || j );
        扩展为:result = (i || j && bitmask); //不是我们所期望的
        应该定义宏为:#define IsXBitSet(var) ((var) && (bitmask))
 
 
5.      数据大小
5.1     调用函数时,缓冲区(参数)大小不等于size参数的值.
        例如: memset(buffer1, 0, sizeof(buffer2);
5.2     sizeof的对象是否不正确?
                sizeof(*ptr)还是sizeof(ptr)
                sizeof(*array)还是 sizeof(array)
                sizeof(array)还是sizeof(array[0]) //当用户需要元素大小时
 
6.      动态分配
6.1     分配
6.1.1   空间是否会不足?
6.1.2   分配者是否假定用户在另一个地方释放之?
        (这样并不一定会错,但是应该纪录下来,包括这样做的理由。类的构造与析构函数要特别注        意.).
 
6.1.3   是否使用了malloc或者calloc, realloc,而不是C++推荐的new?
 
6.2     释放
6.2.1   数组是否使用了delete[]进行释放?
6.2.2   是否还有指针指向已经被释放的空间?
6.2.3   待释放空间是否已经被释放过(6.2.2遗留的问题)?
6.2.4   是否用delete释放了由malloc, calloc, realloc分配的空间?
6.2.5   是否用free释放了new分配的空间?
 
 
7.      指针
7.1     指针无效后,是否未赋值为NULL?
7.2     对指针做copy时,是否需要新分配空间?
 
8.      转换(cast)
8.1     NULL传递给函数作参数时是否被转换为正确的类型?(@@@@需要吗?)
8.2     代码是否依赖于隐式类型转换?
 
9.      计算
9.1     当测试赋值得结果时,是否按需要加上了括号?

                例如: if( a = function()==0)
                应为: if( (a = function()) == 0)
9.2     是否有些需要同步的数据并没有被更新?
 
10.     条件语句
10.1    是否使用==对一个浮点数进行判断?
                如: if (someVar == 0.1),应该使用相减,然后>, >=, <, <=,
                     if (abs(someVar – 0.1) <= eps),eps很小,1e-6等。
10.2    是否用unsigned变量与0比较大小?
                如:if (myUnsignedVar >= 0)     //永远为true
10.3    是否用signed变量直接作为判断表达式?
                如:if (mySignedVar)    //不一定对,为负时可能期望它为false
                如果这样,应该为: if (mySignedVar > 0),等等。
10.4    如果测试是用来检查错误, “错误情况”是否真的合理?
 
11.     流程控制
11.1    控制变量
11.1.1  下限是否是个exclusive的限制?
11.1.2  上限是否是个inclusive的限制?
        例如:(0, 2]就是这样一种区间,上闭下开。但一般情况下推荐使用[0, 2),这样的上开下闭的区        区间。


11.2    分支
11.2.1  在switch时,是否有缺少break的case语句?
                        “从上面掉下”的用法只用在使事情变得简单而且清晰的地方。
11.2.2  switch是否缺少default分支?
                        即便不可能到达default分支,也要写上。有利于检查代码错误。
 
11.2.3  循环是否用一个bool型的变量作为结束标志goto出去?
                        一般应使用break.
 
12.     赋值
12.1    赋值操作符
12.1.1  是否在可用“a += b”的地方使用了 “a = a + b”?
12.1.2  赋值操作符或者拷贝构造函数参数是否为non-const的?
12.1.3  重载的赋值操作符是否做了自相等检测?
                        if (this == &right_hand_arg)
                                return *this;
 
12.2    使用赋值运算符
12.2.1  复制运算符能用初始化替代吗?
12.2.2  是否在表达式与变量之间有错配,如:需要element number,赋给的却是byte co
unt?


13.     参数传递
13.1    是否对非内部类型的参数使用了传值方式?
                因该用reference或者const reference
 
14.     返回值
14.1    返回值是否赋给一个精度不够的变量?
                注:指的是getchar返回int问题
        int getchar(void);
        char chr;
        while ( (chr = getchar()) != EOF ) {
                ...
        };
14.2    是否public成员函数返回一个指向成员变量(或者是外部变量)的non-const reference或者是 non-const pointer?
        这样就会将受保护的变量暴露。
14.3    是否有函数返回一个引用,而实际上应该返回一个对象?
        如:操作符+-*/的重载。
14.4    是否在应该返回const reference的地方返回了值?
 
15.     函数调用

15.1    参数个数不定的函数
15.1.1  fprintf的FILE参数丢失?
15.1.2  是否有多余的参数?
15.1.3  参数类型是否显式地匹配格式串中的参数?
                        指的是printf(”%d”, a_long_int); 其实应该用%ld;
 
15.2    通用函数
15.2.1  函数调用正确吗?是否调错了函数?(如:strchr与strrchr)
15.2.2  函数调用是否违反了该函数需要的先决条件?
 
16.     文件
16.1    临时文件名是否为唯一的?(惊奇的是:这个问题居然是常见的bug)
16.2    文件指针在打开之前是否未关闭前一个打开的文件?
 
17.     隐式类型转换带来的问题
 
 
        class String {
                public:
                String( char *arg ); // copy constructor
                // ...
        };


        void foo( const String& aString );
 
        class Word {
                public:
                Word( char *arg ); // copy constructor
                // ...
        };
 
        void foo( const Word& aWord );
 
        void gorp()
        {
                foo("hello"); // This used to work!
                // Now it breaks! What gives?
        }
        会报错,因为有两个函数可以与foo("hello")的调用可以匹配:
        void foo( const String& );
        void foo( const Word& );

 

 

1.1.2 数组维度是否等于元素总数?
        char entry[TOTAL_ENTRIES];
        应为
        char entry[LAST_ENTRY + 1];
        这个例子中第一种代码很容易造成“差一”错误。
 
1.2     常量
1.2.1   变量的值是否从来都不改动?
        int monthes_in_year = 12;
        应为
        const unsigned months_in_year = 12;
1.2.2   是否有些常量以#define方式定义?
        #define MAX_FILES 20
        应为:
        const unsigned MAX_FILES = 20;
1.2.3   常量是否只用于很少的几个(甚至一个)类?如果是,常量是否全局的?
                const unsigned MAX_FOOS = 1000;
                const unsigned MAX_FOO_BUFFERS = 40;
        应为
                class foo {
                        public:
                                        enum { MAX_INSTANCES = 1000; }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值