代码检视技能属于开发人员的基本功,能够很大程度地反应出开发人员的能力水平,前面4.4.1节已经讲过提高评审检视的方法。下面以实际的C/C++语言方面的代码来讲解代码检视的一些基本关注点和重点检查的内容。
从C/C++语言开发的代码来讲,检视时主要关注以下一些方面:
n 与详细设计的一致性
n 编译设置
n 头文件检查
n 宏定义检查
n 常量
n 全局变量与共享变量
n 静态变量和函数
n 数据结构
n 初始化
n 字符串
n 输入校验
n 内存分配和释放
n 边界条件
n 计算
n 类型转换
n 指针使用
n 数组使用
n 函数
n 系统和标准库调用
n 规范性
n 冗余代码
n 判断循环条件
n 注释文字
n 资源释放
n 特殊的语法规则
n 可移植性
n 网络功能
下面对这些需要关注的方面进行更进一步的说明:
1、与详细设计的一致性
只要将检视的代码对照详细设计进行比较就很容易检查出代码是否和详细设计一致,采用逐行逐字阅读进行比较的方法进行。
2、编译设置
编译设置主要检查以下方面:
n 是否使用了优化选项,优化类型和项目所需要的类型是否一致
n 是否正确使用了编译预定义的宏?比如在VC 中使用winsock2.0时要定义WIN32_LEAN_AND_MEAN 宏
n 是多线程还是单线程模式
n 结构体对齐字节数设置是否正确,跨平台时会不会有问题
n 调用系统的库是静态编译进程序内还是使用动态库调用形式?比如使用MFC就存在这个问题
n 发行版中连接的库是否误连接了调试版本的库(可能会出现将调试版设置拷贝到发行版,导致发行版本中连接了调试版本的库)
n include路径和lib路径设置是否正确,否则装有多个编译器的情况下有可能使用了不正确的头文件和库
3、头文件检查
头文件检查主要关注以下方面:
n 是否包含有多余的其他头文件
n 头文件是否内聚,即是否多个模块共用一个头文件
n 多个头文件的引用是否有先后顺序问题
n 头文件注释是否规范
n 头文件内的内容是否清晰,是否分类排放好并给出了足够的注释
n 包含的系统头文件是否有系统兼容性和移植性的问题
n 是否使用了象 #ifdef __LIST_H__ 之类的宏定义保证头文件不被重复引用
4、宏定义检查
n 宏定义中有参数和表达式时,参数和表达式是否都用括号括起来了。例如:
#define ADD(a, b) (a + b) //正确的应该是 ((a) + (b))
这个定义中就没有将参数a和b括起来,如果使用时a和b是表达式的话,就会因为运算符顺序问题而出问题。
n 续行符\是否使用正确
n 引号“”是否使用正确
n 代码中编译或调试开关的宏是否正确设置
5、常量
常量方面主要检查的主要问题如下:
n 常量是否书写正确,两种典型错误,一是数字或字母由于键盘失误写错,比如2写成3或1等。另一种是常量里有位顺序,将位顺序搞错了。
n 常量是否使用了宏来进行定义
n 程序中是否存在魔法数字
n 16进制数据是否在前面加上了0x
n 常量是否来自规格
n 不来自规格的常量的值是否合理
6、全局变量与共享变量
全局变量与共享变量需要检查的主要问题如下:
n 全局变量是否必须的,是否可以改成局部变量?
n 是否有多个任务访问共享变量,是否进行了有效的保护?
n 当全局变量只限于本文件内使用时,是否定义成静态的?
n 多个任务读写共享变量时,是否可以将读写操作封装成独立函数,而不是在每个模块里都进行加锁解锁操作
7、静态变量和函数
静态变量和函数检视时主要问题如下:
n 静态变量的使用是否正确
n 每次使用静态变量时是否需要重新初始化
n 对不需要重新初始化的静态变量在多次使用后是否有溢出的问题。
n 文件内部使用的函数是否定义成静态的
8、数据结构
数据结构方面考虑的主要问题如下:
n 数据结构里的成员类型定义是否正确
n 结构体里面变量顺序安排是否合理,数据是否对齐
n 是否存在冗余未用的成员变量。
n 类里面是否有私有变量和私有函数放到了公有的定义里去了
n 是否有多个任务调用了数据结构的操作时,是否存在数据重入问题
9、初始化
初始化考虑的主要问题如下:
n 变量使用前是否需要初始化
n 类的构造函数中是否对需要初始化的成员都进行了初始化(使用成员初始序列进行初始化或在函数体内部进行赋值进行初始化)
n 初始化的值是否书写正确
n 数组的初始化是否正确
n 内存或数组在每次使用前是否需要初始化清零
n 多个变量初始化赋值时是否存在顺序问题
n 静态变量和全局变量的初始化是否存在初始化顺序问题
n 字符串数组是否有不需要初始化清零,而只需操作完后在尾部添加’\0’的情况
10、 字符串
检视字符串时考虑的主要问题如下:
n 字符串是否以’\0’结尾
n 字符串是否会超长
n 字符串使用的空间大小是否存在差1问题
n 使用字符串指针时,指向的位置是否存在差1问题
n 输入的字符串前后有空格TAB键、回车键等特殊字符时,程序中是否将前后这些特殊字符删除掉。
n 字符串指针是否可以为空,为空时会有什么现象?
n 字符串内容为空(即第一个字符为’\0’)时会发生什么现象?
n 字符串中如果有转义字符“\”字符时,是否正确地写成了“\\”
n 字符串中有斜杠‘/’时,是否误写成反斜杠‘\’
n 在对字符串进行拷贝或连接操作时,是否对空间大小进行校验?
n 是否有大小写的问题?
11、 输入校验
输入校验需要检视的主要问题如下:
n 函数参数是否需要进行了校验?
n 从文件读取的数据是否进行了校验?
n 使用全局数据时是否需要进行校验?
n 通信收到的数据是否需要进行校验?
n 从消息中接受到的数据是否需要进行校验?
12、 内存分配和释放
内存分配方面需要检查的有以下几点:
n 分配的大小是否正确,是否分配了过大的内存或者分配的内存大小不足,分配的内存大小是否存在差1错误
n 内存分配是否经过判断或者进行异常处理
n 重新分配一块内存时,是否将原有内存释放
n 分配的内存是否需要初始化清零
n 是否有在大循环中不断分配内存导致可能出现系统内存不足情况
释放方面需要检查的有以下几点:
n 所有的分支路径上是否将分配的内存进行了释放
n 是否将已经释放的内存重复释放
n 释放的是否是空指针
n 是否错误释放了另外一个相似的指针
n 释放多块内存时是否存在释放的先后顺序问题
n 是否将动态库中分配的内存在动态库外部释放掉或者动态库外部分配的内存却在动态库内释放
使用realloc()时要考虑以下几点:
n 新增空间是否需要初始化清零?
n 是否还有指针指向老的内存块,并在realloc()后使用指向老的内存块的指针。
13、 边界条件
凡是牵涉边界条件的地方都需要进行边界检查,以下的一些问题供参考:
n 循环变量上的边界是否正确
n 变量的取值是否有边界条件限制,边界是否给出并书写正确?
n 空间边界,如内存大小,数组大小是否正确,是否存在差1和越界情况?
n 数据结构边界,如链表的头一条记录和最后一条记录等边界情况
n 服务器连接数量最大是多少
n 断掉网线或打印机缺纸时会发生什么?
关于边界的具体情况,请参阅本书第3章的3.6.2节,里面有对整数和字符串边界的详细材料。
14、 计算
计算错误也是程序中经常遇到的一个问题,大部分计算错误可以经过测试发现,但并不是所有的计算错误都可以很容易通过测试来发现,以下的一些问题供检视时参考:
n 计算表达式或公式是否书写正确,需要逐字符地进行确认没有输入错误
n 表达式中运算符顺序是否书写正确,同优先级运算符运算时是否存在自左至右结合或自右至左结合运算结果不同的问题
n 是否需要使用括号来保证运算顺序的正确性和增加程序的可读性
n 表达式中括号过多时,括号书写是否正确
n 是否存在计算溢出情况,如两个整数相乘结果超出整数最大范围等情况
n 截断误差和舍入误差是否会引起问题,误差是否会累积下去导致误差越来越大?
n 是否存在除零问题(即零做分母的问题),或者两个整数相除结果得到零然后再和其他整数相乘。
n 加减号是否写错,这两个符号在键盘相邻位置,很容易造成键盘输入失误导致写错
n 是否可以对算法进行优化提高效率
n 是否存在某个变量会累积增加导致长时间运行后的溢出
n 计算结果是否存在差1错误