Exceptionally Challenged
Listen to this article
I've finally finished my foray into C# and I suppose it would be obvious to all that I was rather less than impressed by some "decisions" that were made by the C#/.Net development team(s). Most noteably, the lack of checked exceptions.
So it was with much joy that I stubled upon this article, on the Artima web site. An interview with Anders Hejlsberg, the lead C# architect and a distinguished engineer at Microsoft, on "The Trouble with Checked Exceptions".
Fantastic I thought! At last I'll get some sensible, logical, coherent and rational explanation for some of the stuff that feels so uncomfortable to a Java weenie such as myself.
If only.
Thinking that maybe it was just me, I forwarded the link on to a good friend of mine James Ross who knows quite a lot about the Microsoft world. But alas, he drew similar conclusions. (Portions of our conversion have been included here)
No, it is sad to say but Mr. Hejlsberg shows his true colours and in the process makes me even less impressed with .Net.
I'm usually not fond of taking an argument and disecting it line-by-line. It's often too easy to take stuff out of context and often leaves one open for a counter argument in a similar vein ultimately leading to a flame war. But on the basis that Mr. Hejlsberg wouldn't know me from a bar of soap let alone read my blog, why not.
So without further ado:
"...I completely agree that checked exceptions are a wonderful feature. It's just that particular implementations can be problematic. ...I think you just take one set of problems and trade them for another set of problems."
So he likes them but thinks that it's the implementation of them that's wrong. Um...I must be really stupid but HOW ELSE DO YOU IMPLEMENT CHECKED EXCEPTIONS? I can't think of a simpler way than in Java. In fact I can't think of any other way really. Please enlighten me!
Skipping foward a bit, he reckons
"The concern I have about checked exceptions is the handcuffs they put on programmers ... It is sort of these dictatorial API designers telling you how to do your exception handling."
Well how about being dictatorial about what methods you can override? In C# a method cannot be overriden unless you declare it as virtual.
He then goes on to say:
"Let's start with versioning, because the issues are pretty easy to see there. Let's say I create a method foo that declares it throws exceptions A, B, and C. In version two of foo, I want to add a bunch of features, and now foo might throw exception D. It is a breaking change for me to add D to the throws clause of that method, because existing caller of that method will almost certainly not handle that exception."
Dude, try making an API that will be able to respond, like wrapping those FOUR DIFFERENT EXCEPTIONS into one abstract package-level exception, you clown! This argument doesn't stand up to scrutiny because the same thing can be said about method parameters and return types, so why not get rid of them too!?
In fact he says:
"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...And so, when you take all of these issues, to me it just seems more thinking is needed before we put some kind of checked exceptions mechanism in place for C#."
Right! You're somehow going to go back and change the way exceptions work!? Get real my dear architect. Whatever happened to your argument about versioning issues?
"...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."
AHA! Now we begin to see the truth. He doesn't actually like catching exceptions after all. I dont know what planet he is on but really.
I can just see my nuclear power station monitoring system catching a CoreMeltDownException in the "main message pump" and bringing up a dialog kindly informing the operator he might have a problem. Meanwhile, blissfully unaware, the rest of the system continues on removing the control rods from the core.
And what's with the finally's everywhere to "protect their code"? Holy cow. Don't let this man near any system I'm likely to work on. I don't know about you but I rarely need to write finally statements except maybe in some integration code. Who manages resources this way these days? I'm not saying I dont use them but my code surely isn't as littered with them as he implies it would.
Then he loses all credibility by delving into some spurious arguments on "scalability":
"The scalability issue is somewhat related to the versionability issue...Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with. 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."
Get out of my face! I've never EVER seen this, even in the worst code bases I've had the misfortune to work on. For a start, the numbers he quotes are just ludicrous. But again I say, how about wrapping those FOUR DIFFERENT EXCEPTIONS into one abstract package-level exception!? How about designing a language and libraries that allow smart people to do smart things instead of one that makes it even easier for stupid people to do stupid things?
What's probably even worse, is that now my SQLException propogates all the way from my database layer to my GUI layer. Some "clever" developer realises that he can catch the SQLException, check the ErrorCode for 9901 which he happens to know means a key violation (or whatever) and display some nice message to the user. Whatever happened to encapsulation and abstraction? I mean, I know abstractions can leak but this is Niagra Falls baby!
"But that said, there's certainly tremendous value in knowing what exceptions can get thrown..."
Excellent. Well at least we agree on something. Damn shame that because C# has no way of declaring what exceptions are thrown, I have no way of knowing if there are any to be caught, let alone what they might be. Unless of course it says so in the documentation. Documentation gets out of date VERY QUICKLY.
When I started my porting effort, I was using the mono doco which is still incomplete. This meant in some cases I had no idea if nor what exceptions might need to be attended to. What's even worse, none of the exceptions seem to extend any sane base class so if I decide I need to catch more than one but treat them all the same way (say some kind of IOException) I have a 3 catch clauses!
"But I think we can certainly do a lot with analysis tools that detect suspicious code, including uncaught exceptions, and points out those potential holes to you."
Ahhh. Of course (slap myself on the head) why didn't I think of that? After all what we really need is yet another tool! In fact let's create an AOP Library for C#. Yeah. Then we can inject code into existing libraries to catch exceptions...the possibilities for this are endless ;-)
But seriously, exceptions form part of your API just like methods, interfaces, parameters, abstract data-types, etc. If you think about them in this way, they stop being scary and start being useful.
Some things to remember when using exceptions:
- Don't use exceptions for flow control;
- Create sensible exception heirarchies;
- Never throw more than one class of exception from a method unless you are forced to. That is, if you throw more than one type of exception, make sure they all extend a common base class. That way clients can catch them and/or re-thrown then easily;
- Avoid throwing someone elses exceptions. Eg. Don't throw SQLExceptions from your middle-tier. Wrap them. There is usually no reason for the GUI to know there was an SQLException.
Comments
You know, I've heard uneducated people argue about the checked/unchecked exception debate, and I've heard educated people argue about the checked/unchecked exception debate. Hejlsberg reeks of a lack of education in that department; all of his arguments seemed to be based in a world created by brief glimpses of reality; much like a green programmer.
Posted by: R.J. | January 28, 2004 11:37 PM
That part that stunned me the most in his interview was the scalability bit.
Because he doesn't seem at all concerned about the fact that I'm writing a piece of code that might throw 80 different exceptions. His only concern is that I might have to actually state it explicitly.
God forbid that I might have to be explicit!
Much better to have a piece of code that could throw any one of 80 different exceptions (WTF?) and is silent about it!
One of the best things about checked exceptions is that when you get to a piece of code that throws 80 exceptions, it smells.
In java:
public void clickSubmitButton() throws SQLException { ...
hmmm... code smell... why should clicking a button throw an SQL exception?
In C#:
public void clickSubmitButton() { ...
no code smell, but it still throws the SQL exception, it's just hidden away nicely, so that the programmer doesn't have to care about that fact that her code just did something "exception"ally bad.
*sigh* That's what you get when you take someone who is famous for designing Delphi and pretend he know something about large scale enterprise apps.
Posted by: Tim Vernum | January 29, 2004 12:52 AM
Ah, thanks for writing this up. Good analysis, and I like to see others bashing Hejlsberg. BTW, do you understand why Dr Dobbs gave him this award when C# was first released? He basically ripped off Java, removed some of the best parts from it, and added a bunch of things we don't want, and then Dr Dobbs gives him an award? WTF? Dr Dobbs really lost a lot of respect there.
Posted by: Mats Henricson | January 29, 2004 02:56 AM
For someone who started the piece off with I don't normally like to bash, just in case I take the articles content out of context, Simon you sure did get into the bashing swing quite quickly!
As an avid user of C#, I must say that firstly I like the language, but there are areas I think lack some thought! Check exceptions is one of those areas.
As a general argument to all of Hejlsberg?s comments - Checked exceptions give me the ability to either deal with exceptions, in place, or choose not to deal with them. I can still have a finally if I want. But it?s my choice to do so. I have a normal size brain and if armed with information I can make a decision to either do something or to ignore it.
With C#, I have a normal size brain, but I have no idea what is going to happen. So I spend my life thinking ?Maybe this could happen!?. My normal brain gets overheated quickly and I go into melt down and reach for the nearest Beer. At which point I go I?ll put a finally at the most outer point of my code.
Alternatively I could just continue with my beer and hope that someone writes a tool to tell me where exceptions are thrown!
My 5 Cents worth??.
Posted by: John Sullivan | January 29, 2004 07:26 PM
Mats - I'm with you. I have no idea!!!
John - Yeah I can be harsh sometimes but as I said, I don't do it very often. I'd have been happy if he just said "look I don't like them so I didn't put them in" instead of some lame half-assed response.
Either that or the man really is a fool but I find that hard to believe.
Posted by: Simon Harris | January 30, 2004 03:47 PM
I wonder... was it really his decision?
Consider that the presence of checked exceptions is something that a lot of developers in the MS camp find annoying (and some developers in the Java world, though not me). Perhaps the decision to not have checked exceptions was something imposed by MS management so as not to piss this group off...
This would explain why he can't adequately defend the decision; he's toeing the party line, not espousing his own belief.
Well, it's a possibility. :)
Posted by: Robert Watkins | January 30, 2004 09:16 PM
>> HOW ELSE DO YOU IMPLEMENT CHECKED EXCEPTIONS?
Consider a language permits a throws clause at the method, class or assembly (C#) level. The throws clause is treated as a contract with the caller. In the case where multiple levels of throws are present, the most restrictive subset is selected.
You are permitted three options. First, if you do not specify a throws clause, no guarantee is made about which exceptions are thrown. Second, you can provide an explicit list of exceptions and this means that no other exceptions will be thrown. Thirdly, a special "throws none" clause that indicates that no exceptions are ever thrown.
In the case of a class level throws clause, the limit would only apply to externally callable methods. Private methods would not have to meet the requirement since no external caller could directly access them in the first place. Note that memory exceptions during a new operation could still occur (the allocation error occurs before the class could do anything about it).
When the compiler determines that an unlisted exception could escape or if an implementation change could result in a new exception type escaping, a warning would be generated.
OK, what benefits would this have? Well, you get the basic advantage of knowing when your program (class, assembly) correctly states its exception obligations. Intermediate methods that do not participate in the exception handling process no longer have to have redundant throws clauses. You can reduce redundancy by specifying throws clauses at the class (or assembly) level.
For large numbers of possible exceptions, you could avoid having to wrap by omitting the throws clause. This makes handling easier since you no longer have to dig into the wrapping and can just handle it using a specific catch clause. General errors can still be caught using a generic catch of course. Wrapping would be used only when sematically meaningful.
This system would also note possible future issues since it checks to see if new exceptions would break the contract. This system also does not halt compilation when there is an exception related error, which makes one-off programming easier (scripts, proof of concept, one-time data manipulations, etc.).
Another nice thing is that it is upwardly compatible with C# today. It is almost compatible with Java, except that there are unchecked exceptions that existing throws clauses ignore.
Posted by: Grant | January 31, 2004 01:09 AM
Hey grant, Thanks for the comment. I had also thought about such a "solution". It does however have a few fatal problems.
Firslty, It requires me to recompile every piece of code I have to take advantage of it. As soon as I have a transitive dependency on a piece of code that I don't control I can no longer take advantage of it UNLESS I run a special tool over all my code to work this out.
What's worse is that I can never possibly hope to know what code I will be linked with at runtime because I may depend on 3rd party assemblies and 3rd party implementations of interfaces that acnnot be known to me until runtime.
So it may be backwards compatible but it's hardly a solution to the problem.
Cheers,
Simon
Posted by: Simon Harris | January 31, 2004 07:51 AM
> It requires me to recompile every piece of code I have to take advantage of it...
Yes and no. For code available in source form the compiler can analyze the possible exceptions. For recompiled binary assemblies, it can obtain this information from the metadata. For non-recompiled binary assemblies no exception guarantees are made. This is just as if all methods are unmarked.
This still helps you determine whether *your* exception guarantees are kept. If you make a gurantee, you have a list of exceptions you must handle or explicitly propagate and you have the responsibility to catch unknown exceptions.
> What's worse is that I can never possibly hope to know what code I will be linked with at runtime...
Indeed, you cannot know whether a different version of a dependency throws a new exception type. That is the intention of warning on the case where you make a call to a dependency that could in future throw a new exception type. If you make an exception guarantee and call into a non-source dependency, even if it states a guarantee, you're going to need a general clause in your try/catch. For source dependencies in the same assembly, the compiler can potentially analyze whether or not you can omit the general catch (and default to no guarantees if it can't).
Anyway, I'm not sure how viable the idea really is (that is for the likes of James and Anders). The point, I suppose, was that there are always alternatives to consider.
Posted by: Grant | February 3, 2004 03:15 AM
In Hejlsberg's article on checked exceptions in c#, I believe the error is an assumption on his part based on an original design decision of Microsoft's.
The universal wrapping message pump. My understanding is that windows has a message pump loop that all programs run with and that the "correct" (Microsoft) way is to send then catch exceptions in that pump.
Instead of catching them in the stack as I think java does.
Posted by: Joel Bushart | January 14, 2005 03:42 AM