Exception Handling Considered Harmful

异常处理
被认为存在缺陷

Do, or do not. There is no try.

— Yoda, The Empire Strikes Back
(George Lucas)

by Jason Robert Carey Patterson, Nov 2005

Recent programming languages such as Java, Python and Ruby have chosen to use exception handling as their primary method of error handling, replacing the traditional approach of error return codes. I believe continuing this trend for future programming languages would be a mistake, for two reasons...

最近的编程语言如 Java, Python 和 Ruby 选择使用异常处理(exception handling)作为其主要的错误处理方法, 取代了传统的返回错误码方法. 我相信在未来的编程语言中继续这种趋势将是一个错误, 原因有两个...

  1. Exception handling introduces a hidden, "out-of-band" control-flow possibility at essentially every line of code. Such a hidden control transfer possibility is all too easy for programmers to overlook – even experts. When such an oversight occurs, and an exception is then thrown, program state can quickly become corrupt, inconsistent and/or difficult to predict (think about an exception unexpectedly being thrown part way through modifying a large data structure, for example).
  2. 异常处理在几乎每一行代码中引入了一种隐藏的"带外(out-of-band, 流程之外)"控制流可能性. 这种隐藏的控制转移可能性对于程序员来说太容易忽视了 – 即使是专家. 当这种疏忽发生, 然后抛出异常时, 程序状态会很快变得损坏, 不一致 和/或 难以预测(例如, 考虑在修改大型数据结构的过程中意外抛出异常).
  3. Exception handling does not fit well with most of the highly parallel programming models currently in use or being explored (fork/join, thread pools and task queues, the CSP/actor model etc), because exception handling essentially advocates a kind of single-threaded "rollback" approach to error handling, where the path of execution – implicitly a single path – is traversed in reverse by unwinding the call stack to find the appropriate error handling code.
  4. 异常处理不太适合于目前使用或正在探索的大多数高效的并行编程模型 (fork/join, 线程池和任务队列, CSP/actor 模型等), 因为异常处理本质上提倡一种单线程错误处理的 "回滚(rollback)" 方法,其中执行路径 – 隐含的 一条 路径 – 通过展开调用堆栈反向遍历找到适当的错误处理代码.

Good Intentions

好的意图

Exception handling was originally intended to solve several perceived problems with the traditional approach of error handling via return codes.

异常处理最初旨在通过返回码来解决传统错误处理方法的几个感知问题.

First, by separating the error handling code from the main body of normal code, it was hoped that the code would be less cluttered, and hence cleaner, with the normal, non-error case easier to follow because it was not obscured by necessary but tedious and unlikely error checking/handling.

首先, 通过将错误处理代码与正常业务代码分离, 希望代码不会那么混乱, 因此 更干净, 正常的, 非错误的情况更容易理解, 因此他没有必要的, 但繁琐的且不太常见的错误检查/处理.

Second, by allowing a separation between the point where an error occurs and the point where it is handled, even a potentially very large separation across many function calls, it was hoped to enable better handling of errors deep within libraries, allowing those errors to be propagated back to the application without requiring a whole chain of error checking and returning code to be written, and thus avoiding the tendency for libraries to swallow or generalize errors because it was too much hassle to feed them all the way back in full detail.

其次, 通过允许在发生错误的点和处理错误的点之间进行分离, 甚至在许多函数调用之间可能存在非常深的调用链, 人们希望能够更好地处理库深层的内部错误, 允许这些错误被传播回上层应用程序,而不需要编写整个错误检查链和返回代码, 从而避免了库吞并或泛化错误的倾向, 因为一路反馈完整的细节太麻烦了.

Finally, exceptions were seen as a solution to the "semi-predicate" problem, where for some operations every possible return value is valid and thus an error must be indicated through some other, more indirect means, such as a pass-by-reference error argument or an internal success/failure state indicator within an object.

最后, 异常被视为 "半吊子(semi-predicate)" 问题的解决方案,其中对于某些操作, 每个可能的返回值都是有效的, 因此必须通过其他更间接的方式来指示错误, 例如通过引用错误参数或对象内部的成功/失败状态指示器.

To solve these problems, exception handling essentially advocates a kind of "rollback" approach to error handling. When an error occurs an exception is "thrown", which engages the runtime system to begin a rollback operation by unwinding the call stack, destroying local objects as it goes, until a suitable error handler "catch" block is reached, and execution continues from there.

为了解决这些问题, 异常处理本质上提倡一种错误处理的 "回滚" 方法. 当发生错误时, 会"抛出(thrown)"异常, 这会让运行时系统通过展开调用堆栈, 销毁本地对象来开始回滚操作, 直到到达合适的错误处理程序 "捕获(catch)" 块, 并从那里继续执行.

The primary intended benefit of such an approach is that all of the code between the place where the error happens and is thrown, and the place where the exception is caught and handled, can simply remain blissfully unaware of the error, and not have to detect and handle it explicitly. Local objects just get destroyed automatically while unwinding the call stack, and all is well.

这种方法的主要预期好处是, 在错误发生和抛出的地方, 以及异常被捕获和处理的地方之间的所有代码都可以保持对错误非常愉快的不知道, 而不必显式地检测和处理他. 本地对象只是在解除调用堆栈时自动被销毁, 一切正常.

Sounds good, right?

听起来不错, 对吧?

Hidden Control Flow & Corrupt State

隐藏的控制流 & 损坏状态

One immediately obvious problem with a "rollback" style approach to error handing is that many operations are not so trivially rolled back simply by destroying local objects (and perhaps letting heap objects be cleaned up by a garbage collector). The classic example is I/O – you cannot un-print something to the screen, un-ask for user input, un-overwrite a file's contents, or un-send a network packet. All true, and an excellent point.

"回滚" 式错误处理方法的一个显而易见的问题是, 许多操作并不是简单地通过销毁本地对象 (并且可能让垃圾收集器清理堆对象) 来回滚. 典型的例子是 I/O – 你不能取消在屏幕上打印某些东西, 取消询问用户输入, 取消覆盖文件的内容, 或者取消发送网络数据包. 都是些很常见事实.

But that's just the tip of the iceberg. I/O isn't even the real problem. It is just one of a number of possible non-local side effects that code might have. Far more common, yet often overlooked, is state in general – any code which simply makes changes to some part of a shared data structure, like a document model or a scene graph. Unwinding the stack and destroying local objects won't undo those changes. In fact, in an exception-rich environment where the act of making such changes can potentially cause an exception, it is impossible to write a strongly exception-safe function that has two or more unrelated side effects, of any kind, that cannot be performed atomically.

但这只是冰山一角. I/O 甚至不是真正的问题. 这只是代码可能产生的许多非局部副作用之一. 通常情况下, 更常见但往往被忽略的是 状态(state) – 任何只对共享数据结构的某个部分进行更改的代码, 如文档模型或场景图. 展开堆栈并销毁本地对象不会撤消这些更改. 事实上, 在一个异常丰富的环境中, 进行此类更改的行为可能会导致异常, 因此不可能编写一个具有两个或多个不相关副作用的强异常安全函数, 这些副作用中任何一种, 都不能以原子化执行.

It is impossible to write a strongly exception-safe function
that has two or more unrelated side effects, of any kind,
that cannot be performed atomically.
不可能编写一个非常安全的异常函数
同时处理好两种或两种以上不相关的相关副作用,
这不能原子化地执行.

Consider an exception unexpectedly being thrown part way through modifying a large data structure, for example. How likely is it that the programmer has written code to correctly catch that exception, undo or reverse the partial changes already made to the data structure, and re-throw the exception? Very unlikely! Far more likely is the case that the programmer simply never even considered the possibility of an exception happening in the first place, because exceptions are hidden, not indicated in the code at all. When an exception then occurs, it causes a completely unexpected control transfer to an earlier point in the program, where it is caught, handled, and execution proceeds – with a now corrupt, half-modified data structure!

例如, 考虑通过修改大型数据结构意外地抛出异常. 程序员编写的代码正确捕获该异常, 撤消或重置已对数据结构进行的部分更改并重新抛出该异常的可能性有多大? 不太可能! 更可能的情况是, 程序员根本没有考虑异常发生的可能性, 因为异常是隐藏的, 根本没有在代码中指出. 当一个异常发生时, 他会导致一个完全意外的控制转移到程序中的一个较早的点, 在那里它会被捕获, 处理并继续执行 – 现在可能是一个损坏的, 半修改的数据结构

Any non-trivial shared-data-modifying algorithm cannot, in general, be truly strongly exception-safe unless either the programming language itself provides some form of transactional capability (eg: SQL's commit approach), or the programmer simulates transactional behavior in code by making a copy of the data, modifying the copy, and doing some kind of pointer swap to make the new copy the "real thing" atomically – which is ridiculously tedious and clearly not practical for large objects or complex data structures.

一般来说, 任何非平凡的共享数据修改算法都不可能真正具有强异常安全性, 除非编程语言本身提供某种形式的事务性功能 (例如: SQL 的 commit 方法), 或者程序员通过复制数据, 修改副本来模拟代码中的事务性行为, 并进行某种指针交换, 使新副本在原子上成为"真实的东西" – 这是非常枯燥乏味麻烦的, 显然不适用于大型对象或复杂的数据结构.

So if you're in the middle of modifying data, and an exception occurs, you could easily end up leaving the data in a half-baked state. That is really, really dangerous, because it invites the possibility of silent data corruption. In most cases, any clearly visible error signal, even program termination, is by far preferable to the possibility of silent data corruption. And exception handling simply isn't a clearly visible error signal. Most of the calling code can, and does, simply ignore exceptions, assuming some code further back will catch and handle them.

因此, 如果你正在修改数据, 并且出现异常, 那么你很容易会把数据放在半成品(half-baked, 考虑不周全, 草率的)状态. 这 really 真的是非常危险的,因为它可能会导致数据损坏. 在大多数情况下, 任何清晰可见的错误信号, 甚至程序终止, 都比无声数据损坏的可能性要好得多. 异常处理并不是一个清晰可见的错误信号. 大多数调用代码可以也经常忽略异常, 并假设后面的一些代码会将捕获并处理他们.

Thus, coding styles relying on exception handling over anything more than trivial distance between throw and catch have a tendency to "take simple, reproducible and easy to diagnose failures and turn them into hard-to-debug subtle corruptions", to quote Larry Osterman.

因此, 在抛出和捕获之间的微小距离之外, 依赖异常处理的编码风格往往会 "将简单, 可重复且易于诊断的故障转化为难以调试的细微损坏", 引用 Larry Osterman.

Forcing the calling code to handle the error right away is the correct approach, because it forces the programmer to think about the possibility of an error occurring. That's a key point. The fact that this clutters the code with error checking is unfortunate, but it is a small price to pay for correctness of operation. Exceptions tend to allow, even encourage, programmers to ignore the possibility of an error, assuming it will be magically handled by some earlier exception handler.

强制代码 立即 处理错误是正确的做法, 因为他迫使程序员 思考 发生错误的可能性. 这是一个关键点. 不幸的是, 这会使代码中的错误检查变得繁琐, 但为操作的正确性付出的代价很小. 异常倾向于允许, 甚至鼓励程序员忽略错误的可能性, 并假设他会被调用方异常处理程序神奇地处理.

Forcing the calling code to handle the error right away is
the correct approach, because it forces the programmer
to think about the possibility of an error occurring.

Exceptions tend to allow, even encourage, programmers
to ignore the possibility of an error, assuming it will be
magically handled by some earlier exception handler.
强制代码立即处理错误是
正确的方法, 因为他迫使程序员
考虑发生错误的可能性

例外情况往往允许甚至鼓励程序员
忽略错误的可能性, 并假设他会
被调用方的异常处理程序神奇地处理.

In order to write exception-safe code, at every significant line of code the programmer must take the possibility of an exception and rollback happening into account, to be sure the code cleans up properly and leaves things in a suitable, stable state if an exception occurs – that it doesn't leave a data structure half-modified, or a file or network connection open, for example. That is decidedly non-trivial. It takes a great deal of time and effort, it requires a very high degree of discipline to get right, and it is just far too easy to forget or overlook something – even experts frequently get it wrong.

为了编写异常安全的代码, 在每一行重要的代码中, 程序员必须考虑到发生异常和回滚的可能性, 以确保代码正确地清理, 并在发生异常时保持适当, 稳定的状态, 例如, 不会使数据结构半修改(half-modified), 或文件或网络连接处于打开状态. 这绝对不是小事. 这需要花费大量的时间和精力, 需要非常严格的纪律才能纠正错误, 忘记或忽略某些事情太容易了 – 即使是专家也经常出错.

Putting more general issues aside for just a moment, the C++ exception handling system in particular wasn't very well thought out IMHO, and is by far the weakest part of the language – so much so that I generally recommend people don't use C++ exceptions at all, and turn them off in their compiler if possible.

把更多的一般问题放在一旁, 特别是 C++ 异常处理系统不是很好的思考, 是目前语言中最薄弱的部分 – 所以我通常建议人们强制别使用 C++ 异常, 如果可能的话, 在编译器中关闭他们.

Exception handling is the only C++ language feature which requires significant support from a complex runtime system, and it's the only C++ feature that has a runtime cost even if you don't use it – sometimes as additional hidden code at every object construction, destruction, and try block entry/exit, and always by limiting what the compiler's optimizer can do, often quite significantly. Yet C++ exception specifications are not enforced at compile time anyway, so you don't even get to know that you didn't forget to handle some error case! And on a stylistic note, the exception style of error handling doesn't mesh very well with the C style of error return codes, which causes a real schism in programming styles because a great deal of C++ code must invariably call down into underlying C libraries.

异常处理是唯一在 C++ 语言特性中, 需要复杂的运行时系统的支持, 他也是唯一的 C++ 特性,即使不使用他, 也有运行时的成本 – 有时所能做的通过限制编译器, 为每个对象构造, 销毁, try 代码块 entry/exit, 始终 添加 try catch 隐藏代码, 在优化中通常非常重要. 然而, C++ 异常规范在编译时没有强制执行, 所以你甚至不知道你没有忘记处理一些错误的情况! 从风格上讲, 异常风格的错误处理与 C 风格的错误返回代码非常不搭, 这会导致编程风格中的真正分裂, 因为大量 C++ 代码必须始终调用底层 C 库.

Furthermore, because C++ doesn't have garbage collection it is all too easy even for experts (see here, here, here and here) to accidentally write code which leaks memory if an exception is thrown by some function you call, even if you yourself don't use exceptions. This is further complicated by C++'s lack of a finally block to simplify cleanup. It is also particularly easy in C++ to leave objects in a half-baked state when an exception occurs, because even many "primitive" operations like assignment can potentially throw exceptions. In practice, it becomes essentially impossible not to leave objects in a half-baked state once the objects grow beyond trivial size/complexity. Even many of the STL containers are not strongly exception-safe – they don't leak memory, but they might leave your data in a half-baked state where the operation was only "partially" done, which is not terribly useful or helpful.

另外, 因为 C++ 没有垃圾回收, 哪怕是专家也很容易(see here, here, here and here) 在调用的某个函数引发异常, 意外导致编写代码会引发内存泄漏 即使你自己不使用异常 . 由于 C++ 缺少简化清理的 finally 块, 这一点变得更加复杂. 在 C++ 中, 当异常发生时, 特别容易将对象置于半销毁状态, 因为即使许多"原始(primitive)"操作如赋值也可能引发异常. 在实践中, 一旦对象的大小/复杂度超过了微不足道的程度, 就不可能不让对象处于半成品的状态. 甚至许多 STL 容器也不是很健壮 异常安全 – 他们不会泄漏内存, 但他们可能会让您的数据处于半成品状态,操作只"部分"完成, 这不是非常有用或有用

The core problem is the hidden control-flow possibility. There's a famous joke about a mythical programming language construct called comefrom, which is a parody on the problematic goto statement found in many early programming languages. The idea is that the programmer can, at any point in the program, say "comefrom 20", and any time execution reaches line 20 it will immediately jump to the "comefrom" code. The point being made here is that nothing on line 20 itself indicates that control flow might be diverted like this. Exception handling introduces precisely this kind of hidden control flow possibility, at nearly every significant line of code: every function/method call, every new object construction, every overloaded operator etc.

核心问题是隐藏的控制流可能性. 有一个著名的笑话, 讲的是一种神秘的编程语言结构, 叫做 comefrom, 他模仿了许多早期编程语言中存在问题的goto语句. 这个想法是, 程序员可以在程序中的任何时候说"comefrom 20", 任何时候执行到第 20 行, 他都会立即跳转到"comefrom"代码. 这里要指出的是, 第 20 行本身没有任何东西 表明控制流可能会像这样转向. 异常处理正是在几乎每一行代码中引入了这种隐藏的控制流可能性: 每一个函数/方法调用, 每一个新对象构造, 每一个重载操作符等等

Exception handling thus breaks the "principle of least astonishment", and breaks it HUGE.

因此, 异常处理打破了"最小惊讶原则", 并将其打爆.

Joel Spolsky expresses the issue in his concise and down-to-earth manner as follows: "They are invisible in the source code. Looking at a block of code, including functions which may or may not throw exceptions, there is no way to see which exceptions might be thrown and from where. This means that even careful code inspection doesn't reveal potential bugs. ... To write correct code, you really have to think about every possible code path through your function. Every time you call a function that can raise an exception and don't catch it on the spot, you create opportunities for surprise bugs caused by functions that terminated abruptly, leaving data in an inconsistent state, or other code paths that you didn't think about."

Joel Spolsky 他以简洁和踏实的方式表达了以下问题: "他们在源代码中是不可见的. 查看一段代码, 包括可能引发或可能不会引发异常的函数, 无法查看可能会引发哪些异常以及从何处引发. 这意味着, 即使是仔细的代码检查也不会发现潜在的错误. ... 要编写正确的代码, 必须考虑通过函数的所有可能的代码路径. 每次调用一个可能引发异常但未当场捕获的函数时, 都会产生意外的错误, 这些错误是由突然终止的函数引起的, 导致数据处于不一致的状态, 或者是您没有想到的其他代码路径."

Mismatch With Parallel Programming

与并行编程不匹配

The very idea of rollback/unwinding which is so central to exception handling more-or-less inherently implies that there is a sequential call chain to unwind, or some other way to "go back" through the callers to find the nearest enclosing catch block. This is horribly at odds with any model of parallel programming, which makes exception handling very much less than ideal going forward into the many-core, parallel programming era which is the future of computing.

回滚(rollback)/回溯(unwinding) 对异常处理来说是如此核心, 这一点本身就意味着有一个顺序调用链需要回溯, 或者通过其他方式"返回"调用方以找到最近的封闭 catch 块. 这与任何并行编程模型都极为不一致, 这使得异常处理在进入多核并行编程时代(这是计算机的未来)变得非常不理想

Even when considering the simplest possible parallel programming model of all – a straightforward parallel fork/join, such as processing all of the elements of an array in parallel – the problem is immediately obvious. What should you do if you fork 20 threads and just one of them throws an exception? Unwind back past the forking and kill the other 19 threads, risking data corruption? Unwind but leave the other 19 threads running never to be joined/reaped, and doing who knows what to objects you supposedly destroyed during the unwinding? Make the programmer put in a catch block at the point of forking, which still has to choose between those two basic possibilities anyway?

即使考虑到最简单的并行编程模型 – 简单的并行 fork/join, 比如并行处理数组的所有元素 – 问题也显而易见. 如果 fork 20 个线程, 其中只有一个抛出异常, 该怎么办? 把之前回收回去吗, 杀死其他 19 个线程, 冒着数据损坏的风险? 继续, 但让其他 19 个线程运行, 永远不会被连接(joined)/获取(reaped), 谁知道在继续过程中你应该回溯什么对象? 让程序员在 forking 时放入一个 catch 块, 无论如何, 他仍然必须在这两种基本可能情况之间进行选择

Moving to more interesting and useful models of parallelism, exception handling again seems completely mismatched. Today, for example, the most common practical model used for flexible parallelism is a pool of worker threads each executing small units of work, often called tasks or operations, which are stored in some kind of work queue and dispatched to the thread pool one after another as each thread finishes its current task. Applying exception handling to such a scheme seems impossible, since the units of work are essentially detached from any "caller". The whole concept of unwinding the call stack makes no sense at all in such a situation.

转到更有趣, 更有用的并行模型, 异常处理似乎再次完全不搭. 例如, 今天, 用于灵活并行的最常见的实用模型是一个工作线程池, 每个工作线程执行小的工作单元, 通常称为任务或操作, 他们存储在某种工作队列中, 并在每个线程完成其当前任务时一个接一个地分派到线程池. 对这样的方案应用异常处理似乎是不可能的, 因为工作单元基本上与任何"调用方"分离(detached). 在这种情况下, 回溯调用堆栈的整个概念毫无意义

More sophisticated parallel programming models, such as asynchronous message passing between communicating sequential processes (CSP or the "actor" model), have similar properties to the thread pool and task queue approach, though these properties are hidden by proper language support. Again, since there is no obvious execution path to unwind, and since messages between objects/actors are frequently asynchronous, it is difficult to see how the general approach of exception handling can be applied.

更复杂的并行编程模型, 如通信顺序进程(CSP 或"actor(参与者)"模型)之间的异步消息传递, 具有与线程池和任务队列方法类似的属性, 尽管这些属性通过适当的语言标准支持. 同样, 由于没有明显的执行路径来展开, 而且对象/参与者之间的消息通常是异步的, 因此很难看到如何应用异常处理的一般方法

Finally, because exceptions are an out-of-band control mechanism, existing outside the normal call/return mechanism, they don't fit very well when the CSP or actor model is taken to its logical next step, with objects/actors on different systems connected by a network. You can easily return an error code over a byte stream that happens to be a network connection, but you can't easily throw an exception back over a network connection, because the exception is "out of band" – it doesn't come back via the normal data channel. An elaborate runtime system could, of course, work around this, but is that really a sensible approach?

最后, 由于异常是一种带外控制机制, 存在于正常的 调用(call)/返回(return) 机制之外, 因此当 CSP 或 actor 参与者模型 进入其逻辑下一步时, 他们不太适合, 不同系统上的对象/参与者通过网络连接. 您可以通过恰好是网络连接的字节流轻松地返回错误代码, 但不能通过网络连接轻松地抛出异常, 因为异常是"带外"的 – 他不会通过正常的数据通道返回. 一个精心设计的运行时系统当然可以解决这个问题, 但这真的是一种明智的方法吗?

The simple fact is the concept of rollback/unwinding just doesn't work very well in a highly parallel situation, even a simple one like fork/join, let alone more sophisticated and useful models like thread pools or CSP/actors. Trying to retrofit exceptions and rollback/unwinding into a parallel environment seems like an exercise in complexity, frustration and ultimately futility.

简单的事实是, 回滚/回溯的概念在高度并行的情况下无法很好地工作, 即使是像 fork/join 这样的简单情况, 更不用说像线程池或 CSP/actors 这样更复杂, 更有用的模型了. 试图改造异常并回滚/回溯到并行环境似乎是一种复杂, 最终徒劳的沮丧练习

Exceptional Exceptions

特殊 Exceptions

Many advocates of exception handling admit that it is best used only for extremely rare "exceptional" cases. In other words, you should use error return codes for anything that might actually happen in real life, but as long as you only use exceptions for things that will never actually happen they're fine. Maybe I'm exaggerating for effect here, but you get the point.

许多异常处理的倡导者承认, 他最好只用于极为罕见的"异常"情况. 换句话说, 你应该对现实生活中可能发生的任何事情使用错误返回码, 但只要你只对永远不会发生的事情使用异常, 他们就可以了. 也许我夸大了这里的效果, 但你明白了

I personally take the view that most of the "exceptional" cases they're talking about should basically just be guaranteed by the system to never happen at all – memory allocation failures, runtime stack exhaustion, other kinds of resource exhaustion, memory access violations etc. We shouldn't be exposing those kinds of things to applications at all, because in nearly all cases there is precious little the application can sensibly do to recover from the error anyway. There's useful complexity and then there's useless complexity, and having to write application code to deal with things that will never really happen, or for which the only safe response is program termination anyway, is just adding useless complexity.

我个人认为, 他们所说的大多数"例外"情况基本上应该由系统保证永远不会发生——内存分配失败, 运行时堆栈耗尽, 其他类型的资源耗尽, 内存访问违规等. 我们根本不应该向应用程序公开这些情况,因为几乎在所有情况下, 应用程序都无法明智地从错误中恢复. 有有用的复杂性, 也有无用的复杂性, 而必须编写应用程序代码来处理永远不会真正发生的事情, 或是唯一安全的响应是程序终止, 这只会增加无用的复杂性

Instead, we should be presenting applications with the illusion of a machine with infinite resources, thereby making writing applications that much simpler and less error-prone. If physical resources actually do become exhausted, it should be the responsibility of the operating system, not the application, to take appropriate action. As a simple example, memory allocation should be guaranteed not to fail in general, with special options to return NULL on failure for those few rare cases where recovery from failure makes sense (such as allocating a very large image or handling the possibility of failure in some alternative way like working at a lower resolution).

相反, 我们应该让应用程序看起来像一台拥有无限资源的机器, 从而使编写应用程序变得更简单, 更不容易出错. 如果物理资源确实耗尽, 那么采取适当的措施应该是操作系统的责任, 而不是应用程序的责任. 举个简单的例子, 应该保证内存分配通常不会失败, 对于少数几种罕见的情况, 如果从失败中恢复是有意义的(例如分配一个非常大的图像, 或者以某种替代方式处理失败的可能性, 比如以较低的分辨率工作), 可以使用特殊选项在失败时返回 NULL

For those of you who say "but what about small, embedded devices that have real resource limits?", the answer there is simply to go and look at what's actually being done in the embedded space today. We already have small embedded devices which function as wireless network hotspots, print servers, music servers and NAS servers, all at the same time, all in the size of a power brick. The notion of having "special" versions of programs which run in embedded space and which constantly have to handle resource limits is just as dead as the idea of "special" content for mobile devices (can anyone remember WAP or i-Mode?).

对于那些说"但是小型嵌入式设备有真正的资源限制怎么办?"的人来说, 答案就是去看看今天在嵌入式空间中实际做了什么. 我们已经有了小型嵌入式设备, 他们可以同时充当无线网络热点, 打印服务器, 音乐服务器和 NAS 服务器, 所有这些设备都有一块电源砖那么大. 让"特殊"版本的程序在嵌入式空间中运行并不断处理资源限制的想法, 与为移动设备提供"特殊"内容的想法(有人记得 WAP 或 i-Mode 吗?) 一样已经过时

The future is essentially standard, general-purpose applications, maybe slightly cut down, running on top of slightly cut down but essentially standard, full-blown OSs, all on your phone, or your watch, or inside your soap dispenser. It's a world where even your toaster runs Linux. In such a world, exposing resource limits like the remote possibility of memory allocation failure to applications is just silly.

未来基本上是标准的, 通用的应用程序, 可能会稍微减少, 运行在稍微减少但基本上是标准的, 全面的操作系统之上, 所有这些都在你的手机, 手表或皂液器(soap dispenser)中. 这是一个连你的烤面包机都运行 Linux 的世界. 在这样一个世界里, 向应用程序公开资源限制, 比如内存分配失败的可能性是非常愚蠢的.

Finally

最后

The cold, hard truth is that if you exclude trivial use of exceptions where the exception is caught and handled immediately, essentially mimicking the old error return code approach, then 90% of the other exception handling code out there in the wild isn't exception-safe. It works just fine, as long as an exception never actually happens, but if one does you're basically hosed. Or, to quote Michael Grier: "Exceptions only really work reliably when nobody catches them."

冷酷而残酷的事实是, 如果你排除了异常被立即捕获和处理的琐碎使用, 基本上模仿了旧的返回错误码方法, 那么在其他狂乱异常处理代码中, 90% 都不是异常安全的. 只要异常从未真正发生过, 他就可以正常工作, 但如果发生了, 你基本上是被水淹没了. 或者, 引用Michael Grier: "只有在没有人发现异常时, 异常才会真正可靠地工作."

I believe this clearly tells you there is a problem with the language feature, and the very idea IMHO. I am certain 99% of C++ code isn't exception-safe, I'm equally sure 99% of Objective-C code isn't exception-safe, and I'd be willing to bet a good 90% of Java code isn't exception-safe either, even with garbage collection to clean up memory leaks. The problem isn't just memory leaks, or even unclosed files and network sockets, it's modifications to shared data structures (and related equivalents like database state, partially written files etc). Those don't get undone by unwinding the stack and destroying local objects, nor by a garbage collector, no matter how smart it is about trying to call finalize() methods in the right order.

我相信这清楚地告诉你, 语言功能和思想本身存在问题. 我确信 C++ 代码中的 99% 不是异常安全(exception-safe), 我同样确信 99% 的 Objective-C 代码也不是异常安全的, 而且我愿意打赌, 即使是垃圾收集来清理内存泄漏, 也有 90% 的 java 代码也不例外. 问题不仅仅在于内存泄漏, 甚至包括未关闭的文件和网络套接字, 还在于对共享数据结构(以及数据库状态, 部分写入的文件等相关等价物)的修改这些不会通过展开堆栈和销毁本地对象来撤消, 也不会通过垃圾收集器来撤消, 无论他尝试以正确的顺序调用finalize()方法有多聪明.

I vote that all programming language designers should just say no to exceptions. I know I do.

我认为所有编程语言设计师都应该拒绝异常. 我懂我才用

Exception handling doesn't really work. It doesn't give the benefits it claims. Hardly any real-world code uses it correctly except in the trivial case, which is just a more verbose equivalent of error return codes. Nobody really uses exceptions to any genuine benefit. They just get in the way and make writing code more silently error-prone. And exception handling is a horrible, horrible mismatch to highly parallel programming.

异常处理实际上不起作用. 他没有提供他声称的好处. 几乎没有任何真实世界的代码正确地使用它, 除了在琐碎的情况下, 这只是一个更详细的错误返回代码等价物. 没有人真正利用异常来获得任何真正的好处. 他们只是碍事, 让编写代码更容易出错. 异常处理与高度并行编程是一种可怕的, 可怕的完全不搭

Error return codes work. They are simple. They are effective. They have stood the test of time. More to the point, they are what everyone actually uses when they know an error might really actually happen! That tells you a LOT.

错误返回代码有用. 他们很简单. 他们是高效的. 他们经受住了时间的考验. 更重要的是, 当每个人知道错误可能真的会发生时, 他们都会使用他们! 这会告诉你非常多重要S信息.

If you're a programming language designer, I encourage you to just say no to exceptions, and take your first step into a better, more reliable world.

如果你是一名编程语言设计师, 我鼓励你对异常说, 然后迈出第一步, 进入一个更好, 更可靠的世界.

 

 

posted on 2022-04-02 12:59  喜欢兰花山丘  阅读(152)  评论(2编辑  收藏  举报