代码检视

编程入门 行业动态 更新时间:2024-10-13 00:35:16

<a href=https://www.elefans.com/category/jswz/34/1771412.html style=代码检视"/>

代码检视

1、与详细设计的一致性
 只要将检视的代码对照详细设计进行比较就很容易检查出代码是否和详细设计一致,采用逐行逐字阅读进行比较的方法进行。
2、头文件检查
头文件检查主要关注以下方面:
(1) 是否包含有多余的其他头文件
(2) 头文件是否内聚,即是否多个模块共用一个头文件
(3) 头文件内的内容是否清晰,是否分类排放好并给出了足够的注释
(4) 是否使用了象 #ifdef __LIST_H__ 之类的宏定义保证头文件不被重复引用
3、宏定义检查
(1) 宏定义中有参数和表达式时,参数和表达式是否都用括号括起来了。例如:
#define ADD(a, b) (a + b)  //正确的应该是 ((a) + (b))
这个定义中就没有将参数a和b括起来,如果使用时a和b是表达式的话,就会因为运算符顺序问题而出问题。
(2) 续行符\是否使用正确
4、常量
常量方面主要检查的主要问题如下:
(1) 常量是否使用了宏来进行定义
(2) 程序中是否存在魔鬼数字
(3) 16进制数据是否在前面加上了0x
(4) 常量是否来自规格
(5) 不来自规格的常量的值是否合理
5、全局变量与共享变量需要检查的主要问题如下:
(1) 全局变量是否必须的,是否可以改成局部变量
(2) 是否有全局范围内变量和局部范围内变量重名情况
(3) 是否有多个任务访问共享变量,是否进行了有效的保护
(4) 当全局变量只限于本文件内使用时,是否定义成静态的
(5) 多个任务读写共享变量时,是否可以将读写操作封装成独立函数,而不是在每个模块里都进行加锁解锁操作
6、 静态变量和函数
静态变量和函数检视时主要问题如下:
(1) 静态变量的使用是否正确
(2) 每次使用静态变量时是否需要重新初始化
(3) 对不需要重新初始化的静态变量在多次使用后是否有溢出的问题。
(4) 文件内部使用的函数是否要定义成静态的
7、 数据结构
数据结构方面考虑的主要问题如下:
(1) 数据结构里的成员类型定义是否正确
(2) 结构体里面变量顺序安排是否合理,数据是否对齐
(3) 是否存在冗余未用的成员变量。
(4) 类里面是否有私有变量和私有函数放到了公有的定义里去了
8、初始化
初始化考虑的主要问题如下:
(1) 变量使用前是否需要初始化
(2) 类的构造函数中是否对需要初始化的成员都进行了初始化
(3) 数组的初始化是否正确
(4) 内存或数组在每次使用前是否需要初始化清零
(5) 多个变量初始化赋值时是否存在顺序问题
(6) 静态变量和全局变量的初始化是否存在初始化顺序问题
9、字符串
(1) 字符串是否以’\0’结尾
(2) 字符串是否会超长
(3) 字符串使用的空间大小是否存在差1问题
(4) 使用字符串指针时,指向的位置是否存在差1问题
(5) 字符串指针是否可以为空,为空时会有什么现象?
(6) 字符串内容为空(即第一个字符为’\0’)时会发生什么现象?
(7) 字符串中如果有转义字符“\”字符时,是否正确地写成了“\\”
(8) 在对字符串进行拷贝或连接操作时,是否对空间大小进行校验?
10、输入检查
输入校验需要检视的主要问题如下:
(1) 函数参数是否需要进行检查
(2) 从文件读取的数据是否进行校验
(3) 使用全局数据时是否需要进行校验
(4) 通信收到的数据是否需要进行校验
(5) 从消息中接受到的数据是否需要进行校验
11、边界条件
凡是牵涉边界条件的地方都需要进行边界检查,以下的一些问题供参考:
(1)循环变量上的边界是否正确
(2)变量的取值是否有边界条件限制,边界是否给出并书写正确
(3)空间边界,如内存大小,数组大小是否正确,是否存在差1和越界情况
(4)数据结构边界,如链表的头一条记录和最后一条记录等边界情况
(5)服务器连接数量最大是多少
12、内存分配和释放
内存分配方面需要检查的有以下几点:
(1) 分配的大小是否正确,是否分配了过大的内存或者分配的内存大小不足,分配的内存大小是否存在差1错误
(2) 内存分配是否经过判断或者进行异常处理
(3) 重新分配一块内存时,是否将原有内存释放
(4) 分配的内存是否需要初始化清零
(5) 是否有在大循环中不断分配内存导致可能出现系统内存不足情况
释放方面需要检查的有以下几点:
(1) 所有的分支路径上是否将分配的内存进行了释放
(2) 是否将已经释放的内存重复释放
(3) 释放的是否是空指针
(4) 释放多块内存时是否存在释放的先后顺序问题
使用realloc()时要考虑以下几点:
(1) 新增空间是否需要初始化清零
(2) 是否还有指针指向老的内存块,并在realloc()后使用指向老的内存块的指针。
13、类型转换
类型转换的检查有以下问题供参考:
(1)类型转换是否采用安全的转换机制
(2)当采用强制转换时是否会出问题
(3)signed 和unsigned转换是否存在问题
(4)是否将小空间的类型转换成了大空间的类型
(5)类型转换是否会造成截断、溢出或越界
14、数组使用
数组的使用也是很容易出错的一种,不幸的是现在还没有足够好的方法能保证数组越界一类的问题得到完美的解决,所以通过对数组的检视来保证质量就很重要了,下面给出检视数组的一些建议:
(1)类型是否正确
(2)多维数组是否数据存放顺序正确
(3)数组使用时是否会越界,空间大小是否存在差1错误
(4)作用域是否正确
(5)数组大小是否太大导致浪费
15、指针使用
指针在C/C++中是使用最广泛的一种语法,指针使得语言的功能强大起来,但也给程序质量带来了很大麻烦,使用指针时是极易出错的,可以说C/C++代码中的缺陷大部分都与指针有关,下面给出检视指针的一些问题参考:
(1)指针是否初始化
(2)指针类型定义是否正确
(3)使用前是否申请了内存
(4)引用是否正确,是否引用了释放掉的空间
(5)指向的空间是否正确
(6)是否存在使用野指针现象
(7)释放后再使用时是否需要重新初始化
(8)是否使用了空指针,函数指针是否为空就被调用
(9)指针是否需要校验
(10)指针进行类型转换时是否会引起问题
(11) 指针地址运算是否有误,在地址相加时是否考虑了相加的数字要乘以指针类型所占空间的大小。比如int *p; p+1相比p的大小不是大于1,而是大于一个整数所占空间的字节数。
16、函数
函数方面的一些检视建议如下:
(1)函数调用的参数传递是否正确,
(2)是否有形参和实参使用错误的问题,
(3)函数的返回值和输出是否需要校验,
(4)调用的函数是否对全局数据产生影响
(5)函数功能是否单一,是否在函数里处理了多个不同的功能
(6)函数参数是否需要定义为const
(7)函数是否过长(一般以不超过200行为宜)
17、系统和标准库调用
调用系统函数和库函数时,以下一些检视建议供参考:
(1)系统调用是否正确,调用参数设置是否正确
(2)是否按照标准文档中的要求和注意事项进行了调用
(3)对于存在BUG的系统函数是否采取了规避措施进行调用
(4)对调用系统函数是否需要在调用前进行了输入校验
(5)调用后是否需要对输出进行校验
18、判断循环条件
在程序中的判断和循环条件中,也存在着一些有时通过测试难以发现的问题,主要的检视建议如下:
(1)逻辑运算符是否正确,如| 和||,&和&&运算符是否搞混淆掉或键盘失误写错,
(2)逻辑等号==是否误写成等号=
(3)运算符顺序是否正确,运算符| 、&,||、&&,=、== 的运算顺序需要特别注意
(4)循环判断中的表达式是否正确地使用了括号将运算顺序区分开,并增加可读性
(5)表达式运算是否存在逻辑上的错误
(6)对浮点数是否误用了精确相等进行比较
(7)循环变量是否进行了初始化
(8)循环的中止条件是否在某些情况下无法到达而造成死循环
(9)循环的边界上是否会造成问题
(10)判断条件是否会恒真或恒假

19、计算
计算错误也是程序中经常遇到的一个问题,大部分计算错误可以经过测试发现,但并不是所有的计算错误都可以很容易通过测试来发现,以下的一些问题供检视时参考:
(1)计算表达式或公式是否书写正确,需要逐字符地进行确认没有输入错误
(2)表达式中运算符顺序是否书写正确,同优先级运算符运算时是否存在自左至右结合或自右至左结合运算结果不同的问题
(3)是否需要使用括号来保证运算顺序的正确性和增加程序的可读性
(4)是否存在计算溢出情况,如两个整数相乘结果超出整数最大范围等情况
(5)截断误差和舍入误差是否会引起问题,误差是否会累积下去导致误差越来越大?
(6)是否存在除零问题(即零做分母的问题),或者两个整数相除结果得到零然后再和其他整数相乘。
(7)是否存在某个变量会累积增加导致长时间运行后的溢出
20、资源释放
资源释放方面的一些检视建议如下:
(1)所有的资源是否都进行了释放
(2)要检查是否存在某条路径遗漏了释放
(3)打开文件是否关闭了,信号量是否释放,句柄是否关闭,锁资源是否释放,是否存在死锁问题
(4)全局的资源是否存在随时间累积增加不减少的问题?
(5)其他各种资源如网络socket等是否在各条对应路径上进行了关闭
(6) 类的析构函数中是否对类中需要释放的成员进行了释放
21、效率
从空间和时间效率方面一些检视建议如下:
(1)多个if判断是否可以改为switch或if…else if结构,例如if(res<0){…}
if(res>1){…} if(res>=0){…}改为if…else if…else就可以减少判断次数
(2)多个相同处理的case语句是否归到一起
(3)在多重循环中,最忙的循环是否在最内层
(4)循环体内的判断语句是否可以移到循环体外

看完上面的检视要点后,找一些代码来试验一下看能否通过上面的内容来提高自己的检视能力。下面就举几个代码检视的实例:
1、求和函数的检视实例
/**   整数求和函数,计算0到指定正整数(包括指定整数)间所有整数的和
       @param int num - 指定的整数求和的上界     
       @return int - 求得的整数之和  
*/
int Sum( int num )
{
       int i, sum;
       for ( i =0; i < num; i++ )
       {
              sum += i;
       }
       return sum;
}
下面按照检视要点来对这个函数进行检视:
先分析以上代码中的变量初始化问题,显然sum变量没有被初始化
进行一致性检查发现函数是求正整数之和,函数参数应该定义成unsigned int类型才更确切
根据计算方面的检视要点进行分析,发现当num较大时可能会造成整数上限溢出,要解决这个溢出问题,有三种方法,一是将参数的类型改成unsigned short或者将返回值改成64位整数类型,或者在程序中对输入参数进行上限校验,校验失败返回-1表示失败。
进行循环边界检查发现i<num应该为i<=num
因此检视出来的主要问题如下:
1)        变量sum没有初始化
2)        函数参数类型定义不准确
3)        当参数num的值大到一定程度时会造成整数上限溢出
4)        循环中的条件i<num写错,应为i<=num
2 、日志文件函数的检视实例
/**   写日志函数,实现将日志信息写入日志文件中的功能
       @param char *pszLogFile - 日志文件名  
       @param char *pszLogMsg - 要写入的日志消息字符串
       @return int - 失败返回-1, 成功返回0    
*/
int WriteLog (char *pszLogFile, char *pszLogMsg)
{
       FILE       *fp;
       fp = fopen(pszLogFile, "w");      
       fseek(fp, 0, SEEK_END);
       if ( fwrite(pszLogMsg, sizeof(char), strlen(pszLogMsg), fp)
                     != strlen(pszLogMsg) )
       {
              return -1;
       }
       fclose(fp);
       return 0;
}

下面按照检视要点来对这个函数进行检视:
从输入校验方面的检视要点来看的话,输入参数没有校验。即没有判断输入的文件名是否合法或是否为空。输入的日志消息字符串是否为空也没有判断。
从函数方面的检视要点可以发现打开文件函数的返回值没有进行校验,即没有判断打开文件是否成功,
从资源释放方面的检视要点来看,打开的文件在return -1;这行之前没有被关闭。
再从系统调用方面的检视要点来看,调用fopen使用不正确的参数,实际上第二个参数不能使用”w”,”w”参数会将文件内容清空掉,以前写的信息都会丢失,应该改成“a+b”。改掉这个参数后,fseek()那个调用就可以不要删除掉了。
除了上面的检视要点外,还需要使用元素分析法来进行检查,从日志文件这个元素进行分析,两条日志信息间应该有分隔符分开的,因此需要对pszLogMsg判断是否有分隔符,如果没有的话,程序中应该给它补上分隔符。
所以总共可以得到上面写日志函数的几个主要问题如下:
1)        输入参数pszLogFile, pszLogMsg没有进行校验
2)        打开文件后没有判断是否打开成功
3)        fopen()第2个参数使用不正确,应该使用“a+b”作为参数
4)        return -1;前没有将打开的文件关闭掉
5)        日志文件中的两条日志信息间的分隔符没有考虑进行处理
3、字符串处理函数的检视实例
/**   删除字符串最右边的特殊字符(包括空格、回车符、换行符、TAB字符),直到遇到非特殊字符为止。 源字符串不能修改,删除特殊字符后的字符串放到返回值中。
       @param char *str - 源字符串指针   
       @return char * - 返回删除特殊字符后的字符串,
                        失败或者删除特殊字符后字符串内容为空时返回NULL; 
*/
char * StrTrimRight(char *str)
{
       int len;
       char *psz;
       if ( str == NULL )
       {
              return NULL;
       }
       len = strlen(str);
       psz = str + len;      
       while ( *psz == '\r' || *psz == '\n'|| *psz == ' ' || *psz == '\t' )
       {
              psz--;
              len--;
       }
       psz = (char *)malloc(len + 1);
       strncpy(psz, str, len);
       return psz;
先从校验的检视要点看,对输入的函数参数进行了校验
从函数方面的检视要点来看,调用的malloc函数没有对返回值进行校验
函数参数是否需要定义为const,按照注释里的说明,原字符串不能修改应将字符串输入参数定义为常量
从循环条件方面看,循环是否会中止呢?只有碰到不等于特殊字符中的字符才会结束,如果一个字符串中只有特殊字符的情况下,循环会执行到psz越过字符串str的头部,导致内存越界,将导致异常出现,或者异常没有出现而此时如果越界后的字符凑巧为特殊字符的话,将导致len变成负数,后果都将不堪设想,因此需要将循环的中止条件再加上对len是否小于等于0的判断
从计算方面看,psz = str + len 使得psz 指向了str字符串尾部的’\0’字符,会导致后面的循环内的代码不被执行,将没有把特殊字符删除掉就复制到返回值去了。
由于是字符串处理函数,因此下面着重从字符串的检视要点进行检查,首先考虑字符串内容为空的情况,在这种情况下,按照注释里的说明,应该返回NULL的,所以漏掉了对输入参数内容为空的校验,需要增加一句 if ( *str == ‘\0’ ) return NULL;
从系统和库调用方面来看,strncpy函数不会在复制后的字符串尾部添加’\0’,因此最后还需要在复制完后添加上’\0’
从边界进行分析,变量len具有边界条件,它的下界是0,当len为0时,显然删除特殊字符后的字符串内容为空,此时需要返回NULL,不需要分配内存和调用strncpy操作,因此遗漏了删除特殊字符后字符串为空的情况的处理。
因此总共可以发现这个函数有以下一些主要问题:
1)  psz = str + len; 导致psz指向字符尾部的’\0’字符,引起程序后面出错
2)  while 循环条件有问题,某些情况下会导致异常,应该加上 len 是否大于0的判断
3)  应加上对输入字符串内容是否为空(首字符是否为’\0’)的校验
4)  malloc()函数分配内存后没有判断是否成功
5)  strncpy没有给复制后的字符串尾部添加’\0’字符,需要另行添加
循环完后没有判断删除后字符串的内容是否为空,应通过len是否大于0来进行判断
字符串输入参数应定义为常量
1.看见了if,就想else
2.看见malloc,就去找free
3.函数调用要小心,需要看看返回值
4.看到for循环,就找边界值
5.看见return要注意,要去前面找资源
6.看见数组要提神,问题往往在下标
7.不要小看字符串,长度是个大问题
8.看到函数不要急,看看初始化,各种路径要小心
9.赋值函数最危险,指针没资源

更多推荐

代码检视

本文发布于:2024-02-07 03:54:47,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1753119.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:代码

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!