Hacker News new | past | comments | ask | show | jobs | submit login
The trouble with Checked Exceptions (C# architect) (artima.com)
51 points by davedx on May 24, 2012 | hide | past | favorite | 76 comments



The crux of the question:

> Bill Venners: But aren't you breaking their code in that case anyway, even in a language without checked exceptions? If the new version of foo is going to throw a new exception that clients should think about handling, isn't their code broken just by the fact that they didn't expect that exception when they wrote the code?

> Anders Hejlsberg: No, because in a lot of cases, people don't care. They're not going to handle any of these exceptions. There's a bottom level exception handler around their message loop. That handler is just going to bring up a dialog that says what went wrong and continue. The programmers protect their code by writing try finally's everywhere, so they'll back out correctly if an exception occurs, but they're not actually interested in handling the exceptions.

> The throws clause, at least the way it's implemented in Java, doesn't necessarily force you to handle the exceptions, but if you don't handle them, it forces you to acknowledge precisely which exceptions might pass through. It requires you to either catch declared exceptions or put them in your own throws clause. To work around this requirement, people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody.


Exactly. I think the 2 key failings of the checked exception idea (there are others, but not as important) are

1) Precise exception specifications leak implementation details. This will bite you badly, e.g., when you want to change the implementation from under a published API.

2) The times when we only care that some exception occurred vastly outnumber the times when we care about the exact types of exceptions that may occur. Checked exceptions don't add any value to the common case, in fact they make it worse, because generic exception handlers tend to live many levels up the call stack from where exceptions are thrown(1), and therefore they "aggregate" large amounts of code that that can collectively throw a huge range of exceptions. If you were to carry around detailed "throws" clauses, they would be a mile long.

The funny thing is, all solutions that solve the problems created by checked exceptions do so by subverting the mechanism one way or another. So you're better off without checked exceptions in the first place.

I've always been interested in this discussion because it's one of those cases where an idea seems very good on paper, only to turn out quite bad in practice.

(1) This observation is what makes exceptions such a useful mechanism in the first place.


There is one part of the idea that I'd like to salvage, though, and that's having some clear documentation right there in the code about what kinds of exceptions I can expect to see a procedure throw.

At the very least it'd be useful for when I'm wearing my "DLL author" hat, since it'd give me an easy way to look through the public interface and make sure exceptions won't be causing unrighteous leakage of implementation details for the end-user. I'd rather not put users in a situation where they have to look at the framework source in order to interpret an exception. It's kind of irritating when that happens.

I think it's really not something that should require special language statements, though. A static analyzer should be able to automatically figure it out.


I'm in Hejlsberg's camp on this. Exceptions should rarely be handled. If a non-happy-path use case causes exceptions, you're doing it wrong: exceptions should not drive your program logic; you should be proactively checking or guarding for those conditions. Exceptions are for things you didn't foresee, and ergo are not anything you're going to be able to "handle" other than by bailing out as cleanly as possible.


As a long time Java programmer, who has been writing C# for the last year and a half, I think leaving out checked exceptions from C# may have been a bad idea. What they've done is to identify that checked exceptions are a pain in many cases and simply removed them without providing a proper replacement. This particular quote demonstrates what I mean:

"To work around [the need to declare what you intend to do with each exception] people do ridiculous things. For example, they decorate every method with, "throws Exception." That just completely defeats the feature, and you just made the programmer write more gobbledy gunk. That doesn't help anybody."

Ok, so what you did instead was to effectively put "throws Exception" on every method by default. You actually implemented the workaround you've just criticised into the language?

And what's the effect of this - whenever you call a method in an API, you have no idea what could go wrong with it, so you can't write code to handle it. It wouldn't be so bad if a method's potential exceptions were documented, but they never are. So you end up in a situation where you just code for the simple "happy case", run it, see what breaks, put some exception handling in (for just Exception) where you can, run it again, etc. It's a dreadful way to write code.

I'm not saying Java's checked exceptions were great - far from it - but C#'s "solution" is terrible. The number of times you end up with a NullException because some method failed somewhere deep in your code and something else didn't get initialised is unreal. If you don't believe me, try working with Sharepoint for a few minutes, and that's Microsoft code.

[edit - spelling]


He addresses your point in detail in the interview:

C# is basically silent on the checked exceptions issue. Once a better solution is known—and trust me we continue to think about it—we can go back and actually put something in place. I'm a strong believer that if you don't have anything right to say, or anything that moves the art forward, then you'd better just be completely silent and neutral, as opposed to trying to lay out a framework.

...

I'm a strong believer in being minimalistic. Unless you actually are going to solve the general problem, don't try and put in place a framework for solving a specific one, because you don't know what that framework should look like.


Ok, so what you did instead was to effectively put "throws Exception" on every method by default.

That implicit throws clause was already there in any language that contains unchecked exceptions for things like dereferencing a null pointer, or assertions, etc. So, no, that's not what removing checked exceptions does. Personally, I'm a big fan of Guava's Throwables class. The java I write these days transmutes all checked exceptions into unchecked wrappers at any API boundary that tries to force them on me. And, no, this doesn't mean I'm only coding for the happy case. Rather, I'm choosing where and how to handle the sadness.


It depends what standards you hold the library to. Null pointer exceptions and other runtime exceptions are can be treated as errors - something which needs fixing in the code, rather than something that can be handled. A world of difference.


And that is still the case with the Guava Throwables strategy, which only effects how checked exceptions are propagated.


I can't help feel that warnings and/or a strict mode to enable something similar to checked exceptions would be of help for those that wanted it.

Regarding null exceptions, they are runtime exceptions in Java so it's much the same there. It would be very annoying if they were checked exceptions.


perhaps you can use spec# to specify contracts for methods be even more "checked" http://research.microsoft.com/en-us/projects/specsharp/


I have thought quite a bit about checked vs unchecked exceptions and come to the realization that there is a fundamental philosophical problem that checked exceptions can never overcome.

The idea with checked exceptions are that they should be used for errors which the caller should be forced to handle. But this property is entirely dependent on the context the function is being called from! For example, if the user is specifying a filename for a file, I should be expected to catch the error and display feedback to the user. However, if I just wrote the file to disk 5 seconds ago, I shouldn't be forced to trap the error!

The crux of the problem is this: the severity and recoverability of an error is known only by the caller of the function, not the function itself. Checked exceptions attempt to foist this information into the function itself, where it is unknowable and generally inaccurate. Checked exceptions are a misfeature if I ever saw one.


I agree with the C# designers' reason that checked exceptions are not a good idea, but I don't agree with yours. In other words, your argument is different than theirs, and I don't agree with it. (Although I still agree with your conclusion.)

The crux of the problem is this: the severity and recoverability of an error is known only by the caller of the function, not the function itself.

I agree with that statement, but not that it is the crux of the problem with checked exceptions. That statement is an argument for general error reporting - it need not even be exceptions. Simply, the function that encountered the error does not know what to do about it, so it must report it on up the stack. We could just as easily accomplish that with error return values - I'm not saying we should, just that I think your argument is too generic to support your conclusion.

Checked exceptions attempt to foist this information into the function itself, where it is unknowable and generally inaccurate.

And that I actually disagree with. Checked exceptions require that the calling function acknowledge that an error has occurred, it does not require the calling function to handle the error. The calling function could easily catch the exception and rethrow it, or just add the exception to its own throws clause.

The C# designers' argument is that what I described is generally what people want to do, so why not just make it the default? That default is unchecked exceptions.


Checked exceptions require that the calling function acknowledge that an error has occurred

But why do they require the client to acknowledge the error? Better stated, why would one use a checked exception versus an unchecked? The general wisdom (and perhaps this is where I'm mistaken) is that checked exceptions generally indicate a more recoverable error. This is why checkedness doesn't belong in the function itself: it's an indication to the caller of the error's recoverability, which is completely unknowable.


Note that I was not arguing for checked exceptions. Rather, I was arguing that your argument does not support your conclusion.

With that said, I think it may be reasonable to say "You may be able to recover from a missing file, but no one can help you if this pointer is null." It's the difference between a logic error in the program (null pointer exception, array out of bounds exception) and configuration errors (missing file, lost connection). Checked exceptions are typically explicitly thrown by the called function; unchecked exceptions are typically encountered because the called function itself had an error. In other words, checked exceptions are when the called function recognizes an error, and throws it up to its caller.

So, I am able to distinguish them myself. But I'd still rather not have checked exceptions. The mentality of "acknowledge all errors" makes more sense in the error-code model, as seen in C programs. In that case, if you don't at least check for and report all possible errors that can arise from calling a function, then they will never get reported, and your program will silently be wrong.


Note that I was not arguing for checked exceptions.

Right, I understand that. And I was merely pointing out a flaw in your argument against my argument.

You may be able to recover from a missing file, no one can help you if this pointer is null

My argument (which you seem to be doing a great job of ignoring) is that this distinction is completely unreasonable. As the calling function, I am passing in the pointer, therefore I am the only one who can know whether the null pointer is a recoverable issue! Consider the ArrayOutOfBoundsException - perhaps the array bounds are passed in from the user interface, and the error should be trapped and reported to the user. This checked / unchecked distinction is simply nonsensical from the called function's point of view.

Checked exceptions are typically explicitly thrown by the called function; unchecked exceptions are typically encountered because the called function itself had an error

This is not typical, is not how you are using them in your examples, and also makes no sense. What is the difference between encountering a particular error within a function or a sub function? What if the logic in the function is later extracted into a subfunction?


I'm sorry if you feel that I'm ignoring your distinction. I'm not trying to ignore it, but argue that I think we can distinguish between "likely recoverable" and "likely not recoverable" for most cases. Nothing prevents you from catching unchecked exceptions. So if you know that you're passing in a may-be-null-pointer-and-it's-okay, then you can do an unusual thing and catch that exception. I agree that we can't classify all exceptions as recoverable or not recoverable from inside the called function, with full accuracy. But I disagree that we can't make reasonable guesses that will be true in most cases.

The function may encounter an error but it is not the caller's fault - that's what I mean by "the called function itself has an error."


Sure, we could guess at all sorts of things from within a function. We could guess that the function is running from a terminal, and simply print the result of the function to stout. Hell, we could guess what the caller is going to do with the result of our function and just do that instead! Why even have a caller at that point?

Of course I'm using hyperbole to point out the absurdity of making this distinction, which on the surface may seem reasonable. The fact is, however, the fewer assumptions a function makes about it's calling context, the better. The whole point of functions is that they are to be reused in ways the author may not have intended. The checkedness of an exception is an assumption about the calling context of a function which should not exist.


I don’t see how checked exceptions impose decisions of severity & recoverability on a function. If you are a function, exceptions are how you notify your caller that something has happened for which you are not responsible.

Checked exceptions are not about imposing responsibility, but about disclaiming it and deferring it to a higher authority in a type-safe manner. And the issue seems to be more syntactic than philosophical; Haskell models checked and unchecked exceptions as a monad (or as a monad transformer for running exceptional code in another monad) and it feels very natural.


All exceptions (checked or otherwise) disclaim and defer the error in a type safe way (unchecked exceptions can be declared in the throws clause too!)

The difference is that we can require them to be dealt with by the caller, which is generally thought to be an indicator of the recoverability of the error (I should not have said severity, sorry) which does not belong in the function as it makes no sense in the context of a function.


I agree with you but converting the masses, especially in the workplace, is tough.


Usually those architecture astronauts have a BaseException class... just make it extend RuntimeException and you're done!


I am _very_ glad that C# does not have checked exceptions. Anders is completely correct in that the vast majority of the time individual methods do not handle specific exceptions, they just roll things back and pass the exception up the way. Adding exception attributes in all of these places would be a massive pain.


In my experience many C# programmers simply fail to add much exception handling code at all because the compiler doesn't force them to. The net result is code which breaks in spectacular fashion when the first unexpected condition is met.

Checked exceptions are a nuisance (I'm actually glad Scala doesn't enforce them vs Java), but often leads to inexperienced C# devs writing very fragile apps.

With C# you have to examine docs and source to identify exceptions which could be thrown. Many programmers don't bother, leading to things breaking. In Java the compiler forces you to think about it. I'm not in favour of checked exceptions, but the arguments against them tend to be a bit simplistic.


The net result is code which breaks in spectacular fashion when the first unexpected condition is met.

Which is generally what you want to have happen. Failing fast is a good thing. If your code does anything other than "stop executing" after encountering an exceptional situation, it should be very something carefully considered and well-thought-out. With checked exceptions, the "I'm in a hurry to get this working" version of the code often ends up looking a lot like:

  try {...}
  catch(ExceptionType ex){ //TODO figure out what to do here }
  ... keep going ...
The danger is what happens in that "... keep going ..." section - the code is working under faulty assumptions and that can lead to a lot of non-obvious but horrific bugs. In the unchecked "I'm in a hurry" version, the program simply crashes at that point.

So I think the C# design choice leads to the least pain under bad conditions. I'd rather have a fragile application than one just robust enough to write bad data to my database.

That said, it's not perfect. Any sufficiently lazy/stupid programmer can write the equivalent of "On Error Resume Next" in any language.


> In my experience many C# programmers simply fail to add much exception handling code at all because the compiler doesn't force them to.

Still better than:

    try {
        // Do broken stuff
    } catch (Exception e) {
        throw new RuntimeException(e); // TODO: Add a domain-specific runtime exception so we can actually catch this
    }
I find this or something like it[0] spread across every Java codebase I find myself mired in, and I read a lot of Java these days (much to my dismay, but so it goes).

[0]: Actually, that's not fair. Most people don't bother to include the TODO.


You meant to say "catch (Throwable e)", right? But, yeah, I've had to write that block too many times.

Throwable will catch Errors as well as Exceptions, such as when a constructor called from a dependency injection framework fails. It's kind of irked me for a while that I had to know that. I would have rather had "catch" work on anything, with the option to check the class and rethrow once in a great while. That is, in the few places where the error checking/logging actually belongs (as stated in the article, with which I obviously agree).

There is indeed great irony in Java code littered with catches, which then falls out and kills the main loop without logging anything. (I recently had to diagnose such a case in a system at work over the phone -- sure enough, that's exactly what was coded)


You are not supposed to, and don't need to, catch Errors. The only exceptions you need to worry about as far as checked exceptions go are classes that inherit from Exception but not RuntimeException.


I kind of agree that Java's approach forces people to think about error handling. But really that's it's only virtue. Once people do think about error handling in their program, the best approach almost inevitably involves subverting the checked exception feature.


The reality is that programmers do not have the time to handle every single possible exception. I reckon every few function calls you make could potentially trigger an exception. You generally just have a catchall (or not, and let the program abort), and then catch certain specific exceptions that are common enough.


Yes unchecked exceptions are nice. However it would be nice if the compiler could enforce documentation of possible exceptions raised by a method. Something like the throws keyword of Java with unchecked exceptions.

That way you (through the IDE) could know at any point of your code what exceptions could happen (and not have to guess or go through all the called code to identify them).


Absolutely. I handle the ones that are common enough to be worth investing time in (or have severe enough consequences). The rest recover as best they can and tell the user "Something bad happened. Call the helpdesk." (while logging a stacktrace).


> I reckon every few function calls you make could potentially trigger an exception.

One Java module I had the misfortune of writing theoretically had to deal with checked exceptions on the majority of its calls, and the only possible response to virtually all of them was to crash, because in that context, it could only have meant someone had either ripped the DRAM off the board, or placed the board in a particle accelerator.


In that case, why don't you just put 'throws Exception' on all your methods?


I said "theoretically", did I not?

And there was nothing particularly special about the module aside from the extreme concentration. Every piece of Java I've ever written or even looked at has this same problem on a reduced scale.

My point is to show how utterly broken and worthless the concept is. Either there's a ton of exception-"handling" boilerplate that does nothing useful, or there's "throws Exception" everywhere, itself useless boilerplate that exists only to tell the compiler to buzz off.


Attributes? Why not just

throw new RuntimeException(exception);

Edit: If there weren't exceptions for every mundane thing I think I would prefer no checked exceptions also.


Because you've in essence bypassed the entire checked-exception system anyway?


How? You acknowledged that the exception exists. You didn't do that by putting 'throws' on everything.


> You end up having to declare 40 exceptions that you might throw. And once you aggregate that with another subsystem you've got 80 exceptions in your throws clause. It just balloons out of control.

What he completely ignores is the possibility to wrap exceptions to either aggregate them or to convert them into unchecked exceptions.


But that's exactly one of the main points of the detractors of checked exceptions (of which I'm one): the only sane way of working with it is to subvert it.

Whether you convert the throws clause to a common supertype (which very soon converges to the base type Exception), or wrap everything in a RuntimeException, you are effectively emulating a language without checked exceptions. So what was the point in the first place?


"the only sane way of working with it is to subvert it."

Perhaps.

In my projects, I use wrapped exceptions to convert "what blew up" exceptions into "what to do about it" exceptions. A poor man's event routing. For example, I wrote an ETL workflow thingie that would retry, reset, restart, backoff (throttle) depending on what was failing.

I'd be totally game for pre declaring what exceptions your code will catch, harkening back to the days of "on message do this" type programming. Then the compiler can tell verify that all the thrown exceptions get caught by somebody.

Meanwhile, I've never had a problem with checked exceptions.

Methinks the real cause of angst over checked exceptions are all these mindless frameworks, APIs, libraries, strategies, design patterns, aspects, inversion of common sense, injection of nonsense, etc.

The complexity borne of the manner in which Spring, JPA, Hibernate, connection pooling, aspects, MVC, blah blah blah attempt to hide, obfuscate, decouple, mystify, pseudo architect "solutions" ... well, it's just insane.

Someone upthread off-handedly noted the irony of declaring all these exceptions only to have the main event loop eat an exception without any logging. Story of my life. Debugging silent death failures really, really sucks.

Which is why I work as "close to the metal" as possible and worry not about unchecked exceptions.


You can work with it or route around it when it doesn't make sense for you or the particular context i.e. it gives you choice. Additionally, I regard checked exceptions as automatic documentation.


Exactly. I've been using Java for 13 years and have yet to see a method signature with more than a handful of exception types thrown, let alone 40 or 80. I've told you a millions times not to exaggerate ;)


Thanks ! I just thought i was missing somehting , that Heisenberg did not mention exception wrapping. The funny thing about that technique is, that a lot of people do know it, they ususlly just print a strack trace and ignore the exception...


I sense some uncertainty on how to spell his name...


oops sorry for the typo ;)


For old articles, please put a date in the title. In this case, (2003).


If it was something that was time-dependent, I'd agree, but in the case of an interview talking about the design of two languages in current use,it's timeless.


That's all the more reason to put the date there. It signals an article that is relevant today despite being almost ten years old. The argument is considerably more impressive when you realize that it happened ten years ago and is still relevant, but I didn't know it was a timeless classic until I saw this comment thread.


Checked exceptions are fine.

The problem is, that they are not composable and most languages have no syntactic sugar for dealing with them as Monads.

Other checked error mechanisms like Either or are much easier to manage.

I do hope e.g. Java sometimes in the future gets syntactic sugar to deal with checked exceptions as Monads.


The Java Programming Language's checked exception feature is fundamentally broken. All newer Java specifications have acknowledged this and utilize un-checked exceptions.


All but one (and arguably the most important) Android.

Android is full of brand new checked exceptions.

And I do what I always do with checked exceptions:

    catch (e) {
      throw new RuntimeExeception(e);
    }
(you can't keep adding throws clauses because the compiler won't let you add them if the interface you implement can't handle them).


I have long wondered about the syntax of exceptions - if it was:

  void function()
  {
  try:
    function_body...
  catch(Exception e):
  finally:
  }
By leaving the function body in the same indentation as it would be without exception handling, this might help to make the code a little more readable than:

  void function()
  {
    try
    {
      function_body...
    }
    catch(Exception e)
    {
    }
    finally
    {
    }
  }


My mind parsed the first code block as a mix of Python and C. IMO, the later would be more readable since it involves parsing only one rule that the eye is already used.


It reminds me more of C, where labels and goto are often used for error handling (labels using the same `:` syntax).


Now that you you mention labels, I see the similarity to C. I might have been influenced by OP's reference to use indentation to make code more readable instead of using braces.


That would be useful if you only need a try/catch at method level. If you need two try/catch chunks in a method then that wouldn't work.

I do agree that having try/catch at method level would be useful in many ways - as an additional piece of syntax.


I tend to agree that Java's checked exceptions are annoying (hello SQLException). However, when I used C# I was annoyed that the VS intellisense and the C# generated documentation didn't tell me what exceptions might actually be thrown! In practice this meant just not handling anything at all.


That's because the developer didn't put the exception into the documentation. But at the same time you really shouldn't rely on that because something down the line could throw an exception that isn't covered in the documented exceptions.


The thing that really annoys me about checked exceptions is that map/reduce/fold like functions throw anything by definition. Basically as soon as you allow any general type function as a parameter, you need to mark yourself as throwing everything.

That is really limiting in some scenarios.


Exceptions are a bad idea. Even in the more saner languages such as python, they tend to obfuscate needlessly.


so you prefer to decentralize your exception handling code ? how do you scale that ?


The problem is that when exceptions are available, people will start using them for non-exceptional things and (the worst case) basic control flow.

Case in point from a popular framework, Django: the standard way of getting an object from the DB via the ORM is Class.objects.get. This method either returns a single object or raises on exception if there are zero rows in the db or another exception if there are two or more rows. It may also raise other kinds of exceptions.

Now, it's clear that having zero rows is not very exceptional, and even >1 is somewhat debatable. Note especially that this is not just some weekend project by a nobody, it is a framework that is widely used and respected.


>> and (the worst case) basic control flow. I call it "java-way goto"

I can't agree about Class.objects.get - method must return exactly one row by known pk value.

So it perfectly sane to raise exception for zero rows.

Class.objects.filter is working the way you want.


Except that get accepts as parameters non-unique non-primary fields too.

I have every reason to believe that the Django devs are capable people who thought about this and landed on this specific usage with a clear rationale... but it still is a misuse of error handling capabilities of a language. And a sort of misuse that everyone else does sometimes as well.


I agree with your first paragraph but not with the Django example. User.objects.get(id=1) failing is exceptional because you are asking for an object with a specific and unique property. Otherwise, you would be using User.objects.filter(age>20) (which doesn't throw an exception).


I might kinda agree that exact queries against primary keys not having results (or having more than 1) would be exceptional, but this actually happens for every other field as well.


Sometimes you want non-local transfers of control, beyond just exceptions. Python uses exceptions as both error conditions and signals.

It's seemingly nearly mandatory in discussions regarding exceptions to bring up Common Lisps's condition system, which is a superset of exceptions. I've included a link to a chapter from a book on Lisp.

http://www.gigamonkeys.com/book/beyond-exception-handling-co...


that's not a Django problem; it's a Python problem. Using exceptions for control flow is idiomatic in Python. Case in point: a generator is supposed to raise a StopIteration exception in order to signal termination.


Go has no exceptions. Instead, functions can return multiple values, one of which is an error, and you check the error value on invocation. It's tedious and awful and makes your code very ugly and it's absolutely correct and I love it. It takes some getting used to, but once you do it for a while, you realize that exception handling systems are broken and unnecessary. E.g.:

    func MaybeAnInt() (int, error) {
        if FAIL {
            return 0, errors.New("this is the error message")
        }
        return 42, nil
    }

    x, err := MaybeAnInt()
    if err != nil {
        // handle your error
    }
    // normal shit here.


That is the best argument against GO I have ever seen.

You now have to mix up your exception code and your normal code (or, as I suspect most people will do, ignore the error return value).

Did they kill stack traces too?


You cannot ignore the error return value in Go.

If you would think a bit more broadly, you might also see this so that all the error handling code is visible, explicit and right there with the actual logic, and not hidden away what might be (and often is) multiple layers of modules.


>You cannot ignore the error return value in Go.

that's not true. You can ignore the second parameter by explicitly naming it with an underscore, like this:

    x, _ := MightFail()


Well ok, you can explicitly ignore them, but not by being negligent.


it sounds bad in theory, but in practice it turns out to be quite good. It's actually the opposite; because you know that there's no exceptions, control flow is very obvious. Whether a function does or does not return an error is a part of its signature. You always know, when calling a function, if it may or may not return an error, and it is up to the caller to decide what to do with that error. As a result, there's no control flow pitfalls like forgetting to close a filehandle because an exception was thrown in some function that you didn't know could throw an exception.

>Did they kill stack traces too?

no, we have stack traces.

Also, it's Go, not GO.


that does not scale. you can have hundreds of calls that require error handling inside your code. considering your example you would get the section "//handle you error" hundreds of times. sprinkled all over the place.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: