@@ -1968,7 +1968,7 @@ try {
19681968
19691969当我意识到犯了这么大一个错误时,简直吓了一大跳,在本书第 2 版中,我在处理程序里通过打印栈轨迹的方法“修补”了这个问题(本章中的很多例子还是使用了这种方法,看起来还是比较合适的),虽然这样可以跟踪异常的行为,但是仍旧不知道该如何处理异常。这一节,我们来研究一下“被检查的异常”及其并发症,以及采用什么方法来解决这些问题。
19701970
1971- 这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。
1971+ 这个话题看起来简单,但实际上它不仅复杂,更重要的是还非常多变。总有人会顽固地坚持自己的立场,声称正确答案(也是他们的答案)是显而易见的。我觉得之所以会有这种观点,是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言,而是像 C++ 和 Java 这样的“强静态类型语言”(也就是编译时就做类型检查的语言),这是前者所无法比拟的。当刚开始这种转变的时候(就像我一样),会觉得它带来的好处是那样明显,好像类型检查总能解决所有的问题。在此,我想结合我自己的认识过程,告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的,当然,很多时候它还是非常有用的,但是当它挡住我们的去路并成为障碍的时候,我们就得跨过去。只是这条界限往往并不是很清晰(我最喜欢的一句格言是:所有模型都是错误的,但有些是能用的)。
19721972
19731973### 历史
19741974
@@ -1990,15 +1990,15 @@ C++从 CLU 那里还带来另一种思想:异常说明。这样,就可以用
19901990
19911991C++的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致;比如,如果函数或方法会抛出某些异常,那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同,C++不会在编译时进行检查以确定函数或方法是不是真的抛出异常,或者异常说明是不是完整(也就是说,异常说明有没有精确描述所有可能被抛出的异常)。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符,C++会调用标准类库的 unexpected() 函数。
19921992
1993- 值得注意的是,由于使用了模板,C++的标准类库实现里根本没有使用异常说明。在 Java 中,对于范型用于异常说明的方式存在着一些限制。
1993+ 值得注意的是,由于使用了模板,C++ 的标准类库实现里根本没有使用异常说明。在 Java 中,对于范型用于异常说明的方式存在着一些限制。
19941994
19951995### 观点
19961996
1997- 首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++异常说明的启发,以及受 C++程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。
1997+ 首先,Java 无谓地发明了“被检查的异常”(很明显是受 C++ 异常说明的启发,以及受 C++ 程序员们一般对此无动于衷的事实的影响),但是,这还只是一次尝试,目前为止还没有别的语言采用这种做法。
19981998
19991999其次,仅从示意性的例子和小程序来看,“被检查的异常”的好处很明显。但是当程序开始变大的时候,就会带来一些微妙的问题。当然,程序不是一下就变大的,这有个过程。如果把不适用于大项目的语言用于小项目,当这些项目不断膨胀时,突然有一天你会发现,原来可以管理的东西,现在已经变得无法管理了。这就是我所说的过多的类型检查,特别是“被检查的异常"所造成的问题。
20002000
2001- 看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C#的设计人员发现:
2001+ 看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示,因此这并不足以说明问题。一名 C# 的设计人员发现:
20022002
20032003> “仅从小程序来看,会认为异常说明能增加开发人员的效率,并提高代码的质量;但考察大项目的时候,结论就不同了-开发效率下降了,而代码质量只有微不足道的提高,甚至毫无提高”。
20042004
@@ -2053,7 +2053,7 @@ public class MainException {
20532053
20542054### 把“被检查的异常”转换为“不检查的异常”
20552055
2056- 在编写你自己使用的简单程序时,从 main() 中抛出异常是很方便的 ,但这不是通用的方法。
2056+ 在编写你自己使用的简单程序时,从主方法中抛出异常是很方便的 ,但这不是通用的方法。
20572057
20582058问题的实质是,当在一个普通方法里调用别的方法时,要考虑到“我不知道该这样处理这个异常,但是也不想把它‘吞’了,或若打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进 RuntimeException 里面,就像这样:
20592059
@@ -2166,7 +2166,7 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型
21662166
21672167异常是 Java 程序设计不可分割的一部分,如果不了解如何使用它们,那你只能完成很有限的工作。正因为如此,本书专门在此介绍了异常——对于许多类库(例如提到过的 I/O 库),如果不处理异常,你就无法使用它们。
21682168
2169- 异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java坚定地强调将所有的错误都以异常形式报告的这一事实 ,正是它远远超过语如 C++这类语言的长处之一,因为在 C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
2169+ 异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具,使得你可以在运行时报告错误并从错误中恢复,但是我一直怀疑到底有多少时候“恢复”真正得以实现了,或者能够得以实现。我认为这种情况少于 10%,并且即便是这 10%,也只是将栈展开到某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为。无论这是否正确,我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实 ,正是它远远超过语如 C++ 这类语言的长处之一,因为在 C++ 这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己“错误是否正在成为漏网之鱼?”(只要你没有“吞咽”异常,这是关键所在!)。
21702170
21712171就像你将要在后续章节中看到的,通过将这个问题甩给其他代码-即使你是通过抛出 RuntimeException 来实现这一点的--你在设计和实现时,便可以专注于更加有趣和富有挑战性的问题了。
21722172
@@ -2180,11 +2180,11 @@ WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型
21802180
21812181我们开始讨论Go编程语言,我很着迷,因为Rob Pike等人。我们已经清楚地提出了许多关于语言设计的非常尖锐和基本的问题。基本上,他们已经采取了我们开始接受的有关语言的所有内容,并询问“为什么?”关于每一种语言。学习这门语言真的让你思考和怀疑。
21822182
2183- 我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做Big Upfront Design。
2183+ 我的印象是,Go团队决定不做任何假设,只有在明确需要特征的情况下才能改进语言。他们似乎并不担心进行破坏旧代码的更改 - 他们创建了一个重写工具,因此如果他们进行了这些更改,它将为您重写代码。这使他们能够使语言成为一个持续的实验,以发现真正需要的东西,而不是做 Big Upfront Design。
21842184
21852185他们做出的最有趣的决定之一是完全排除异常。你没有看错 —— 他们不只是遗漏了经过检查的异常情况。他们遗漏了所有异常情况。
21862186
2187- 替代方案非常简单,起初它几乎看起来像C一样。因为Go从一开始就包含了元组 ,所以你可以轻松地从函数调用中返回两个对象:
2187+ 替代方案非常简单,起初它几乎看起来像 C 一样。因为 Go 从一开始就包含了元组 ,所以你可以轻松地从函数调用中返回两个对象:
21882188
21892189``` go
21902190result , err := functionCall ()
@@ -2194,11 +2194,11 @@ result, err := functionCall()
21942194
21952195就是这样:对于每次调用,您都会获得结果对象和错误对象。您可以立即检查错误(这是典型的,因为如果某些操作失败,则不太可能继续下一步),或者稍后检查是否有效。
21962196
2197- 起初这似乎很原始,是古代的回归。但到目前为止,我发现Go中的决定都得到了很好的考虑 ,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?
2197+ 起初这似乎很原始,是古代的回归。但到目前为止,我发现 Go 中的决定都得到了很好的考虑 ,值得深思。我只是做出反应,因为我的大脑是异常的吗?这会如何影响 James 的问题?
21982198
2199- 它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进catch和finally子句 。正是这种替代执行路径的世界导致了 James 抱怨的问题。
2199+ 它发生在我身上,我已经将异常处理视为一种并行执行路径。如果你遇到异常,你会跳出正常的路径进入这个并行执行路径,这是一种“奇异世界”,你不再做你写的东西,而是跳进 catch 和 finally 子句 。正是这种替代执行路径的世界导致了 James 抱怨的问题。
22002200
2201- James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过try-finally跟踪创建以确保清理发生 (Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。
2201+ James 创造了一个对象。理想的情况下。对象创建不会导致潜在的异常,因此你必须抓住它们。你必须通过 try-finally 跟踪创建以确保清理发生 (Python团队意识到清理不是一个特殊的条件,而是一个单独的问题,所以他们创建了一个不同的语言构造 - 以便停止混淆二)。任何导致异常的调用都会停止正常的执行路径并跳转(通过并行bizarro-world)到 catch 子句。
22022202
22032203关于异常的一个基本假设是,我们通过在块结束时收集所有错误处理代码而不是在它们发生时处理错误来获益。在这两种情况下,我们都会停止正常执行,但是异常处理有一个自动机制,它会将你从正常的执行路径中抛出,跳转到你的并行异常世界,然后在正确的处理程序中再次弹出你。
22042204
0 commit comments