章节目录

在问题变成问题前进行避免

本节阅读量:

在程序运行时可能会发生语义错误,而这些错误可能会立即被注意到,也可能不会。在新引入的代码或环境变化导致问题表现为程序故障之前,错误可能在代码中潜伏很长时间而未被检测到。错误在被发现之前在代码库中停留的时间越长,就越有可能变得难以查找,原本可能很容易修复的问题也会变成一场耗费时间和精力的调试冒险。

那我们该怎么办呢?


不要犯错误

嗯,最好的做法是一开始就不要犯错误。下面是一个不完整的清单,可以帮助您避免出错:

  1. 遵循最佳实践。
  2. 不要在疲劳或沮丧的时候编程。
  3. 了解语言中常见的陷阱(也就是我们在教程中警告您不要做的那些事情)。
  4. 不要让函数太长。
  5. 尽可能使用标准库而不是编写自己的代码。
  6. 注释代码。
  7. 从简单的解决方案开始,然后逐步增加复杂性。
  8. 避免过于巧妙或不直观的解决方案。
  9. 优先考虑可维护性,而不是性能。

重构代码

当您向程序中添加新功能(“行为更改”)时,您会发现您的一些函数的长度在增长。随着函数变得越来越长,它们变得越来越复杂,也越来越难理解。

解决此问题的一种方法是将单个长函数分解为多个较短的函数。这种在不改变代码行为的情况下对代码进行结构更改的过程(通常是为了使程序更具组织性、模块化或性能)称为重构。

那么对于一个函数来说,多长算太长呢?占满一个垂直屏幕的函数通常被认为太长了——如果必须滚动才能读完整个函数,那么函数的可理解性会显著下降。理想情况下,函数应该少于十行。少于五行则更好。

请记住,这里的目标是最大限度地提高代码的可理解性和可维护性,而不是最小化函数长度——为了节省一两行而放弃最佳实践或使用晦涩的编码技巧,对代码没有任何好处。


防御性编程简介

错误不仅可能是您自己造成的,还可能是由于用户以您预料之外的方式使用程序而引发的。例如,如果您要求用户输入一个整数,而他们却输入了一个字母,那么您的程序会如何表现呢?除非您预料到了这种情况并添加了相应的错误处理,否则结果肯定不太对劲。

防御性编程是一种编程实践,程序员试图预见最终用户或其他使用该代码的开发人员(包括程序员自己)可能滥用软件的所有方式。这些误用通常可以被检测到,然后加以缓解(例如,要求输入错误的用户重新输入)。

我们将在以后的课程中探索与错误处理相关的主题。


快速查找错误

由于在大型程序中完全不出错是很困难的,因此下一个好的策略是尽快捕获您所犯的错误。

做到这一点的最好方法是每次只编写一小部分程序,然后测试代码并确保其正常工作。

不过,我们还可以使用其他一些技术来辅助。


测试函数

帮助发现程序问题的一种常见方法是编写测试函数来”检验”您编写的代码。下面是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>

int add(int x, int y)
{
	return x + y;
}

void testadd()
{
	std::cout << "This function should print: 2 0 0 -2\n";
	std::cout << add(1, 1) << ' ';
	std::cout << add(-1, 1) << ' ';
	std::cout << add(1, -1) << ' ';
	std::cout << add(-1, -1) << ' ';
}

int main()
{
	testadd();

	return 0;
}

该函数通过使用不同的值调用 add() 函数来对其进行测试。如果所有的结果都符合我们的期望,那么我们可以合理地相信该函数工作正常。更好的是,我们可以保留这个测试函数,在修改 add 函数时重新运行它,以确保不会意外破坏 add() 的功能。

这是单元测试的一种原始形式,这是一种软件测试方法,通过该方法测试源代码的小单元,以检查它们是否正确。

与日志框架类似,有许多第三方单元测试框架可供使用。您也可以自己编写,但我们需要学习更多的语言特性才能理解相关的实现方式。


约束条件检查

基于约束条件检查的技术是指添加一些额外的代码(如果需要,可以在非调试构建中编译掉),用于检查是否违反了某些假设或期望。

例如,如果我们正在编写一个计算阶乘的函数,而该函数需要一个非负参数,那么函数可以在实际执行计算之前先检查调用方是否传入了非负数。如果调用者传入了负数,函数会立即报错,而不是产生不确定的结果,这有助于立即发现问题。

实现这一点的一种常见方法是通过 assert 和 static_assert,我们将在后续课程中介绍它们。


静态代码分析工具

程序员容易犯某些常见的错误,其中一些错误可以被特定的程序发现。这些程序通常被称为静态分析工具(有时非正式地称为 linter),它们通过分析源代码来确定特定的语义问题(这里的"静态"意味着这些工具分析的是源代码,而非运行时行为)。静态分析工具发现的问题不一定是您当前遇到的问题的原因,但有助于指出代码中的薄弱区域。

您已经有了一个可以直接使用的静态分析工具——那就是编译器!除了确保程序的语法正确外,大多数现代 C++ 编译器还会进行一些简单的静态分析,以识别一些常见的问题。例如,如果您尝试使用尚未初始化的变量,许多编译器会发出警告。

目前已有许多静态分析工具,其中一些可以识别300多种类型的编程错误。在我们的小型程序中,使用静态分析可以帮助找到代码不符合最佳实践的地方。在大型程序中,强烈建议使用静态分析工具,因为它可以帮助发现几十个甚至几百个潜在问题。


3.8 调用堆栈

上一节

3.10 第3章总结

下一节