编程精粹—— Microsoft 编写优质无错 C 程序秘诀 09:编码检查表

这是一本老书,作者 Steve Maguire 在微软工作期间写了这本书,英文版于 1993 年发布。2013 年推出了 20 周年纪念第二版。我们看到的标题是中译版名字,英文版的名字是《Writing Clean Code ─── Microsoft’s Techniques for Developing》,这本书主要讨论如何编写健壮、高质量的代码。作者在书中分享了许多实际编程的技巧和经验,旨在帮助开发人员避免常见的编程错误,提高代码的可靠性和可维护性。


不记录,等于没读。本文记录书中附录 A:编码检查表。


这里给出的问题列表,总结了书中的所有观点。

读者需要持续的建立自己的准则列表,并决不允许同样的错误出现两次。每当在项目中发现 BUG 后,都要问问自己:

如何检测出这个 BUG?

如何预防这个 BUG?

然后,将得到的启示加入自己的准则列表中去。


为了提醒你回顾本书最重要的知识点,我创建了几个核查表。你可以在自己的开发中回顾这些知识点:设计编码实现为调试做准备测试在线调试。我假设你已经满足以下条件:

  1. 使用优秀的编译器,开启所有警告输出;
  2. 维护一个 DEBUG 版本;
  3. 立刻修复 BUG;

为了有效的使用核查表,在每次向项目新增代码时你都要检查它们。实际上,“每次”代表的是“接下来的几次”新增代码。再之后,你应该对背离、破坏准则的代码有一种第六感,也就是能闻得到坏代码的味道。

设计

  • 设计包含未定义或无意义的行为吗?那么随机或罕见的行为呢?

    设计是否允许不必要的灵活性或基于不必要的假设吗?

    设计中有未经仔细思考的细节吗?

  • 设计中用到静态或全局缓冲区传递数据吗?

    函数依赖其它函数的内部实现细节吗?

  • 设计需要处理任何特殊情况吗?是否隔离了这些特殊情况代码?

  • 查看函数的输入和输出:

    • 每个输入和输出都只表示一种类型的数据吗?
    • 会包含错误值或其它难以注意的值吗?

    健壮的接口确保每个输入和输出都是清晰的,以便程序员不能忽视重要的细节。malloc 函数返回 NULL 错误值、realloc 可以传递大小为0的参数值,这些都不是健壮的接口。

  • 预判程序员如何调用你的函数。“显而易见”的方法是否正确?

  • 站在维护者的角度,你的函数在调用点具有可读性吗?函数的参数能清晰的表明调用的意图吗?

    一个函数只做一件事吗?如果函数接受 TRUEFALSE 参数,常常表明函数做了多件事情,这可不是好设计。

  • 你的函数返回错误值吗?重新定义这些函数可以消除错误条件吗?要知道,如果函数返回错误,必须要在每个调用点处理错误。

  • 最重要的是,你使用单元测试自动地、彻底地验证设计吗?如果没有,你必须考虑可测试的替代方案。

编码实现

  • 对比设计和编码实现:你是否准确地实现了设计?设计和实现之间的微小差异可能会使你陷入困境。

  • 代码中是否有不必要的假设?是否使用了不可移植的数据类型?

  • 检查你代码中的表达式。它们是否有上溢或下溢?变量呢?

  • 是否使用危险的C语言惯用语,比如嵌套的 ?: 操作符或者用移位实现除法?

    代码中混用位操作和算术操作,并且没有充分理由吗

    你有使用危险的 C 惯用语吗?比如在算术环境中使用逻辑表达式的结果 0 和 1。

  • 仔细查看你的代码。是有团队中一般程序员都无法理解的炫技型 C 代码?考虑使用主流 C 重写代码。

  • 可能一个函数只做了一件事,但是这件事是用单一路径完成的吗?是否使用不同的代码实现了不同的特殊情况?要尽量消除代码中的每个 if 语句。

  • 是否调用返回错误的函数?你能修改设计不再调用吗?这样就可以消除错误处理代码。

  • 是否引用了无权访问的内存?特别是,是否引用了已经释放的内存?

    是否读取过其它子系统的私有数据结构?

  • 如果你的函数使用指向输入或输出的指针,你的代码是否将其引用限制为仅保留这些输入和输出所需的内存?如果没有,你的代码可能基于了某些不正确的假设,比如假设调用者为数据分配了能保存最大数值的内存。

增加调试支持

发现代码中的隐藏 BUG 并不容易,向项目中增加断言和其它调试代码可以减少发现隐藏 BUG 所需的时间。

  • 是否使用断言验证函数参数?当因为没有足够的信息,你不能验证某个特殊参数时,那么维护额外的调试信息会有帮助吗?回想一下仅用于调试的 sizeofBlock 函数,对于验证指向已分配内存的指针所起到的帮助。
  • 是否使用断言验证你的假设,或检测未定义行为的非法用法。
  • 防御性编程在“修复”内部 BUG 的同时,是否也掩盖了错误?你是否在 DEBUG 版本中使用断言检查这些 BUG。对于对外接口(比如用户输入数据),这条并不适用,对外部接口的防御,应该是防弹的。
  • 断言清晰吗?如果不清晰,给断言增加详细注释。当断言捕获一个错误时,如果程序员看不懂断言的目的,他常常会假定这个断言是无效的,然后删除掉断言。注释可以保护你的断言。
  • 如果你的代码使用动态分配内存,你会使用仅用于调试的代码将未初始化内容设置为已知的特殊值吗?将内存设置为特殊值,可以更容易的发现和重现使用未初始化内存的 BUG。
  • 如果你的代码释放内存,在释放前先破坏内存内容了吗?
  • 你的代码有重要的算法吗?你会再写一个完全不同的、只用于调试的算法去检查主算法吗?
  • 为了尽早检测出 BUG,是否可以在程序启动时进行调试检查?比如对数据表的检测。

测试

程序员测试自己的代码至关重要,即使为此推迟交付也在所不惜。

  • 代码编译后是否不生成任何警告信息?如果你使用 lint 或者类似诊断工具,你的代码能通过所有测试吗?你的代码能通过单元测试吗?如果你跳过这些步骤中的任何一个,你就失去一个更早检测到 BUG 的机会。
  • 你是否使用调试器对新代码进行逐条跟踪,不仅专注于代码,还关注流经该代码的数据?这可能是在代码中捕获 BUG 的最好方法。
  • 你是否“清理”任何代码?如果是,你对此进行测试了吗?是否对这些代码进行逐条跟踪?要记住,清理代码和写新代码一样必须进行彻底的测试。
  • 为新代码编写单元测试了吗?

调试

  • 你能重现测试组报告的 BUG 吗?如果不能,记住 BUG 不会自己修复。要么隐藏起来了,要么已经修复了。为了确定是那种情况,你需要使用测出BUG的程序版本进行测试。
  • 你发现 BUG 的真正原因了吗?还是仅仅发现 BUG 的表面症状?确保跟踪到 BUG 的真正原因。
  • 这个 BUG 如何预防呢?提出一个可以预防此 BUG的 精确指南。
  • 这个 BUG 如何自动检测呢?使用断言能捕获它吗?使用某些调试代码呢?编码习惯和流程的哪些变化会对此有帮助呢?






每一份打赏,都是对创作者劳动的肯定与回报。
千金难买知识,但可以买好多奶粉

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值