一篇blog,显得略长,本文对应第5-8章,第1-4章请参考Part 1。
编程技术
代码评审、手工执行代码
要点:
- 检查代码里有没有常见错误;
- 手工执行代码,以验证其是否正确;
- 通过绘图来解析复杂数据结构;
- 在大型纸张和白板上,通过各种颜色的图示来演算复杂问题;
- 在绘图的过程中操作实物,以便更深地投入到正在研究的问题中。
审读代码并与同事讨论
Rubber Duck Technique:橡皮鸭技术,把代码的工作原理解释给别人(或自己)听。
代码评审:Gerrit、GitHub;要注意礼节。
角色演练:可用于通信协议(Bob and Alice游戏)、人机交互以及工作流等场景。
给软件添加调试机制
调试模式是一套功能或机制,需要有相应的代码来支撑。使软件进入调试模式的几个例子:
- 通过编译软件时所用的选项来决定软件是否进入调试模式。如编写C/C++代码时,就可定义DEBUG常量,并根据该常量的值来进行决策;
- 通过命令行选项来决定软件是否以调试模式运行。如Unix系统的sshd就提供
-d
选项,其他很多程序也提供有类似的选项; - 给进程发送信号(signal),令其进入调试模式。旧版BIND域名服务器就是这么做的;
- 通过命令打开调试模式(这种命令可能不会写在开发文档中)。如通过某种很少见的按键组合来开启。在某些版本的Android系统上,连续单击七次build number(版本号)菜单,就可以开启USB调试模式。
调试模式,可让软件进入特定的状态。
libmicrohttpd:小型嵌入式HTTP服务器程序。
可通过调试命令来模拟:网络随机丢包、数据无法写入磁盘、无线信号衰减、实时时钟功能错乱、智能卡读卡器配置不当等情况。
要点:
- 给程序添加一个选项,令其能够进入调试模式;
- 添加相应的调试命令,使调试者能够操控程序的状态、记录其所执行的操作、降低其在运行时的复杂程序、迅速在其用户界面之间跳转,并展示复杂的数据结构;
- 添加命令行、Web以及串行连接等界面及接口,以便对嵌入式设备及服务器进行调试;
- 在调试模式下通过一些命令来模拟那些与外部因素有关的错误。
日志
日志记录机制:Unix的syslog库、Apple的ASL系统日志机制、Windows的ReportEvent API、适用于C++的Boost.Log v2、在node.js环境中,采用lumberjack协议来记录日志,可考虑选用Bunyan及Winston包、在Unix shell命令行界面,可通过logger命令把消息记入日志、在Unix内核(及设备驱动)里面,应该调用printk函数来记录消息。
要点:
- 通过日志记录语句来搭建一套可以持久维护的基础调试平台;
- 应该用现有的日志框架来记录,而不要去重新做一套框架;
- 根据你所关注的话题以及你想要记录的细节来对日志框架进行配置。
单元测试
引入单元测试,UT,纳入VCS,对已有项目代码编写UT时可能需要重构。
推荐阅读书籍:Working Effectivelywith Legacy Code
要点:
- 通过单元测试来检查可疑的例程,以便发现其中的错误;
- 为了提升测试效率,我们要选用合适的单元测试框架、要重构产品代码,使其便于接受测试,并且要使测试的执行得以自动化。
断言
涌现:有些错误并不是单独由某一部分代码所导致,而是在多个代码块相互集成的过程中涌现出来的。
三个条件:
- 前置条件:precondition,必须满足这些条件,算法才能够见效;
- 不变条件:invariant,算法在处理过程中,会令已经处理好的那部分数据满足这些条件;
- 后置条件:postcondition,当算法根据规范把数据处理好之后,这些条件必然成立。
用于排解各种问题的断言的用法:
- 在程序的开头放置断言,以验证CPU架构属性(例如其整数型数据的尺寸)是否符合要求;
- 在例程的入口点放置断言,以验证传入的参数是否具备正确的类型(如果你所使用的编程语言不对参数类型进行检查),并确保它们具备有效(如