15、 类型转换
类型转换的检查有以下问题供参考:
n 类型转换是否采用安全的转换机制
n 当采用强制转换时是否会出问题
n signed 和unsigned转换是否存在问题
n 转换前是否进行安全校验
n 是否将小空间的类型转换成了大空间的类型
n 类型转换是否会造成截断、溢出或越界
16、 指针使用
指针在C/C++中是使用最广泛的一种语法,这也是C/C++有别于JAVA,fortran,basic等语言的地方,指针使得语言的功能强大起来,但也给程序质量带来了很大麻烦,使用指针时是极易出错的,可以说C/C++代码中的缺陷大部分都与指针有关,下面给出检视指针的一些问题参考:
n 指针是否初始化
n 指针类型定义是否正确
n 使用前是否申请了内存
n 引用是否正确,是否引用了释放掉的空间
n 指向的空间是否正确
n 是否存在使用野指针现象
n 释放后再使用时是否需要重新初始化
n 是否使用了空指针,函数指针是否为空就被调用
n 指针是否需要校验
n 指针进行类型转换时是否会引起问题
n 指针地址运算是否有误,在地址相加时是否考虑了相加的数字要乘以指针类型所占空间的大小。比如int *p; p+1相比p的大小不是大于1,而是大于一个整数所占空间的字节数。
17、 数组使用
数组的使用也是很容易出错的一种,不幸的是现在还没有足够好的方法能保证数组越界一类的问题得到完美的解决,所以通过对数组的检视来保证质量就很重要了,下面给出检视数组的一些建议:
n 类型是否正确
n 多维数组是否数据存放顺序正确
n 数组使用时是否会越界,空间大小是否存在差1错误
n 作用域是否正确
n 数组大小是否太大导致浪费
18、 函数
函数方面的一些检视建议如下:
n 函数调用的参数传递是否正确,
n 是否有形参和实参使用错误的问题,
n 调用函数前是否需要校验,
n 函数的返回值和输出是否需要校验,
n 调用的函数是否对全局数据产生影响
n 函数功能是否单一,是否在函数里处理了多个不同的功能
n 函数参数是否需要定义为const
n 回调函数原型是否和定义一致
n 函数是否过长(一般以不超过200行为宜)
19、 系统和标准库调用
调用系统函数和库函数时,以下一些检视建议供参考:
n 系统调用是否正确,调用参数设置是否正确?
n 是否按照标准文档中的要求和注意事项进行了调用
n 对于存在BUG的系统函数是否采取了规避措施进行调用
n 对调用系统函数是否需要在调用前进行了输入校验?
n 调用后是否需要对输出进行校验?
20、 规范性
规范性方面的一些检视建议如下:
n 是否符合内部的编码规范
n 是否和业界的编码规范兼容
n 注释格式规范是否符合要求
n 代码修改时的注释是否记录了时间和修改者信息以便于跟踪
n 变量命名是否易于理解
n 多个变量名是否容易造成混淆?是否有多个命名相似的变量在一起?
n 是否有全局范围内变量和局部范围内变量重名情况
21、 冗余代码
冗余代码在程序中虽然不直接影响质量,但会影响程序的可读性,给后续维护增加困难,因此程序中的冗余的代码最好都删除掉,检查冗余代码时,以下建议供参考:
n 代码中是否存在无用的调试和测试代码
n 是否存在废弃不用的函数代码
n 是否存在注释掉的一些垃圾代码,不仅要检查使用注释符号/*…*/ 和//注释掉的代码,还要检查使用宏定义注释掉的代码,如#if 0注释掉或 #ifdefined (__MACRO__) 之类的宏定义的注释掉的代码
22、 判断循环条件
在程序中的判断和循环条件中,也存在着一些有时通过测试难以发现的问题,主要的检视建议如下:
n 是否将>,<两个符号写错,这两个符号在键盘相邻位置,很容易造成键盘输入失误
n 逻辑运算符是否正确,如| 和||,&和&&运算符是否搞混淆掉或键盘失误写错,
n 逻辑等号==是否误写成等号=
n 运算符顺序是否正确,运算符| 、&,||、&&,=、== 的运算顺序需要特别注意
n 循环判断中的表达式是否正确地使用了括号将运算顺序区分开,并增加可读性
n 表达式运算是否存在逻辑上的错误
n 对浮点数是否误用了精确相等进行比较
n 循环变量是否进行了初始化
n 循环的中止条件是否在某些情况下无法到达而造成死循环
n 循环的边界上是否会造成问题
n 判断条件是否会恒真或恒假
23、 注释文字
注释和文字方面的一些检视建议如下:
n 代码中的注释是否达到一定比例,一般要求20~30%左右的注释,即注释行占整个代码行的比例要达到20~30%左右。当然根据不同的项目类型和编码风格,注释率要求会有所不同
n 是否要求按一定的格式进行注释,有些工具可以将代码中的注释导出来形成文档,比如Doc++工具就可以将符合一定注释规范的注释导出来形成文档
n 函数头的注释检查,函数内容描述是否足够帮助理解函数的行为,参数和返回值描述是否足够帮助调用函数的人如何使用函数,参数的范围是否进行了描述
n 程序中的处理是否和注释中的描述一致
n 函数中的关键地方是否都进行注释,特别是一些难于理解的地方是否有注释
n 修改的地方是否进行了注释说明修改原因和记录修改的时间和修改人
n 程序中的信息文字是否都集中放在一个地方,便于本地化。
24、 资源释放
资源释放方面的一些检视建议如下:
n 所有的资源是否都进行了释放
n 释放前是否要进行合法性检查以避免重复释放或释放掉还未分配的资源
n 要检查是否存在某条路径遗漏了释放
n 打开文件是否关闭了,信号量是否释放,句柄是否关闭,锁资源是否释放,是否存在死锁问题
n 全局的资源是否存在随时间累积增加不减少的问题?
n 其他各种资源如网络socket等是否在各条对应路径上进行了关闭
n 类的析构函数中是否对类中需要释放的成员进行了释放
n 是否存在在一个模块中分配却在另外一个模块释放现象
25、 特殊的语法规则
C++中有许多的语法规则需要注意,稍有不慎就会造成问题,以下几点建议供参考:
n 类中是否需要拷贝构造函数
n 函数重载时是否会导致调用到错误的成员函数
n 是否更多的用包含来替代继承
n 基类中的析构函数定义成虚函数了吗
n 静态变量的初始化和使用是否正确
n 重载new和delete时,是否将原型定义置于所有全局变量和相应头文件之前
n 构造函数中调用虚函数时是否考虑了虚机制不起作用的问题,调用的只是本地版本
n 使用setjmp()和longjmp()时是否考虑了析构函数不被调用的问题
n 异常处理里是否调用了被进行了异常处理的代码
26、 可移植性
以下检查建议供参考:
n 与系统相关的调用是否都被封装在专门的模块里
n 非系统功能封装模块中是否调用了某个系统特有的API或函数
n 是否兼容老的版本
n 程序中的变量或数据是否存在大小字节顺序问题
n 结构体数据对齐格式是否兼容各个系统
n 是否使用了行内汇编代码
27、 网络功能
以下检查建议供参考
n 连接的IP地址、端口参数等是否进行网络字节顺序转换
n 收发数据是否都进行了正确的数据字节顺序转换,需要将收到的数据从网络字节序转成主机字节序,而将发送的数据在发送前先从主机字节序转换成网络字节序进行处理
n 网络通信时的连接、收发等操作是否进行了超时处理
n 对收发的网络数据是否进行了大小限制
n 接受数据的缓冲区大小是否足够,接受长度参数是否写错,是否存在差1错误
n 发送数据的大小参数设置是否和实际发送数据大小一样
n 收发数据前是否判断了可以收发数据
n 对网络连接是否需要进行适当的限制,有些服务器软件会限制一个客户只能建立一个连接,防止某个用户占用过多资源而影响其他用户的网络速度
n 网络操作失败时的处理是否正确
n 当处理完一个连接中的所有请求时,是否在所有相应路径上将连接关闭掉了
n 通信是阻塞方式的还是异步方式的?如果是阻塞方式是否会导致程序一直等待
以上列出的代码检视要点只是部分建议,并非完整的内容,里面的内容细节由于牵涉到C/C++编程和软件设计等方面的知识,不属于本书的范畴,所以没有进行详细讲解。事实上要做一个完整的检查列表是一个不小的工作量,可能一本书也不一定能够将所有要检查的内容讲清楚,涉及的知识不仅限于C/C++语言方面的知识,还包括安全性编程、防御性编程、软件设计、各种专业知识、业务知识等方面的内容。因此要提高检视水平的话还需要多加强专业知识和业务知识的学习。
另外一个要说明的问题是,不要期望通过检视能发现绝大部分的缺陷,事实上检视发现的问题数量并不比测试发现的多,但是检视的好处在于有很多通过检视发现的问题是无法通过测试来发现或很难通过测试来发现的,并且有些问题通过检视来发现的代价远低于测试。