Hacker News new | past | comments | ask | show | jobs | submit login
Exceptions are Bad (atlassian.com)
66 points by chrisbroadfoot on May 30, 2011 | hide | past | favorite | 42 comments



Exceptions 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.


Let 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!


> [...] 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.


> But I'm not sure how to implement the generic class Either in Java.

An abstract class with two concrete private implementations and the relevant factory methods (probably on the abstract class).

The part which is going to break you is handling the type-safe unwrapping of error or value. You could do that with blocks/lambdas if Java had blocks/lambdas which worked (Smalltalk-style), but it does not.

Oh, and I think you can't make the abstract class final since you need to extend it, so others are free to extend it as well, and then you're screwed.

I tried implementing a Maybe type in C# once, it did not end well (in the sense that I could not really enforce type-safety, if I remember well), and I'd expect Java would be even worse.


Please 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?


> 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.


Thanks 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


> 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?

Not that I know, but I am very much a haskell novice. You may want to ask for further information in /r/haskell, on reddit.


> Now the proposal -- `Let's have a tuple: Either<error_code, return_value> '

The Either data type is not a tuple.


"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


"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.


To 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.


> 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.


Great 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 ;-)


Now 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?


Your 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.


So basically an Either is for returning a different (ostensibly error) type in a language that normally cares about the declared return type in a method signature?

How would that work for passing the result to another method for analysis? Relax the rules there too? Wouldn't that require doing away with compiler/interpretor type enforcement altogether? (not that I have a problem with that)


The type checker will confirm that other function accepts a (Either Bad Good) as its argument. If you want to use a function that only takes a Good and returns a SomethingElse, call it in a "do" block and the call will be skipped if you got a Bad (because in the Either monad, the Left value is used for short-circuit failure) or use liftM to convert it to a function that takes a (Either Bad Good) and returns a (Either Bad SomethingElse).

A lot of other languages had to bake in exception type checking as a weird special case, because even though the type of a function (because that's really about when you can and cannot use it) should depend on which exceptions it handles, they made that way too much hassle to actually enforce.

However, Haskell is still evolving, and people have also added a couple versions of the typical bypass-the-type-system exception trainwreck, for reasons that escape me.

http://www.randomhacks.net/articles/2007/03/10/haskell-8-way...


I 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'


If you read the comments, the author wrote a comment that basically says this. Exceptions should be used for fatal errors, not for recoverable errors.


I 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


This 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-4ff... [1] http://blogs.oracle.com/jrose/entry/longjumps_considered_ine...


These 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?


That was boring. I like Damien Katz' [epic piece about errors and exceptions](http://damienkatz.net/2006/04/error_code_vs_e.html) much better.


Errors 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.


"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."

In C(++/#), yes.

In Haskell, there's two absolutely critical differences, which are worth pointing out, because while it's fun to raid Haskell for ideas it's important to understand the context from which they come or you'll end up thinking good ideas are just crazy. (I want to see other languages raid Haskell, but it ends up being very easy to copy the form and entirely, utterly miss the substance. I see the same problem with Erlang copiers.)

First, the compiler forces you to unpack the error. Now, you can still write code that discards the error or fails to handle it, but you can't simply forget.

Second, you can compose these together:

    bigOperation = do
        a <- eitherOperation
        b <- eitherOperation
        c <- someOtherEitherOperation
        return $ a + b + c
With the do notation in the Either monad, you'll get the first error in the sequence, or the final result; you do not need to handle each error individually. This is a choice you make on purpose; if you want to, you can, if you want to handle errors in some other manner, you can, etc, this is not "the" error handling technique in the language, it's just one that ships in the base library. Personally I don't much like it because people tend to use Either with a string in the error position, and I really hate it when strings are the base error type.

(Also, there is a bit of a problem composing together different bits of code that handle errors differently, which is a disadvantage of giving the programmer so much flexibility. The exceptions mechanisms may not be perfect but they are consistent.)

And, indeed, just copying the Either type into C(++/#) is copying the form and missing the substance. I don't know what the solution for C(++/#) is, none of those languages have rich enough abstraction to copy Haskell here.


IIRC in c# you can abuse selectmany and it's inline linq syntax to achieve more or less the same effect. (google "monad selectmany" for examples)


groovy helps a bit with this when you use the jvm -- lots of their operators are null aware [1], ie

  max([3, null, 4]) == 4
and they have a null-safe dereference operator [2], so returning null is actually reasonable as an error

  def user = User.find( "admin" )           //this might be   null if 'admin' does not exist
  def streetName = user?.address?.street
Collapsable to:

  def streetName = User.find("admin").?address.?street
It's not perfect, but it's a lot better than plain java. Compare to:

  String street = null;
  User user = User.find("admin");
  if (user != null) {
    Address addr = user.address;
    if (addr != null)
      street = addr.street;
  }

  // or even
  String street = null;
  if (User.find("admin") != null && User.find("admin").address != null)
    street = User.find("admin").address;

    

I think one of my biggest complaints -- and the reason I think Gosling is a moron -- is how programmer unfriendly java is. Some syntactical sugar as above, so you don't have to nest null checks, goes a long way towards making languages more friendly to devs. Even simple things like in ruby being able to say

  1_000_300_233 == 1000300233
makes it a lot nicer to read big numbers. Sigh. If Gosling actually wrote code instead of doing whatever he wastes his time doing, you'd think that's the sort of thing you could just add to the language.

[1] http://groovy.codehaus.org/Null+Object+Pattern

[2] http://groovy.codehaus.org/Operators#Operators-SafeNavigatio...


As for variance of Exceptions, that's a pipe dream. Imagine a system that has a subsystem that looks something up in a 'database'. ... or it could be using something like JDBC (which could throw it's own exceptions).

Some languages try to address this by wrapping exceptions. For example, exception chaining in Python:

http://www.python.org/dev/peps/pep-0344/

The code that uses the subsystem will then be able to catch a meaningful exception (such as CannotCreateDatabaseTable), while for diagnostic purposes it is possible to check out or log the inner exception (such as NetworkTimeout). It's a compromise between "dumbing down" the exception (higher-level information is added, even) and making it impossible to anticipate the possible errors.


Java and C# have inner exceptions built into the base classes and it's not at all hard to implement inner exceptions in other languages like PHP.

And you're right, this is a good compromise.

When it comes to exceptions (and many other things) you realize some boundaries are more important than others. If you're building, say, a database access library that has a well-defined API, you are better off defining a good set of meaningful exceptions.

Inside a particular module, however, creating new exception classes tends to create more problems than it is worth. I can't tell you how many times I've seen basically the same exception defined three or four times by different programmers in different parts of the same system.

Even when you look at the "better" examples of exception hierarchies the results are frequently depressingly bad. For instance, many database access APIs just give a SQLException or the equivalent thereof. Some of them will add a huge range of Exceptions that are pretty superfluous such as InvalidTableNameException, InvalidColumnNameException, YouViolatedTheTypingRulesOfDatabaseXException (all of which should be DontProgramaticallyGenerateSQLException) but they never seem to have a DuplicateKeyException which is the one case where you actually might want to do something different from all of the others.


I haven't programmed Java for a while, so I missed that development. Back when I learned it this didn't exist yet, or at least was rarely used.

Yes agreed, database libraries seem to be notably bad in exception reporting. In Python, even though SQL is wrapped in a general layer, exceptions are still different per engine. MySQL has one exception with an error code which makes it very hard to distinguish between even a duplicate key and a network error.

Another mistake that many libraries make is that they make a generic 'FooLibraryException' and subclass all their exceptions from there, instead of from where it makes sense (IoError, KeyError, MemoryError ...)

But even with those issues, I hugely prefer exceptions to the 'check return value after every call'.


"Another mistake that many libraries make is that they make a generic 'FooLibraryException' and subclass all their exceptions from there, instead of from where it makes sense (IoError, KeyError, MemoryError ...)"

Actually, this isn't black and white. When I need to create more than one exception class for a library, I prefer to avoid subclassing standard exceptions to prevent that kind of confusing situation:

  try:
    afile = open('myfile.txt')
    content = afile.read()
    my_library.do_something(content)
  except IOError:
    # recover from afile.read() failure
If my_library was returning a IOError, you would not know if the problem came from your code or the library code. Moreover, this would leak implementation detail.

Of course, you can always get around this by wrapping each library call within a try/catch block, but that does not scale well if you make many calls interspersed with your code.


Well, at least from a maintainability perspective it's more clear to do

  try:
    afile = open('myfile.txt')
    content = afile.read()
  except XXX:
    (handle exception)
  else:
    (continue here using content)
If you want to capture the exceptions from a certain call. Otherwise, my_library.do_something might start raising a IOError somewhere in its bowels later on, and you have a confusing debug process.

When you find that you have a lot of similar or even repeated try/catch blocks, using 'with' context managers might be a good idea.

My point was that an IOError is an IOError, wether it's a MongoDBIOError or a MySQLIOError. Yes, you leak some implementation detail (namely, that I/O is used) but do do useful diagnostics you need some (abstract) implementation knowledge... network error: is your router plugged in? io error: is the disk full? keyerror: implementation bug? and so on.

Hm maybe this would be a case for multi-inheritance exceptions? MongoDBIOException is both and IOException as a MongoDBException. Don't even know if it's possible, or wise :)


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.

I like the idea (was it from Code Complete? Can't find the reference now) that your exception class hierarchy should be cross-functional. i.e. instead of the Logger throwing instances of LoggerException, the DB code throwing DBExceptions, etc, your exception hierarchy would reflect the concerns you mention: Transient error vs. bad data etc.


I 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.


The 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.


What'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.


The 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.


Agreed. 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.


The 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.


Gaaahh.... 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)


Look 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: