首页  编辑  

附錄A 程式寫作檢查表

Tags: /超级猛料/Book.凤毛麟角(电子书籍片断)/完美程式設計指南/   Date Created:

附錄A 程式寫作檢查表

为了提醒你本书的重点,我列出了对程序的几种检查要项,你可以在底下这些程序开发的主要阶段中检查一下:设计,实作,除错支持,测试,与除错。我没列出在程序开发的整个过程中应该要作的那些项目-我假设你已经打开了编译器提供的警告选项,维护了一份除错版本的程序,有错误回报就立刻修正问题等等。

要有效利用这些检查表,你得在每次在项目中写上新程序时检查表上的项目。从实际的观点来说, " 每次 " 其实是说 " 下几次 " 你写新程序之后。这么做了以后,你应该就能培养出一种能够察觉程序有没违反那些零错误程序写作原则的第六感了。

设计阶段

当你考虑一个功能的不同设计方式时,不要光看一个设计方式是不是最快或最小的。想想实作上、维护上跟使用程序上会出现的风险,对每一种可能的设计方式,检查一下这些项目:

        设计中有未定义或无意义的行为吗?会不会出现随机或罕见的行为?设计中有不必要的弹性或不必要的假设吗?设计上有没有反复无常的地方?

        有资料透过静态或整体缓冲区传送吗?有任何函式依赖其它函式的内部运作机制吗?有函式处理超过一件是的吗?

        你的设计必须处理特例吗?你有将处理特例的程序分离出来吗?

        检查函式的输出入资料。每个输入跟输出状态只表示一种资料,或是有包括错误状态跟其它难以注意到的情形?健全的接口设计会让每个输出入资料对程序员都很清楚明朗而不会遗漏任何如 malloc 会传回 NULL 的错误状态或 realloc 会在 size 参数为 0 时将内存释放掉之类的重要细节。

        预估一下程序员们会如何使用你的函式。那些 " 明显 " 会被用到的使用方式动作正常吗?回忆一下 realloc 造成内存块遗失的例子。

        维护上,你的函式在呼叫端可读吗?每个函式都应该只处理一件事,参数应该让呼叫者了解其意义。 TRUE FALSE 参数的出现经常代表着函式处理超过一件事,或函式本身设计不良。

        你的函式会传回错误值吗?能不能重新定义那些函式来消除错误状态?记住,函式传回错误时,在每个呼叫点,错误都一定要会被处理到-不然就会出错。

        最重要的,你可以自动彻底的使用单元测试来核对设计的正确性吗?如果不能,你应该考虑采用另一种可以测试的设计方式。

实作阶段

在实作了设计后,你应该检查这些项目,确定你的实作健全而防错:

        比较实作与设计之间的差别,你精确的实作了设计的东西吗?小心,设计与实作之间的小差异都可能带来问题。记住先前那个 UnsToStr 坏掉的例子,就只是因为设计上要求使用无号整数而实作上写成了非负整数。

        程序中有不必要的假设吗?在可以使用具可移植性的资料型态时候,你有没用到不具移植性的资料型态呢?实作时有没有什么草率处理的地方?

        检查程序中的表达式,有溢位或借位的情形吗?变量也有这些情形吗?

        你有用到巢状的 ?: 运算子或其它危险的 C 语言惯用语法,如位位移替代除法吗?你有无缘由的混用位运算子跟数值运算子吗?你有用到不可靠的 C 语言惯用语法吗?像在数值运算中使用逻辑表达式产生 0/1 的值?将危险的写法用差不多但较安全的方式重写吧。

        好好检查一下程序,你有用到团队中一般程度的程序员看不懂得 C 语言语法吗?考虑用主流的 C 语言用法重写一遍吧。

        你的每个函式大概都只处理一件事,不过那件工作只用了一条执行路径去处理,还是用了不同的执行路线去处理不同的特例呢?如果完成一件事的程序是透过处理特例的程序代码完成的,你可以使用不同的算法来消除这些特例吗?试着消除程序中的每个 if 叙述。 ?

        你有呼叫任何会传回错误的函式吗?你可以修改设计,让那样的用法变得不必要而可以消除错误处理的需要吗?

        你有动到你无权处理的内存吗?特别是那些动到已释放内存的动作?你有读取其它子系统拥有的内部数据结构吗?

        如果你的函式使用指向输入或输出的指针,你的程序有限制对这些指针的存取只动到输出入所需的内存吗?如果没有,你的程序可能对呼叫者配置出来的内存块大小作了错误的假设。

加上除错支持阶段

加上除错检查与其它除错码到实作程序中,可以减少找出程序中隐藏的错误所需的时间。底下的检查表列出了值得让你考虑使用的除错检查根除错码:

        你有用除错检查来核对函式参数吗?如果你发现自己由于缺乏足够信息而没办法核对某一特定参数,提供更多除错信息会有用处吗?回忆一下除错专用的 sizeofBlock 函式对于检查已配置内存块的指针多有用处。

        你用除错检查来核对假设条件或找寻未定义行为的违规使用吗?对未定义行为的检查避免程序员们滥用实作上未指定好的地方。

        防御性程序设计方式在内部问题出现时 " 修正 " 错误,让这些错误找不出来。你有用除错检查在除错版的程序中找寻这些问题吗?(当然,这里对防御性程序设计方式的看法不能用在修正使用者的不良输入所采取的防御措施上。)

        你写的除错检查够清楚吗?如果不,确定加上解释那些检查用途的注释。不幸的,当程序员们碰到一个除错检查失败时而不了解那个检查的用途时,它们常会假设那个除错检查无用而拿掉它。写好注释可以帮你保留下必要的除错检查。

        如果你的程序配置了内存 , 你有用除错专用码来将未初始化的内容设定成某个已知的明显垃圾值吗?将内存设定成固定内容将让找寻跟可靠的重现使用未初始化内存的问题更容易些。

        你的程序在释放内存前会先把里头的内容填成不合法的垃圾值吗?

        你有算法重要到让你该用另一套不同,但是同等功效的除错用算法来进行结果核对吗?

        有什么除错检查可以尽可能在你的程序开头早期就找出错误来吗?特别是在程序开头检查那些资料表格?

测试阶段

程序员们测试自己的程序是件十分重要的事情,即使那会让程序的推出延期。这一段落的问题说明了你所应该采取最有用处的测试步骤:

        程序编译时会不会产生任何包括所有选择性编译警告的警告讯息?如果你用 lint, 或是类似的诊断工具,你的程序能通过所有的测试吗?程序能通过你的单元测试吗?如果你跳过了其中任何一步,你就正丧失着易于找出错误的机会。

        你有在除错程序中,不只注意程序,还留意资料流的去向的,逐步追踪所有新的程序代码吗?这大概是找出你写的程序中的错误的最好办法了。

        你有 " 翻动 " 任何程序吗?如果有,你有测试过更动过的部分吗?你有在除错程序中逐步追踪过改过的地方吗?记住,更动过的程序实际上跟必须彻底测试过的新程序一样。

        你应该替新程序设计单元测试吗?

除错阶段

在每次找寻一个回报给你的错误时,你应该检查底下的问题:

        你找得出那个问题吗?如果不,记住,一个错误不会自己不见;问题一定是隐藏起来了,或是已经被修好了。要找出是哪一种情形,你应该检查被回报有问题的那个程序版本。

        你找到了造成错误的真正根源,还是只找到了问题带来的症状?确实找出问题的根源来。 ?

        怎样自动找出同样的错误?除错检查有用吗?除错程序代码呢?改变你的程序写作方式或写作过程有用吗?