很多人代码一些完,马上开始Make, 殊不知他错过了一次很好的机会。这个机会就是代码自查,它的目的不仅仅是为了发现程序的错误,更是为了认识到自己编程习惯的局限和盲点,从而在下次进行有效地改进。因为,人自己发现自己的错误并修改的过程,是一种自愈和自我免疫的过程。
1. 前提
满足了下面的前提,就可以开始代码自查:
-
- 项目需求已经确定,有可参考和检查的文档,
-
- 主要的功能,已经拆分为各个子模块;
-
- 和其他模块的接口,已经定义好;
-
- 每个模块的代码,已经用代码实现;
当然,如果你模块划分得越细,也可以逐模块开始代码自查。
2. 过程
可以做两轮自己的code review,每轮的侧重点不一样:
2.1 功能自查
第一轮检查,主要侧重于以下的方面的检查
2.1.1 文件级
重点检查:
- 头文件是否能避免重复保护;
- 如果需要支持C++,头文件是保护extern C;
- 头文件中的extern 是否有定义;
- 头文件中的函数是否有定义、定义是否一致;
- 是否遵循最小暴露原则;
- 在全局静态数组的定义中,使用了需要运行时才能确定的变量,导致了编译错误
2.1.2 子模块级
- 子模块内部实现的逻辑是否符合预期;
- 子模块对外的接口函数的参数和返回值是否恰当;
- 是否有之和子模块相关的变量实现成了全局变量;
2.1.2 函数级
- 输入输出参数是否冗余、缺失、类型不对;
- 函数内部是否声明了不用的变量;
- 函数返回和函数声明是否一致;
- 参数检查是否遗漏或者过多;
- 内部循环是否退出条件过若或者过强;
- swtich/case 是否遗漏break、default,或者default不可能执行到
- 申请的内存是否遗漏释放;
- 对申请到的内存的使用是否越界;
- 该函数是否在临界区,是否包含临界区,是否会死锁;
- 功能逻辑是否符合预期
2.1.3 宏定义级
- 是否有些近似的代码反复出现没有用宏替换
- 是否把宏当做函数用了,忽略了宏定义只是简单的完全替换关系
2.1.4 拼写级
- 有一个数据结构中的一个成员,只有类型,没有变量名称;
- 遗漏分号、过多分号;
- 拼写错误,特别是夹杂有大小写的情况:idLsu/idlsu lsfsret/LsfsRet
上面检查过程中,发现立即可以修改的马上修改,比较复杂的可以放到一个TODO list的FIFO里去;
2.2 性能检查
第二轮检查侧重于以下的方面:
2.2.0 算法
针对具体的功能和场景,区分是响应时间有限还是节省资源有限,列出所有可选的算法实现,确定是否当前是最合适的实现;
2.2.1 IO路径
- IO路径上的所有操作是否都是必需在请求返回前执行的,如果不是,可以拆分到后台线程去执行;
- IO路径上是否会全抢锁等待,如果有尽量去掉锁或者减小等待的时机;
2.2.2 并发的粒度
- 是用多进程还是多线程去并行;
- 是否需要绑定处理器核去执行;
2.2.3 锁的类型
- 是否必需,能否用无锁队列替换;
- 是否适用读写锁;
2.2.4 IO合并和放大
- 每次是否有太多的没有改动的数据也落盘了;
- 是否依赖了必需落盘的数据,导致它没及时落盘;
同样,如果上面有问题,也需要添加到一个TODO List里面去。
2.3 完成TODO List
- 根据上面自查完成后生产的TODO list,把里面每一项完成;
- 完成TODO list后,对照自己常犯错误的清单,再次自查,修改完成之后可以交由编译器去编译调试了。
如果此时,你发现编译器也没发现任何错误和警告,恭喜你,已经比较牛了