编程精粹—— Microsoft 编写优质无错 C 程序秘诀 04:对程序进行逐步跟踪

本文主要是介绍编程精粹—— Microsoft 编写优质无错 C 程序秘诀 04:对程序进行逐步跟踪,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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


不记录,等于没读。本文记录书中第四章内容:对程序进行逐步跟踪。


前两章我们讲到了 断言为子系统设防 这两种自动化检查方法,这两种检查措施都很有价值。我们也说过,不要等待错误发生。要“挨门挨户”的搜查错误:在程序中加上能够积极地寻找这种问题的调试代码。
这里隐含着一个条件,你必须知道哪里可能会发生错误,也就是说:事先,你得知道错误“长什么样”。如果你不知道错误是什么,就抓不到这个错误。比如你只在门窗上安装了报警器,那么当小偷从地下室的开口进入,就不会引起警报。
保证家中物品不被偷走的可靠办法是:在小偷有可能光顾的期间内呆在家里。防止错误进入程序的办法也是这样,在 最有可能出现错误 的时候,密切关注。因此,找到错误的最佳方法是使用调试器逐步执行所有新代码。通过逐步执行每条指令,关注数据流,可以快速发现表达式和算法中的问题。专注于数据而不是指令,可以从另一个角度来审视代码。逐步调试代码虽然花时间,但远没有大多数程序员想象的那么耗时。

那么,什么时候是 最有可能出现错误的时候?
答案是:在编写或修改程序的时候,最可能出现错误,我们要认识到这一点的重要性。编写无错代码的最好办法是在编译时对其进行详尽的测试。

我们在第一章中介绍了 黑箱 测试。测试者给程序提供大量的输入,然后检查输出,如果测试者认为相应的输出没有问题,那么则认为程序没有问题。

黑箱测试的缺陷在于,除了提供输入和检查输出之外,测试者再没有别的方法对代码进行测试。给一些输入,得到了正确的输出,并不能保证代码是正确的。就像你问一些问题,倾听对方回答,并不能确定对方是不是疯子,因为你不知道对方脑袋里在想些什么。

上面的这些限制对程序员来说都不是什么难事。程序员可以在代码中设置断点,一步一步跟踪代码的运行,观察输入变为输出的过程。程序员测试程序最好的办法是对代码进行逐条跟踪,对中间的结果进行认真的查看。

不要等到出了错误再对程序进行逐条地跟踪

  • 对正常处理程序逐条跟踪

  • 对错误处理部分逐条跟踪

  • 对程序中每一条可能的路径逐条跟踪(明显的路径:ifswitch,其它路径:&&||?:

习惯于对代码进行逐条跟踪会产生一个有趣的现象,即他们很快就会学会编写较小的容易测试的函数,因为对大函数进行逐条地跟踪非常痛苦。

对代码进行逐条跟踪的真正作用是:可以使我们观察到数据在函数汇中的流动

如果单步执行仍不能得到执行的细节,那么对于关键部分的代码要进行汇编指令级的逐条跟踪

小结

  • 代码不会自己生出错误,错误是程序员的产物,编写新代码或者修改现有代码都可能产生错误。如果想发现代码中的错误,最好的方法是逐条跟踪代码。
  • 逐条跟踪代码并不会花费太多时间,与返工带来的影响相比,这点时间划算的很。
  • 一定要对每一条代码路径进行逐条的跟踪,尤其是错误处理部分。&&||?: 运算符,它们每个都有两条代码路径需要测试。
  • 当需要汇编级别的逐条跟踪时,不要回避。

题外话

本书第一稿出版于 1993 年,当时软件工程领域还没有 敏捷开发测试驱动开发 等概念(这些概念大范围被认可是在 2000 年左右)。作者写这本书的时候遵循一个理念,那就是只介绍那些并不广为人知,没有被广泛实践,也没有被广泛使用的概念和开发哲学。那个时候作者已经认识到测试的重要性,提倡编写较小的容易测试的函数。在当今 (2024 年) 来看,这是再平常不过的事情,甚至,对于“防止错误进入程序”,我们也有了更号的方法,那就是 测试驱动编程 。我在之前的博文《C 嵌入式系统设计模式 01:软件开发概述》中描述过自己对测试驱动开发的见解,现摘录如下:

开发无缺陷软件的最新技术是一种称为 测试驱动开发 (TDD) 的敏捷实践。

无缺陷的软件 (defect-free software)表示该软件没有任何错误、缺陷或问题,能够按照预期的功能和需求正常运行。

“开发无缺陷的软件?这怎么可能!”换做以前的我,不假思索地就会如此断言。但那时的我,从没有想过,我给出的这个结论,依据是什么。事实上,我没有依据,我连脑子都没动过一下,就草率地下了这样的判断。

而现在,我开始反思这个问题,一方面是因为我的思维方式发生了转变,不再轻易地给出片面的结论;另一方面,是因为我尝试了测试驱动开发。尽管我依然认为开发完全无缺陷的软件是一个难以企及的目标,但测试驱动开发确实让我看到了这一目标的可能性。它让我对我的软件质量越来越有信心。

在尝试测试驱动开发的早期,最难以理解、最难以坚持的可能是被称为 Bob Martin 的测试驱动 3 条原则:

  1. 不允许编写任何产品代码,除非它可以让失败的单元测试通过。
  2. 不允许编写任何足以导致失败的更多的单元测试,编译失败也算失败。
  3. 不允许编写任何足以让单元测试通过的更多的产品代码
  1. You are not allowed to write any production code unless it is to make a failing unit test pass.
  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

首先,你需要先编写单元测试,这就是为什么叫做测试驱动的原因之一。但是根据规则 2,你不能写太多的单元测试,一旦单元测试代码无法编译或者断言失败,就必须编写产品代码。但是根据规则 3,你只能编写恰好使单元测试编译通过或者断言通过的产品代码,而不能再多!

当我年轻时,我曾尝试测试驱动编程,先后尝试过两次,都因为第 2 和第 3 条原则而放弃,我觉得它很愚蠢!如果你没有尝试过测试驱动开发,也许无法体会到我当时的感受。没有关系,我只是想分享一下我曾经的态度和后来的变化。

随着年龄的增长,我的思维方式发生了转变,我收起了排斥和轻视的态度,放下成见,严格遵守测试驱动原则来编写一个程序。然后,我被测试驱动开发迷住了。

我开始认识到,那些我曾经认为愚蠢的规则,实际上并不愚蠢,而是构建稳健软件必不可少的基础,坚持这样的原则,能确保我的代码在全面且细致的测试用例中得到验证,换句话说,能产生彻底测试过的产品代码。

彻底测试,是每一个软件开发者的理想目标,只有达到这一条件,我们才有可能触及编写无缺陷软件的可能。很多时候,我们都在竭尽全力追求彻底测试,但受限于大脑的逻辑和记忆能力,总是会遗漏某些测试路径。然而,遵守测试驱动开发的 3 条原则,可以帮助我们系统化地进行测试,确保每一个细节都得到了验证。只是遵守 3 条原则,就能实现如此的效果,这真是太划算了。


题外话 2

如果你对测试驱动感兴趣,想详细了解,推荐阅读《测试驱动的嵌入式 C 语言开发》。这本书的原作者和译者,给予他们再多的赞美都不为过。






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

这篇关于编程精粹—— Microsoft 编写优质无错 C 程序秘诀 04:对程序进行逐步跟踪的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1072440

相关文章

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下

Nginx中配置使用非默认80端口进行服务的完整指南

《Nginx中配置使用非默认80端口进行服务的完整指南》在实际生产环境中,我们经常需要将Nginx配置在其他端口上运行,本文将详细介绍如何在Nginx中配置使用非默认端口进行服务,希望对大家有所帮助... 目录一、为什么需要使用非默认端口二、配置Nginx使用非默认端口的基本方法2.1 修改listen指令

Python异步编程之await与asyncio基本用法详解

《Python异步编程之await与asyncio基本用法详解》在Python中,await和asyncio是异步编程的核心工具,用于高效处理I/O密集型任务(如网络请求、文件读写、数据库操作等),接... 目录一、核心概念二、使用场景三、基本用法1. 定义协程2. 运行协程3. 并发执行多个任务四、关键

AOP编程的基本概念与idea编辑器的配合体验过程

《AOP编程的基本概念与idea编辑器的配合体验过程》文章简要介绍了AOP基础概念,包括Before/Around通知、PointCut切入点、Advice通知体、JoinPoint连接点等,说明它们... 目录BeforeAroundAdvise — 通知PointCut — 切入点Acpect — 切面

MySQL按时间维度对亿级数据表进行平滑分表

《MySQL按时间维度对亿级数据表进行平滑分表》本文将以一个真实的4亿数据表分表案例为基础,详细介绍如何在不影响线上业务的情况下,完成按时间维度分表的完整过程,感兴趣的小伙伴可以了解一下... 目录引言一、为什么我们需要分表1.1 单表数据量过大的问题1.2 分表方案选型二、分表前的准备工作2.1 数据评估

基于Python编写自动化邮件发送程序(进阶版)

《基于Python编写自动化邮件发送程序(进阶版)》在数字化时代,自动化邮件发送功能已成为企业和个人提升工作效率的重要工具,本文将使用Python编写一个简单的自动化邮件发送程序,希望对大家有所帮助... 目录理解SMTP协议基础配置开发环境构建邮件发送函数核心逻辑实现完整发送流程添加附件支持功能实现htm

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

MySQL进行分片合并的实现步骤

《MySQL进行分片合并的实现步骤》分片合并是指在分布式数据库系统中,将不同分片上的查询结果进行整合,以获得完整的查询结果,下面就来具体介绍一下,感兴趣的可以了解一下... 目录环境准备项目依赖数据源配置分片上下文分片查询和合并代码实现1. 查询单条记录2. 跨分片查询和合并测试结论分片合并(Shardin