6.3 将可能出错的代码括起来的语句结构
至此我们了解到,到 1964 年 PL/I 语言诞生时,很多对当今的异常处理意义重大的特征已经被提出来了,如允许定义出错时的处理操作,可以追加新的错误类型,可以自主触发出错等。
然而,它和现在 Java 语言、C++ 语言、Python 语言等采用的异常处理的语句结构有很大的不同。PL/I 语言是先定义好出错时的处理操作,再编写可能出错的代码。与这种形式不同的是,Java 等语言是先(用 try{…} 括起来)编写可能出错的代码,然后编写出错时的处理操作。那么这种语句结构是何时、基于什么原因产生的呢?
John Goodenough 的观点
1975 年,John Goodenough 在自己的论文 {9[John B. Goodenough,“Exception handling: issues and a proposed notation”, Communications of the ACM, Vol.18 Issue 12, ACM, 1975, pp.683-696 此时把失败称为异常(exception)已经很普遍了。]} 中提出了一种更好的异常处理的方法 10。他的观点是这样的:命令有可能会抛出异常,而程序员有可能忘记这种可能性,也可能在不正确的地方编写异常处理或者编写不正确类型的异常处理。为使编译器能够对程序员的错误发出警告,减少这种可能性,需要做到两点。一是明确声明命令可能抛出何种异常,二是需要有将可能出错的操作括起来的语句结构。
10John Goodenough 在创作这篇论文时是 SofTech, Inc 的职员,之后担任卡耐基梅隆大学软件工程研究所的最高技术负责人。更多详细资料请参考:http://www.sei.cmu.edu/about/people/profile.cfm?id=goodenough_12984
以这里提议的括起来的语句为基础,现代大部分语言采用了先括起来可能出错的操作,再编写错误处理的语句结构。明确声明命令可能抛出何种异常,这个设计方针在 Java 语言的异常检查中得以继承,我们将在后面的 6.6 节中详细解说。
引入 CLU 语言
从 1975 年 Goodenough 的论文发表直到 1977 年,程序设计语言 CLU11 引入了异常处理的机制,追加了置于命令后面的错误处理语句结构 except。CLU 语言从最初就具有用 begin…end 将代码括成块状的功能,这一功能和 except 相结合,就实现了将可能出错的操作括起来再补 充错误处理的代码编写方式。以下是一段 CLU 语言代码,从百分号到句末为注释。错误的类型,可以是诸如除数为零时的 zero_division12。
11后文介绍 Liskov 置换原则的章节中会提到 CLU 语言的发明者——Barbara Liskov,请参考 12.1 节。
12CLU 语言中抛出异常的命令和 PL/I 语言一样,都为 signal。
CLU
begin
% 可能出错的操作
% 可能出错的操作
end except when 错误的类型:
% 出错时的处理
% 出错时的处理
end
引入 C++ 语言
不久后的 1983 年,C++ 语言诞生。针对异常处理的语句结构问题从 1984 年到 1989 年间经历了多次讨论,C++ 语言最终确认追加一种语句结构,把关键字 try 放在那些被括起来的可能出错代码的前面,把关键字 catch 放在捕捉并处理错误的代码块前面 13。按照 C++ 语言设计者斯特劳斯特卢普( Bjarne Stroustrup)的说法,try 只是一个为了方便理解的修饰符 14。
13关于这个过程的详细描述,请参考斯特劳斯特卢普的著作 The Design and Evolution of C++。BASIC 语言和 PL/I 语言具有的错误处理后返回出错那行的 resume 功能,该功能被取消的过程颇为有趣,推荐读者阅读。
14“try 也不是必需的,但没有它理解不便,因此最终决定引入这一看起来多余的关键字”。请参考斯特劳斯特卢普 的著作 The Design and Evolution of C++。
C++
try {
/ 可能出错的代码 /
/ 可能出错的代码 /
} catch {
/ 错误处理命令 /
/ 错误处理命令 /
}
另外,C++ 语言还选用 throw 作为触发异常的命令 15。之所以没选择 PL/I 语言和 CLU 语言中使用的 signal,是因为 signal 已经在标准库中被使用了。于是,触发异常的表述就变成了抛出异常。
15“使用 throw 这一关键字是因为更易理解的 raise 和 singal 两个关键字已经在标准库作为函数名字占用了”。请参考斯特劳斯特卢普的著作 The Design and Evolution of C++。
引入 Windows NT 3.1
上世纪 90 年代初,微软公司开始用 C 语言编写新的 Windows 操作系统,这就是 1993 年发布的 Windows NT 3.1。这个版本的制作时,也考虑到需要有便于操作的错误处理机制,于是在操作系统和 C 语言编译器导入了结构化异常处理(Structured Exception Handling,SEH)的概念。结构化异常处理中,除了将可能出错的代码括起来的 try 和将错误处理的代码括起来的 except 之外,还有将即使出错也要执行的代码括起来的 __finally。结合随后出现的语言中的叫法,我们把表示即使出错也要执行的关键字称为 finally。下一节我们来讲 finally 的必要性。
C语言中使用SHE的代码
try{
try{
/ 可能出错的代码 /
/ 可能出错的代码 /
}finally{
/ 出错与不出错都要执行的代码 /
}
}except(…){
/ 错误处理的代码 /
}
