章节目录

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

本节阅读量:

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

那我们该怎么办呢?


不要犯错误

嗯,最好的事情是一开始就不要犯错误。下面是一个不完整的列表,可以帮助避免出错:

  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章总结

下一节