4 ms·
> When possible errors are part of the function specification, on the other hand, we are almost OK. This is the single best piece of advice in this article. Th
by fmap 8y ago
> When possible errors are part of the function specification, on the other hand, we are almost OK.
This is the single best piece of advice in this article. The second thing you have to document is the postcondition in case of an error - what state is the program left in?
With both a normal and an error postcondition you can fully specify your program. Like the author, I'm convinced that most of the pain with error handling stems from programmers ignoring the consequences of an error. That's the reason why approaches that force you to deal with errors explicitly (Maybe a, Result e a, etc.) end up being more robust. Otherwise, a part of the program just ends up missing.
However, from a theoretical perspective, exceptions are superior. The reason is that the error postcondition really does represent a non-local exit. Just like an ordinary return statement, it should be implemented as one instead of forcing programmers to walk the stack by hand. The latter is both less efficient and more error prone. Additionally, resource management must be integrated with error handling anyway and exceptions provide a clean opportunity to connect the two. This is one of the things that C++ gets right.
- rumcajz 8y agoIs there a way to reconcile the two? If the stack is walked up automatically programmers aren't going to deal with errors explicitly. No?
- panic 8y agoWhen a database transaction fails, it rolls back to the state before the transaction. Exceptions ought to work like this too. Then you wouldn’t have to think about all the places an exception could be thrown. A try-catch block would either completely succeed, following the well-tested success path, or completely fail, leaving the program in its original state.
- gpderetta 8y agoIn C++ there is a thing called exception safety and there are three level: * No throw: the function will not fail. Full stop. * Strong exception safety: if the function fails the state of the object(s) is acting on is unchanged. This is similar to transactional atomicity guarantee. * Basic guarantee. If the function fails the state of the objects is unspecified but valid (i.e. no invariant is violated), but data might be lost. From the point of view of the caller of course no throw is the most desirable property, the strong and finally basic. Anything less than that (i.e. corruption, leaks, dangling pointers) is considered unacceptable. Another important insight is realising that exception guarantees have little to do with exceptions and everything to do with postconditions in the return path: for example the same techniques used to guarantee strong safety on the face of exceptions also work to guarantee postconditions on the faceof multiple explicit retun paths.
- mcguire 8y agoTransactions and strong exception safety have two things in common: they're easy to use and hard to implement.
- gpderetta 8y agoYou are correct, but fully transactional semanatics by default would be extremely hard to do on a non gc-ed system language like C++. I could definitely see a language with such a feature though (transactional memory would be a good place to start I guess).
- dllthomas 8y agoYou can't un-fire the missiles. This might be a great approach for some (plausibly very large) subset of cases, but it can't handle everything.
- aaron_m04 8y ago> You can't un-fire the missiles. True, but you can always offer at least the basic guarantee, and you can always document what you are guaranteeing to the caller.
- gpderetta 8y agoYou can wait to fire them until commit time though. Also abort sequences are a thing so you can kinda-sorta unfire them (talk about compensating sequence!).
- dllthomas 8y ago> You can wait to fire them until commit time though. Not in a way that truly solves the problem. Any time you are coordinating multiple actions that are irreversible and may fail, you'll need some contract other than "either your transaction exceeds or everything is rolled back."
- pron 8y ago> I'm convinced that most of the pain with error handling stems from programmers ignoring the consequences of an error. This is one of my favorite empirical studies of software: Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems (2014)[1] It says that, indeed, many catastrophic errors happen because of ignoring the consequences of errors when the handling code was either empty (explicit ignore) or just logged the condition, even when the language enforced error handling. A simple tool they wrote to recognize it would have prevented 33% of the catastrophic failures they'd studied in Cassandra, HBase, HDFS, and MapReduce. So even when programmers are forced to explicitly respond to an error, they handle it with what amounts to a ¯\_(ツ)_/¯. I speculate that it's because psychologically we don't want to think hard enough about what to do when things that seem exceptional happen. [1] https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf https://www.usenix.org/system/files/conference/osdi14/osdi14...
- mcguire 8y agoA second example is Java's checked exceptions. They make errors part of the method's interface, but are almost universally hated because doing anything useful with errors is just too damn hard.
- tonyedgecombe 8y agoThe trouble with checked exceptions is most of the time you want the exception to continue up the stack but you are forced to write unnecessary code.
- pron 8y agoIf you want to propagate the exception, you don't need to write code, just to declare the method as throwing. Sometimes people don't want to do that because they don't want to declare the exception, but that means that you've decided that that should be the point where the exception is handled because the caller is not written to expect exceptions, and therefore the code is not unnecessary. It's true that in some cases this is forced on you because of how some of the standard library's higher-order functions work.
- afranchuk 8y agoThis is one place where syntax that allows easy monadic composition really shines, because it becomes really simple for programmers to "walk the stack". Of course this still requires the "in case of error" state to be defined, but purely functional expressions can allow that to be trivial. A good example is the Monad instance of Either in Haskell.
- ken 8y agoI hear what you're saying, but I confess I don't 'get' exceptions. On the spectrum of ways to report an error (C return values, Swift errors, C++ exceptions, Lisp conditions), they seem like an arbitrary spot in the middle that doesn't really give me the best of anything. They're neither as efficient as C/Swift, nor as flexible as Lisp. Once you're going to go to the effort to walk the stack, why not give the caller the opportunity to continue? It's a huge increment in power for what seems like a minimal addition.
- dllthomas 8y ago> They're neither as efficient as C/Swift, nor as flexible as Lisp. My understanding is that, at the cost of a significant penalty for the (hopefully rare) exceptional case, error handling with exceptions can be faster than returning values like in C in the (hopefully common) successful case.
- kazinator 8y agoIn fact, the addition is almost a subtraction. To make restartable exceptions to work, all you need is a way to search for points without unwinding the stack, and invoke closures there. In TXR Lisp, I unified conditions and restarts into a single mechanism, which is called exceptions. There are two kinds of handling frames: ones for which an unwinding takes place first, and ones which just intercept the search. Both are identified by an exception symbol which exists in an inheritance hierarchy. It's all documented in detail here: http://nongnu.org/txr/txr-manpage.html#N-0146B946 http://nongnu.org/txr/txr-manpage.html#N-0146B946 There are dialect notes comparing with ANSI CL, and an example program shown in both TXR Lisp and a CL translation for comparison.
- makecheck 8y agoActually, C++ doesn’t really get this “right”, they mostly just get to say “we have exceptions in the language”. Exceptions are one of the most frustrating behaviors in C++ (e.g. lots of ways to outright crash your program, no way to really understand the full code path that an exception came from, easy to make serious mistakes like having code paths that throw exceptions in destructors).