计算机编程/错误处理
错误处理技术
[编辑源代码]本章介绍各种错误处理技术。首先描述技术,然后用示例函数及其调用来展示其用法。我们使用 √ 函数,当用负参数调用时,它应该报告错误条件。
返回值
[编辑源代码]function √ (X : in Float) : Float begin if (X < 0) : return -1 else calculate root from x fi end
C := √ (A2 + B2) if C < 0 then error handling else normal processing fi
我们的示例利用了 √ 的所有有效返回值都是正数的事实,因此 -1 可用作错误指示器。但是,当所有可能的返回值都是有效的,并且没有返回值可用作错误指示器时,此技术将无法工作。
错误(成功)指示器参数
[编辑源代码]错误条件通过额外的 out 参数返回。传统上,指示器要么是布尔值,其中“true = 成功”,要么是枚举,第一个元素为“Ok”,其他元素指示各种错误条件。
function √ ( X : in Float; Success : out Boolean ) : Float begin if (X < 0) : Success := False else calculate root from x Success := True fi end
C := √ (A2 + B2, Success) if not Success then error handling else normal processing fi
此技术在数学计算中看起来不太好。
全局变量
[编辑源代码]错误条件存储在全局变量中。然后直接或间接通过函数读取此变量。
function √ (X : in Float) : Float begin if (X < 0) : Float_Error := true else calculate root from x fi end
Float_Error := false C := √ (A2 + B2) if Float_Error then error handling else normal processing fi
从源代码中可以看出,此技术的麻烦之处在于选择重置标志的位置。你可以让被调用者或调用者执行此操作。
此外,此技术不适合多线程。
异常
[编辑源代码]编程语言支持某种形式的错误处理。这从早期 Basic 方言中的经典 ON ERROR GOTO ...
到现代面向对象语言中的 try ... catch
异常处理不等。
其理念始终如一:你将程序的某个部分注册为错误处理程序,以便在发生错误时调用。现代设计允许你定义多个处理程序来分别处理不同类型的错误。
一旦发生错误,执行就会跳转到错误处理程序并在那里继续执行。
function √ (X : in Float) : Float begin if (X < 0) : raise Float_Error else calculate root from x fi end
try: C := √ (A2 + B2) normal processing when Float_Error: error handling yrt
异常处理的最大优势在于它可以在一个异常处理程序中阻止多个操作。这减轻了错误处理的负担,因为不必独立检查每个函数或过程调用以确认其是否成功执行。
契约式设计
[编辑源代码]在 契约式设计 (DbC) 中,必须使用正确的参数调用函数。这是调用者对契约的贡献。如果实际参数的类型与形式参数的类型匹配,并且实际参数的值使函数的先决条件为真,则子程序有机会满足其后置条件。否则,就会发生错误条件。现在你可能想知道这将如何运作。让我们先看一个示例
function √ (X : in Float) : Float pre-condition (X >= 0) post-condition (return >= 0) begin calculate root from x end
C := √ (A2 + B2)
如你所见,函数要求 X >= 0
的先决条件 - 也就是说,只有当 X ≥ 0 时才能调用该函数。作为回报,该函数承诺其返回值也 ≥ 0 的后置条件。
在完整的 DbC 方法中,后置条件将声明一个关系,该关系完全描述了运行函数时产生的值,类似于 result ≥ 0 and X = result * result
。此后置条件是 √ 对契约的贡献。使用断言、注释或语言的类型系统来表达先决条件 X >= 0
体现了契约式设计的两个重要方面
- 编译器或分析工具可以帮助检查契约。(例如,当
X ≥ 0
来自 X 的类型,并且调用 √ 时使用的参数类型相同,因此也 ≥ 0 时,就会出现这种情况。) - 可以在调用函数 **之前** 机械地检查先决条件。
第一个方面增加了安全性:没有程序员是完美的。每个需要由程序员自己检查的契约部分都有很高的出错概率。
第二个方面对于优化非常重要——如果可以在编译时检查契约,则不需要运行时检查。你可能没有注意到,但如果你仔细想想: 从来不会是负数,前提是 幂运算符和加法运算符以通常的方式工作。
我们为一段从不失败的代码创建了 5 个不错的错误处理示例。这就是控制 DbC 某些运行时方面的绝佳机会:你现在可以安全地关闭检查,代码优化器可以省略实际的范围检查。
DbC 语言在面对契约违规时的行为方式上有所区别
- 真正的 DbC 编程语言将 DbC 与异常处理结合起来——在运行时检测到契约违规时引发异常,并提供重新启动失败例程或以已知良好状态阻塞的方法。
- 静态分析工具在分析时检查所有契约,并要求以一种永远不会在运行时违反契约的方式编写代码。
此列表概述了各种编程语言中使用的标准或主要行为和错误处理技术。这并不意味着在所列的编程语言中不可能使用其他技术或行为。
语言 | 技术 | 空 | / 0 | 数组 | 整数 | 浮点数 | 类型 |
---|---|---|---|---|---|---|---|
Ada 月度精选 2005 年 9 月 | 异常/DbC1 | 处理 | 处理 | 处理 | 处理 | 处理 | 处理 |
C | 返回/变量 | 崩溃 | 崩溃 | 故障 | 故障2 | 故障 | 故障2 |
C++ | 异常 | 崩溃 | 崩溃 | 故障 | 故障2 | 故障 | 故障2 |
Python | 异常 | 处理 | 处理 | 处理 | 处理 | 处理 | 处理 |
SPARK | DbC | 处理 | 处理 | 处理 | 处理 | 处理 | 处理 |
错误类型 | |||||||
空 | 空指针或空元素访问。 | ||||||
/ 0 | 除以 0。 | ||||||
数组 | 数组越界访问。 | ||||||
整数 | 整数范围越界/溢出。 | ||||||
浮点数 | 浮点数计算越界。 | ||||||
类型 | 类型转换越界。 | ||||||
处理方式 | |||||||
处理 | 以适当的方式处理错误——例如使用错误或异常处理程序。 | ||||||
故障 | 程序继续以未定义或有故障的方式运行。 | ||||||
崩溃 | 程序崩溃。 | ||||||
技术 | |||||||
DbC | 语言使用契约式设计来避免错误条件。 | ||||||
异常 | 语言在出现错误条件时引发异常或使用类似的技术。 | ||||||
返回 | 语言使用返回代码来指示错误条件。 | ||||||
变量 | 语言使用一个或多个全局变量来指示错误条件。 |
1 : Ada 通过其强大的类型系统支持非常有限形式的 DbC。
2 : 在 C 和 C++ 中,行为已定义,有时会故意使用,但在本比较中,我们认为是无意使用。