Logo
Section 1 / 8

断言类型

本课程解释了为何需要使用断言语句,随后探讨了D语言中不同类型的断言语句。

导入

断言类型

本课程解释了为何需要使用断言语句,随后探讨了D语言中不同类型的断言语句。

assert 的必要性

在前一章中,我们学习了如何使用异常和作用域语句来保证程序的正确性。assert 是实现这一目标的另一个强大工具。assert 确保程序所依赖的某些假设是成立的。

有时很难判断应该抛出异常还是使用 assert。在下面的所有示例中,我将不加过多解释地使用 assert。我们将在本章稍后详细讨论两者的区别。

尽管并不总是显而易见,但程序中充满了各种假设。例如,以下函数是在假设两个年龄参数都大于等于零的前提下编写的:

1import std.stdio; 2 3double averageAge(double first, double second) { 4 return (first + second) / 2; 5} 6 7void main() { 8 auto result = averageAge(-1, 10); 9 10 writeln(result); 11}

虽然程序中出现负年龄值可能是不合理的,但该函数仍然会计算出一个平均值,这个值可能在程序中被无意使用,导致程序继续处理错误的数据。

再举一个例子,以下函数假设它始终只会以两个命令之一调用:“sing” 或 “dance”:

1import std.stdio; 2 3void applyCommand(string command) { 4 if (command == "sing") { 5 robotSing(); 6 7 } else { 8 robotDance(); 9 } 10} 11 12void robotSing() { 13 14 writeln("robot singing"); 15} 16 17void robotDance() { 18 19 writeln("robot dancing"); 20} 21 22 23void main() { 24 string command = "teach"; 25 26 applyCommand(command); 27}

由于这个假设,除了“sing”之外的任何命令都会触发 robotDance() 函数的调用,无论命令是否有效。

当这些假设仅存在于程序员的脑海中时,程序很可能最终运行出错。assert 语句可以检查这些假设,并在假设不成立时立即终止程序。

assert

assert 可以以两种方式使用:

1assert(逻辑表达式); 2assert(逻辑表达式, 消息);

逻辑表达式代表了对程序的一个假设。assert 会评估该表达式以验证该假设是否成立。如果逻辑表达式的值为真,则认为假设有效;否则,假设无效,将抛出 AssertError 异常。

顾名思义,此异常继承自 Error。正如我们在异常章节中所知,继承自 Error 的异常绝不能被捕获。程序必须立即终止,而不是在假设失效的情况下继续运行。

上述 averageAge() 函数中的两个隐含假设可以通过两个 assert 调用来明确表达,如下所示:

1import std.stdio; 2 3double averageAge(double first, double second) { 4 assert(first >= 0); 5 assert(second >= 0); 6 return (first + second) / 2; 7} 8 9void main() { 10 auto result = averageAge(-1, 10); 11 12 writeln(result); 13}

这些 assert 检查传达了“假设两个年龄都大于等于零”的含义。也可以理解为“只有当两个年龄都大于等于零时,该函数才能正确工作”。

每个 assert 都会检查其假设,若不成立则以 AssertError 终止程序:

1core.exception.AssertError@deneme(2): Assertion failure

错误消息中 @ 符号之后的部分指明了失败的 assert 检查所在的源文件和行号。根据上述输出,失败的 assert 位于文件 deneme.d 的第 2 行。

assert 的另一种语法允许在检查失败时打印自定义消息:

1import std.stdio; 2 3double averageAge(double first, double second) { 4 assert(first >= 0, "年龄不能为负数。"); 5 assert(second >= 0, "年龄不能为负数。"); 6 return (first + second) / 2; 7} 8 9void main() { 10 auto result = averageAge(-1, 10); 11 12 writeln(result); 13}

有时,人们认为程序永远不可能进入某个代码路径。在这种情况下,通常会使用字面量 false 作为逻辑表达式,使 assert 检查失败。例如,为了表明 applyCommand() 函数不应被除“sing”和“dance”以外的命令调用,并防止这种情况发生,可以在不可能的分支中插入 assert(false):

1import std.stdio; 2 3void applyCommand(string command) { 4 if (command == "sing") { 5 robotSing(); 6 7 } else if (command == "dance") { 8 robotDance(); 9 10 } else { 11 assert(false); 12 } 13} 14 15void robotSing() { 16 17 writeln("robot singing"); 18} 19 20void robotDance() { 21 22 writeln("robot dancing"); 23} 24 25 26void main() { 27 string command = "teach"; 28 29 applyCommand(command); 30}

该函数确保只对它已知的两个命令正常工作。

注意: 在这里,另一种选择是使用 final switch 语句。

static assert

由于 assert 检查用于程序的正确执行,因此它们在程序实际运行时才生效。但也存在一些关于程序结构的检查,可以在编译时就完成。

static assert 是 assert 的对应形式,它在编译时执行。static assert 的优势在于,它甚至不允许编译那些原本会在运行时出错的程序。自然的要求是,逻辑表达式必须能够在编译时求值。

例如,假设菜单标题将显示在一个宽度有限的输出设备上,以下 static assert 确保标题永远不会超过该限制:

1import std.stdio; 2 3void main() { 4 enum dstring menuTitle = "Command Menu"; 5 6 static assert(menuTitle.length <= 16); 7}

注意,string 被定义为 enum,以便其长度可以在编译时计算。

假设程序员将标题改为更详细的描述:

1import std.stdio; 2 3void main() { 4 enum dstring menuTitle = "Directional Commands Menu"; 5 6 static assert(menuTitle.length <= 16); 7}

此时,static assert 检查将阻止程序编译:

1Error: static assert (25u <= 16u) is false

这会提醒程序员输出设备的限制。

当用于模板时,static assert 的作用更加显著。


在下一课中,我们将深入探讨 assert 的各种特性。

异常和作用域

断言的特性