跳至内容
Topos Uranos

摘要

本课结束第八单元,也随之结束整门课程。它汇集本单元三节课的收获;重建从模块的接口通向作为方法的调试的概念链;把其中适用于任何语言的内容与 C++ 特有的内容分开;展示无人诊断的单一定义规则违规,并把本课程中的全部未定义行为汇成一份清单,注明每种情形由什么检测;在结业测评中重新考查入门测评的技能;提出六道综合本单元的挑战题;最后给出本课程的结课项目,规格说明精确,附有评分细则,分为四个阶段,每个阶段都有一份经过验证的参考答案。

学习目标

  1. 用头文件、头文件保护和契约把程序组织成多个模块,分离编译,并诊断链接器的消息。
  2. 依据规格说明测试一个模块:编写在检测器下运行的自动化测试程序,并用断言检查不可能发生的情况。
  3. 用方法(复现、精简、提出假设、二分排查)和调试器定位缺陷,并记录调查过程。
  4. 识别本课程展示过的未定义行为及每种情形由什么检测,以及无人诊断的单一定义规则违规。
  5. 把整门课程融入一个项目:对一个管理表格数据的程序进行规格说明、分解、构建、测试、代价度量和调试。

本单元的收获

本单元旨在教会如何组织、验证和调试那些已经无法装进一个文件、也无法装进脑子里的程序。这一目的已经达到,所依托的是导言声明为已知的内容:声明与定义、编译与链接、契约、不变式以及二分查找。本单元开篇的图书目录程序原本装得进一个文件,它把 parseInt 又复制了一遍,并且因为在规格说明写着“两端都包含”的地方写了 <,悄无声息地漏掉了《百年孤独》;在本课最后一节,它被重写为结课项目:四个带契约的模块,一个响应命令的主程序,以及一个包含二十五个用例的测试程序。其中两个用例,“区间,边界”和“区间,单一年份”,在原程序的条件下会失败,因此这个错误不会在无人察觉的情况下重现。

多个文件与自建库

多个文件与自建库一课把一个程序分成负责声明的头文件、负责定义的实现文件和负责使用的 main.cpp。它介绍了头文件保护,用来防止在一个翻译单元内重复包含;单一定义规则,它解释了“multiple definition”和“undefined reference”,并赋予 inline 如今作为许可的含义;用 -c 进行的分离编译,只重新编译那些源文件或头文件发生了变化的目标文件;以及一个由前面各课的函数组成的自建库。

程序测试

程序测试一课教会了:从规格说明推导用例,按区域、边界和特殊情况划分,并用另一条途径计算预期结果;编写只报告失败、以退出码 0 或 1 结束的自动化测试程序;用 assert 检查不可能发生的情况,同时知道 -DNDEBUG 会连同其条件一起把它删掉;用 static_assert 和 constexpr 在编译期测试;以及在检测器(内存错误检测器 AddressSanitizer 和未定义行为检测器 UndefinedBehaviorSanitizer)下运行测试。这一切都基于迪杰斯特拉的告诫:测试能显示错误的存在,却永远不能显示错误的不存在。

程序调试

程序调试一课把调试作为一种实验方法来介绍:复现失效,提出可证伪的假设,用实验在假设之间作出裁决,并对可疑区段进行二分排查。它讲解了这一工具(用 -g 且不开优化来编译,在三种系统上配置 Visual Studio Code,一次真实的 GDB 会话,普通断点和条件断点,单步执行和调用栈),以及比工具更有价值的策略:精简用例,用断言寻找最后一个正确点,从检测器报告中第一个属于自己代码的栈帧入手。