7 ms·
Exceptions are Bad
- BruceForth 15y agoGaaahh.... Just because C++ and Java do something incredibly stupid when it comes to exceptions, doesn't mean exceptions are bad. Just that C++ and Java suck. (hint: exceptions without restarts are retarded)
- dexen 15y agoLet me get this straight: he's doing a a full circle -- without examining how the current situation came to be. At some point we went for exception, because (warning: big simplification ahead!) library authors figured they can't trust client code to always check return value for reported error condition. We got automatic, forced checking injected by compiler -- `exceptions'. With all the pains of fugly syntax in certain languages, because in many projects the trade-off makes sense. With the reasonable default for unexpected and unhandled condition: error is logged and process is stopped. Now the proposal -- `Let's have [[EDIT]]a tagged union: Either<error_code, return_value> ' takes us back to where we started -- client code is free to not check for errors. The process will not stop nor even print backtrace if the programmer forgets to check; the process will happily go on with invalid / unsupported / mangled / whatever data. No automatic propagation of error down the stack to outer handler in our function's caller. No stack unwind nor automatic object destruction. No hierarchy of error types (no specific errors derived from general ones). No nothing. And no discussion of those matters. Oh, my. Reliability in software has just gotten a new name. EDIT: fix'd mistake pointed out by tianyicui & thesz. Thanks!
- tianyicui 15y ago> Now the proposal -- `Let's have a tuple: Either<error_code, return_value> ' The Either data type is not a tuple.
- jerhewet 15y ago"In mathematics and computer science, a tuple is an ordered list of elements. [...] The term originated as an abstraction of the sequence: single, double, triple, quadruple, quintuple, sextuple, septuple, octuple, ..., n‑tuple, ..., where the prefixes are taken from the Latin names of the numerals. The unique 0‑tuple is called the null tuple. A 1‑tuple is called a singleton, a 2‑tuple is called a pair and a 3‑tuple is a triple or triplet." http://en.wikipedia.org/wiki/Tuple http://en.wikipedia.org/wiki/Tuple
- thesz 15y ago"Either" isn't ordered list of elements. It's result of a choice, a tagged sum of sets. So Either is not a tuple by any mean.
- Deestan 15y agoTo elaborate: > "Either" isn't ordered list of elements. Although the Either<T,U> type looks like a pair definition, an Either<T,U> return value contains only one element of type T or U.
- jerhewet 15y ago> without examining how the current situation came to be. The problem, in a nutshell, with just about everything in the field of Computer Science today. See: George Santayana.
- dexen 15y agoGreat pointer, thanks! I don't have problem with doing a full circle -- actually, that's a good thing once in a while. Cue Apple using BSD and GNU for MacOS X. I take issue with not considering what reasons got us where we are to-day, and what issues we bring back from the past. State of art (previous research and application work) should be laid out. Pros and cons have to be listed explicitly, or at least referenced. Weighted one against the other. I'd call that ``matter of scientific integrity'', if we were talking about Computer Science ;-)
- tianyicui 15y ago> [...] client code is free to not check for errors. The process will not stop nor even print backtrace if the programmer forgets to check; the process will happily go on with invalid / unsupported / mangled / whatever data. [...] This statement is simply not true, at least in languages that actually have Either data types (read: Haskell). But I'm not sure how to implement the generic class Either in Java.
- dexen 15y agoPlease elaborate on how it works? Perhaps I got this very wrong; never used an Either<> class. Would an attempt to access the value (if an error was indicated instead) cause error condition? Would it be similar in nature to an exception?
- masklinn 15y ago> Would an attempt to access the value (if an error was indicated instead) cause error condition? Would it be similar in nature to an exception? Either is a tagged union (actually a sum type), you have a value OR an error, which are typed (haskell uses Left and Right as the constructors for Either, by convention Left holds an error and Right holds a value, because right is correct). You can not just "cast" your Either, you have to type-check it with a case (or use monadic operators) to unwrap it, and with the right flag the compiler will ensure you're not missing cases. At compile time. Either is basically Schrödinger's box (the computation resulted in both a value and an error), and the compiler checks that you're set for handling both a dead and a live cat, or it will not let you open the box.
- dexen 15y agoThanks for explanations; now it makes sense to me. Another question: is there built-in support, or an idiom or standard practice for unwinding multiple levels of stack? Is it possible to have handler code is not directly in the function that called, but somewhere lower on the call stack -- and have it reached automatically like an exception does? Btw., it seems the bolder statement I post to HN, the wronger it turns out to be... oh, boy XD
- Strilanc 15y agoI don't agree that returning an Either is better than returning/throwing. It depends on what you do in the error case. If you try to recover, then Either is better. If you fail fast, then throwing is better. The main problem with checked exceptions, at least in Java, is the clumsiness of doing things right. Ideally every function would expose exceptions corresponding to useful error cases. Instead, because there's so much boilerplate involved in creating new exception types and wrapping exceptions as the proper type, programmers are more likely to swallow or blindly propagate. I believe making checked exceptions signifcantly better is as easy as adding two features to Java: - Easy wrapping. 'wraps Type1 as Type2' for try blocks and function signatures. - Easy inline defining of new exceptions. 'throws forge ParityMismatch extends IllegalArgumentException'
- aidenn0 15y agoIf you read the comments, the author wrote a comment that basically says this. Exceptions should be used for fatal errors, not for recoverable errors.
- troels 15y agoThat was boring. I like Damien Katz' [epic piece about errors and exceptions](http://damienkatz.net/2006/04/error_code_vs_e.html http://damienkatz.net/2006/04/error_code_vs_e.html) much better.
- henrikschroder 15y agoNow we have a more honest result type. It tells you that it will definitely for each and every String return you something, either the Integer value or a NumberFormatValidation (or an ErrorMessage, or an error String or whatever). There is no null to deal with anywhere and no ugly catch blocks. Is there really a significant difference between: Either<Bad, Good> result = callMyFunction(foo, bar); if (result.getBad() != null) { //Handle error } //Do something with result.getGood(); ...and the regular: try { Good result = callMyFunction(foo, bar); //do something with result; } catch (Exception e) { //Handle error } The first example is one line shorter, yay, but if you want to "throw" your error upwards, you have to convert that Bad object into whatever is the error class for your current scope and return that. Did you really gain anything here? Or does the Either class have some extra magic on it that makes this unnecessary?
- mechanical_fish 15y agoYour example is too simple. Yes, if what you want is the exact same semantics that you get with exceptions, Either is little better than exceptions. I believe the trick with Either is that it assists one in designing completely different strategies. For example, you can design plugins that accept ambiguous arguments. Sometimes -- often, actually -- you really want to write this: user_photo = load_user_photo(username) ui.display_user_page(username, user_photo) but instead you are compelled to write something like this: try { user_photo = load_user_photo(username) } catch (PhotoFormatException e) { ui.tell_the_user_the_format_is_bad user_photo = NULL } catch (Exception e) { if (ui.interactive?) { ui.tell_the_user_a_terrible_error_occurred(e) } unless (ui.app_in_production?) { throw new MyScopeException(e, "bug") } } if (is_null(user_photo)) { ui.display_user_page_without_photo(username) } else { ui.display_user_page_with_photo(username, user_photo) } (Now, I wrote that in an extra-clumsy way, but only so that we might fully appreciate the clumsiness of the logic. In the real world, I'd probably try to do this more elegantly. The result would probably (a) look cleaner; (b) be far more abstract, confusing, and overdesigned than it needed to be; and (c) contain a half-implemented broken implementation of the Either pattern. ;) What we have here is an application that might need to behave differently if there is a photo or not, and differently if the user is a production customer, a script running in production, or a developer running on a local machine. And because exceptions behave like exceptions whether I like it or not -- they always travel up the stack, they're always in a panic -- that try/catch logic has to be here, at this level. But if load_user_photo() could return an Either I could just pass that Either to ui.display_user_page(). And then I could plug in a "ui" class that does something like this [1]: class Ui { function display_user_page(username, user_photo) { # The customer is blind; user_photo is irrelevant. say("The username is " + username) } } Or this: class Ui { function display_user_page(username, user_photo) { print("The username is " + username) if user_photo.is_good?() { display_photo(user_photo) } } } Or this: class Ui { function display_user_page(username, user_photo) { if user_photo.is_bad?() { throw new HorrorException("I am shocked") } } } or whatever you like. This logic doesn't necessarily belong in your main logic, it belongs in a plugin, and the Either pattern helps you send it there. --- [1] These are oversimplified examples too, of course. In the real world my class would probably do a little more exception handling. There is a certain amount of irreducible pain in dealing with the infinite number of things that might go wrong; all the language can do is give us the flexibility to organize the resulting pile of code as best we can.
- thepumpkin1979 15y agoI think checked exception are indeed annoying, specifying what exception types can be thrown in the method documentation it's pretty much enough. However I don't think ugliness is the main reason why exception should be avoided as much as possible, I think is performance: You get a performance penalty when you throw exceptions. About the return values, since .NET 2.0 the TryParse calls were introduced and it works pretty well in the case you don't need to know the details of a failed parsing operation which I think is the 95% of the cases: public static bool TryParse( string s, out int result ) I'm not saying we should stop using exceptions, but we should have more High poerfomance alternatives like this in Java. More info here: http://msdn.microsoft.com/en-us/library/f02979c7.aspx http://msdn.microsoft.com/en-us/library/f02979c7.aspx
- benjiweber 15y agoThis is a common dogma in .net land. If we're being fair a more accurate statement would be "you currently get a performance penalty when you throw exceptions in Microsoft's implementation of .net". Exceptions are much faster in mono than Microsoft's .net[0], are much faster still in Java[0] and can be made as fast as goto in Java in some circumstances[1], particularly if you control the Exception class being thrown. Claiming Exceptions are slow is like saying "Java is slow". Languages and language features aren't slow, implementations are. [0] http://weblog.ikvm.net/PermaLink.aspx?guid=388b2a6d-e7b2-4ffa-86e7-450c87e6178f http://weblog.ikvm.net/PermaLink.aspx?guid=388b2a6d-e7b2-4ff... [1] http://blogs.oracle.com/jrose/entry/longjumps_considered_inexpensive http://blogs.oracle.com/jrose/entry/longjumps_considered_ine...
- thepumpkin1979 15y agoThese are very interesting articles benjiweber and you are right, it depends of the implementation. My point is that there are some cases in which you can gain some speed by no throwing exceptions at all and TryParse is a perfect example of this(at least in .NET). Also exception-handling blocks might prevent the Virtual Machine from inlining the method(again, at least in .NET), is this true for the JVM too?
- jakevoytko 15y agoExceptions have a major advantage not mentioned by the OP. In code, errors usually happen several stack frames away from the code that knows how to recover. The network can't transmit data because a remote computer doesn't respond[0], so it passes an error to the network library, which passes an error to an RPC dispatcher, which passes an error to the high-level RPC interface, which finally produces an error at the site of your RPC call, where you can decide what to do. If you don't have exceptions, you must write all of this error handling code yourself. Plus, if you need to pass the error higher than the call site to make a good decision, you must write that too. However, if you have exceptions, you can avoid writing most of the error passing code, and you can group error handling logic in the same place. You get more flexibility in structuring and refactoring your code, in exchange for encountering problems when things unexpectedly go out of scope. [0] This certainly passes my 'exceptional circumstance' threshold, since success is no longer possible.
- PaulHoule 15y agoErrors are bad. Errors don't respect encapsulation. No amount of design work will anticipate a SomebodyPouredACokeOnARouterInDetroit exception. If a system is composable, there's alway the chance that some subsystem is going to throw an error that wasn't anticipated. Things like Either[A,B] make me cringe because they're just like what we used to do back in the 1980's in C. You know, it used to be that you wanted to write int doAandB(x,y) { return A(x)+B(y); } but then you had to deal with error values, and it turns out that A and B never returned a negative integer so you'd write int doAandB(x,y) { int a,b; a=A(x); if (a==-1) return -1; b=B(x); if (b==-1) return -1; return a+b; } 3 lines of code balloons to 9 lines of code. Read carefully: adding error handling has tripled the size of this code sample! That's what exceptions win for you... Effectively you get the extra 6 lines of code written for you. You might argue that the default behavior of exceptions might not be exactly what you want, but it's much better than the first example I wrote... Aborting is a better default behavior than barreling past an error. Now, Either[A,B] is better than returning an in-band signal like -1, but still requires you to write the bulky error handling code. A real advantage of having something like Either[A,B] is when you're using asynchronous communications. Just the other day, I was dealing with something that used Scala actors and it turned out that sometimes the "business end" of the actor would fail with an Exception. If you catch the Exception in the actor and send it in a message to the next actor, it lets the next actor see the Exception and decide what it wants to do about the failure. As for variance of Exceptions, that's a pipe dream. Imagine a system that has a subsystem that looks something up in a 'database'. That 'database' could be an in-RAM lookup table (never throws exceptions), it could be an on-disk lookup table (can throw IOException), it could be a web service (can throw networking-related exceptions), or it could be using something like JDBC (which could throw it's own exceptions). If you constrain the kind of exceptions that can be thrown by the subsystem, you either destroy the composability of the system or you 'dump-down' the error information that's available to the executive level of the software. Now, if you REALLY want to move forward but not back with composability of error handling, what's needed is some mechanism that the executive level gets 'hints' that would help it deal with the error. That is, lets it answer questions like: (i) if I try this again immediately, is it likely that this will succeed? (ii) if I try this again in five minutes, is it likely that this will succeed? (iii) was this error caused by specific bad data I sent, or is this subsystem in a completely failed state... etc.
- brown9-2 15y agoWhat's the problem with returning null from the Integer parse method, provided that you document when this method will return null? The author dismisses this option without much of an explanation. The aversion to using null for a return type here seems to be a symptom of the same problem as throwing an unchecked exception - when the behavior isn't probably documented, the developer isn't given a chance to know that the return from the method needs to be handled. But, if you can't trust developers to write or read documentation, then is it really worth it to go out of you way to invent new ways of returning a non-success response from this method? The underlying problem still exists - lack of communication.
- Strilanc 15y agoAgreed. However, sometimes you want more information. Having the method specify why there was an error, so you can give a helpful error message to the user, is desirable.
- aidenn0 15y agoThe problem with returning null is that you end up with an error in one part of the code causing an exception in a later part of the code. Integer i = foo(); //Do a bunch of stuff doSomething(i); is much harder to debug, especially if doSomething() is documented that it is forbidden to call with a null. You now need to find where i came from, which can be non-trivial. If instead you return Either<SomeError,Integer> you now can't call a function that hasn't contracted to either not unbox the value, or deal with the error. It's making the type signature match the documentation, which means that the compiler will help catch programmer error. I don't see how this can be anything but a good thing.
- coliveira 15y agoThe main problem is to decide what really an exception is. One can make the point that, in 99% of the cases, exceptions in a function should be handled as predictable actions. If a correct client needs to deal with it, it is better to use if/else than a try/catch clause. The later almost always makes to code more obscure and difficult to understand. There is a reason why in professional C programs most of the code goes to error handling: it is necessary. The equivalent Java code will just have one try/catch per line of code, or just let exceptions crash the app.
- russell 15y agoI am rather fond of the jQuery style of chaining method calls, which doesnt work if methods return status codes. OTOH I dont like checked exceptions at all. Mainly because development teams go overboard with an exception for every type of problem. I make a call to a subsystem and I immediately have to deal with half a dozen exception types so that I dont have to clutter up the signature of my method. Especially in other peoples code where the exceptions are just swallowed leaving behind "//TODO add logging". The Either class looks like a reasonable alternative, although I would do it slightly differently. Either<String> result = myMethod(). probably with a different. result.getError(); would return some kind of error object result.getValue(); would throw an exception if there were an error. It looks promising. I think I will play with it in Java.
- masklinn 15y agoThe problem here is that you can't really statically check your stuff. With either and match completeness turned on, the compiler will force you to handle the error case, you have to "ignore" it very explicitly by doing nothing in its handling.
- jaekwon 15y agoLook at Node.js, where exceptions don't necessarily travel up written code, begetting the convention of passing an error object, similar to what the OP proposes. I don't like it, I don't think anyone particularly likes it. It makes you miss exceptions.