4 ms·
The pragmatic programmer book has a great quote about exceptions. Something about how once an exception is caught your stack starts to unwind and a new program
by johnwatson11218 10y ago
The pragmatic programmer book has a great quote about exceptions. Something about how once an exception is caught your stack starts to unwind and a new program is now running on your system. Can't find it right now.
The worse production bugs I have seen involve complicated exception handlers that generated a second exception while trying to write to the filesystem or something.
In that case some other exception handler now starts to execute further up the stack. It really is as if a new program is now compiled and installed on your production servers. A new program that has read/write access to the production db. A program that most likely was never executed in QA and has never been seen by any person.
It is a bit hyperbolic but I have never seen an exception handler over four lines long that did not contain a bug.
My attitude towards exceptions is like a fire alarm. If I hear a fire alarm it means one thing and I have one action to take. Any notion of providing different pitched fire alarms to encode information would be absurd and dangerous, that is how I view exception hierarchies.
It is my understands that the Go language from google decided not to include this language feature.
I have heard that Java's checked exceptions are now seen as an important, failed experiment.
- sgift 10y ago> I have heard that Java's checked exceptions are now seen as an important, failed experiment. Sadly, this is what people often say these days, I still think they provide far more value than unchecked exceptions. I really liked it when I didn't have to provide any exception signatures and didn't have to think about where to catch them. I didn't like it so much anymore when unchecked exceptions started crashing my code, because there were exceptions thrown at points I never expected them to be thrown. Either no exceptions or checked exceptions, but unchecked? My head :(
- silvestrov 10y agoThere are almost always the possibility of exceptions: you can get NullPointerException, OutOfMemoryError, ArrayIndexOutOfBoundsException, etc. There is a log of exceptions that derive from RuntimeException. You must always assume that code in the exception handler can fail unless you have written every single line and it allocates no memory at all.
- sgift 10y agoAll these exceptions are basically errors in your code, which is the reason they are runtime exceptions in the first place. The API needs a way to tell you that you produced a bug (because the type system, programming environment, etc. isn't advanced enough to prevent that case), but it doesn't have to bother you with writing code to handle that case: If it happens at production time you have a bug. Fix it. Don't try to work around it.
- ivan_gammel 10y agoNot all. Some of them are related to execution environment of an application, not to your code, and in this case developer may actually have something to do to adapt to the environment, rather than simply crashing.
- sgift 10y ago> Not all. Some of them are related to execution environment of an application, not to your code Let's say "more or less all": Yes, if there's an OOM because the system has not enough memory at all you maybe have to tell the user what to do instead of just crashing because the environment failed you, but in almost every other case the moment you accept the environment as parameter it becomes part of your code: If someone gives you a null object you can throw an IllegalArgumentException and show the calling program that it misused your API, but if you accept null and then call some method without checking for null the NPE you get is your bug, not that of your caller. Edit: Rewritten to make my point clearer.
- specialist 10y agoI still don't understand the complaint against checked exceptions. I've concluded that unchecked exceptions (above the runtime) and exception chaining are painful artifacts necessitated by using overwrought frameworks like Spring, J2EE, JPA, etc. Most useful anti-checked exception TL;DR I've gotten is "What do you do when your code can't act (recover) on a checked exception, like some library fails on I/O?" My answer remains "I handle it." After demo'ing my code, buddies conceded that my strategy works because I'm coding "close to the metal". In that case, it was an ETL/workflow engine (framework) that used hand-coded state machines (eg get next, data error, retry, restart). Looking forward, I'm keen to learn Erlang and Elixir. I have the impression they would provide for free all the error handling and recovery I painstakingly hand-coded.
- pekk 10y agoThe complaint is that it adds a lot of noise as people are forced to write handlers for exceptions they're not interested in, and these handlers are typically empty.
- int_19h 10y agoChecked exceptions don't play well with high-order functions, and basically anything else where one piece of code is calling into another piece such that it doesn't know what it is at compile-time (dynamically loaded extensions, DI, UI event handlers, and even plain old virtual methods in many cases). Basically, at those boundaries, you either have to say that the called code can throw anything - but then what about your own contract? you will either have to swallow everything; or rethrow everything, making it a part of your contract; or wrap everything in an exception type that you declare vas part of your contract, which is really a disguised version of rethrowing that doesn't add any meaningful value. Or else you say that the callee cannot throw anything, and then it is forced to swallow exceptions at the boundary. Here's a very simple example of how it affects design: try implementing Java's List<T>, or even Iterable<T>, on top of a file. Something really simple, like one string element per line. You'll find that it all works, except for error handling - because every read can fail, and neither List.get() nor Iterator.next() let you throw an appropriate exception type. In contrast, in .NET, File.ReadLines will happily return an IEnumerable<string>, which will throw IOException when a given line couldn't be read - because IEnumerator is not limited with respect to what exceptions it cannot throw. To properly deal with this problem, you need value-dependent types. Then you can do things like write, say, a generic implementation of map() that takes any random sequence, and throws the same exceptions that said sequence can throw when it's iterated.
- Joeri 10y agoI don't typically find much value in an exception, checked or unchecked, that is passed up the chain more than one step. The ability to intelligently handle an exception other than by logging/swallowing it decreases very quickly with every step up the call chain. If I don't know what to do with an exception in a particular method it is highly unlikely the caller of that method will have more of a clue. That's why I tend to catch all exceptions and then either produce a fallback value, or signal back to the caller by returning an Optional (if the only useful thing to tell it is that it wasn't possible to produce a value) or by throwing a new specifically tailored exception and making that checked. In either case, the caller must be explicit about what they want to do when things go wrong.
- ivan_gammel 10y agoThere are few specific cases, when you actually can do something after multiple returns, and they belong to RuntimeException hierarchy. I've seen good example of it just few hours ago, when my IntelliJ WebStorm (Java application) signalled, that it doesn't have enough memory (OoME) and suggested to update Xmx config setting and restart. Apparently, such exceptions should be handled at the root of execution stack, but sometimes, like in this case, there are ways to handle them with minimal damage.
- voodootrucker 10y agoThat's why you allocate every resource in a try-with-resources block, and let the exception fly all the way up to the top level of the stack, where you log it. Everything is unwound, everything is safe, special logic and wrapping not required (though wrapping can be valuable if there is information to add along the way).
- cbsmith 10y agoNah, they're pretty much a flaw. The idea of having compile time checks for exceptions is a good one, but the assumption that the callee understands what should be checked/unchecked by the caller is... not well supported by the evidence.
- benashford 10y agoGo's approach requires specific code to handle errors in each and every function, and it's very easy to forget or introduce a bug in the propagation of the errors (or to ignore one altogether). So I wouldn't use that as proof that the world has settled on global "stop the world" exceptions vs checked exceptions. Rust is another one that requires specific Result<DataType, ErrorType> to be passed back to a calling function, that either contains the return value or an error. But unlike Go, it's very difficult to ignore the error entirely, but it does require code to handle and propagate errors up the stack. Although both Go and Rust also have the concept of a panic, that does stop the world (or at least the current thread, unless the panic is captured but that's another story...).
- fauigerzigerk 10y agoIt's pretty difficult to ignore an error entirely in Go as there's a syntactical red flag everywhere you do it. The problem with Go's error handling is that it doesn't play nice with using functions as part of larger expressions.
- mratzloff 10y agoIt's my favorite thing about Go.[0] Even in C++ I use error values instead of exceptions, regardless of RAII, so it was a natural transition to Go. Although I'd like to see what a C-style language with Common Lisp-style error conditions and restarts would look like. Ruby has a little bit of that functionality with `redo` and `retry`. [0] Followed by multiple return values, duck-typed interfaces, automatic pointer dereferencing, channels, and a rich standard library. Sorry, this didn't begin as a rah-rah Go post.
- fauigerzigerk 10y agoThe problem I have with Go's error handling is that it makes you choose: Either you write a function that can return errors or you write a function that can be used as part of a larger expression. It can never be both. result := f() * g() becomes x, err := f() if err != nil { //handle f() err } y, err := g() if err != nil { //handle g() err } result := x * y Even without exceptions I can imagine to have something like result, err := f() * g() if err != nil { //handle err } Of course that raises a lot of other questions if functions can have side-effects (but possibly no more so than shortcut logical operators)
- mratzloff 10y agoYou must choose, but there's nothing stopping you from collecting errors in the background and creating a monad. Return a maybe value and expose a way to access the error.
- fauigerzigerk 10y agoWhat's stopping me from doing that more often than I do is that it's too much work. Collecting errors is just part of it. You also have to make sure nothing that has side effects gets called after the first one. Without language support this is tortuous and error prone.
- specialist 10y ago"...involve complicated exception handlers..." You mean delegating exception processing to a separate code path, a la the Strategy pattern? Or just straight up catch blocks?
- johnwatson11218 10y agoThe case I remember was the exception handler was trying to do file I/O and got a second exception that was handled somewhere up the call stack. That second exception is the one that was listed in the support ticket.
- sievebrain 10y agoI think your complaint is simply that error handling code paths are rarely tested, which is true, but is also true in programs that don't use exceptions.
- johnwatson11218 10y agoIn the context of an organization that has a formal QA department, it is very difficult for the QA folks to make the system throw all the relevant exceptions. It falls to the developers to test those code paths and then there are a million other things that have higher priority.
- agumonkey 10y agoThe stack unwinding was such a sad thing to me. Reading about CL condition system made me smile widely. You have more data to treat the exception. While reading the abstract, suddenly the word Exception felt absurd. It's rarely an exceptional condition, it's really a parallel planned error checking. Also, being from the Java era, an Exception was mostly a boring way to ensure error checking, while lispers did use exception as cut / tree jmp to provide solution values to an algorithm. ps: funny today youtube suggested this https://www.youtube.com/watch?v=zp0OEDcAro0 https://www.youtube.com/watch?v=zp0OEDcAro0
- kristianp 10y agoThe closest I can find to that quote is the following, from the section called "Dead Programs Tell no Lies": "when your code discovers that something that was supposed to be impossible just happened, your program is no longer viable. Anything it does from this point forward becomes suspect, so terminate it as soon as possible. A dead program normally does a lot less damage than a crippled one."