Philip Rieck

Phil in .net

Considering Exceptions Harmful Considered Harmful


Joel Spolsky is a man whom I have come to respect because of his writing. I still do, and it will take more than one posting by him that I disagree with to change that.

But boy, do I disagree with his disagreement with exceptions. So do others (like Jesse Ezel in this post, Dare Obasanjo in this post, and with more brevity, Andy Smith with this post). So let me put in my two cents.

Let's say I have a function f(x), defined as 1/x. If I want to evaluate this over some domain, I may say y= f(x). Here's our basic function.... similar, you may say to a function in code: (lets stick to ints for input) ouble f(int x){ return 1D/x; }Great. Now what happens when I pass in 0?

Without exceptions, I have two choices.

  • Return a "guard" value, like 1.7x10308 that means "Error", and check for that each time I call f(x).
  • Change f(x) to
      int f(int x, out y)
      { if(x == 0) return 1;
       y = 1D / x;
       return 0;
      }

The problems with the first approach are many, so let's list a few:

  1. For each type of error I want to return, I "lose" a valid result -- what if that's the result I wanted?.
  2. What if the precision of a double changes? I have a hole in my results, not just at the end. Say hello to the return of "magic" numbers!
  3. Someone using my function may not read the docs (heaven forbid), and use my "guard" value as an actual return value in blissful ignorance. Oops, hope he's not graphing from -1 to 1.

Okay, so that's probably not the best way-- for this function, every possible return value is valid data, so let's use the second approach... Oh, no ... more problems!

  1. (joel mentions this one, but says it's better than the exception)I can't say g(f(x)) -- I must say
        result  = f(x, out y);
        if( result == 0)
         result = g(y);
        ...
        
    (when objects are returned, you lose the notation of  f(x).g(y) for something similar).
  2. What about String.Format, and other variable-argument functions? out function as the first parameter?
  3. Which brings us to conventions. Every other function in the world returns data as the return... and sometimes error codes. Are all my functions going to use out params, or only some of them. Please don't say "case-by-case"
  4. Shall we ask about propery gets? Array accessors? how do we put a return value on these and an error... are these special cases (I hate special cases!), or are they requirements for new language design?

Then we have (sorry..) more: What about errors written by me, the author of f(x). I don't check for overflow, so you don't know about it. And, what about memory problems, threads aborting and other "errors" that don't occur in a function, or that a function knows about.
These are both great cases for exceptions.. If I have a finally block, my cleanup code will be called, barring the machine shutting off or other acts of god (like stack problems), even if I didn't know about the possibility of an error occuring.  And better yet, I can't easily ignore the problem and hope it goes away.  I have to do something explicit like this:

  try{
    y = f(x);
    } catch(Exception){}
  
Compare that to f(x,y);.. which is easier to ignore the error?


Finally (yes, I'm almost done, for those of you still here), as to the "magic goto" claim - the biggest problem with Joel's post - It's not magic, it's simple defensive development. Here's the mantra: Assume any single operation you perform can throw an exception. There, that's the magic. You don't have to handle each one - and shouldn't try. That's the beauty of exceptions. Just cleanup after yourself and let the problem go up the chain.

With error codes (either approach)you'd have to have some way to either handle every possible error that could occur, or notify your caller of every possible error that you can't handle.

Mr. Spolsky, I can't enumerate every single error that can occur, so I'll stick to exceptions.   They may not be the perfect solution, but I think they're better than any alternative I've used.

Comments

Paul Wilson said:

In your example, 1/x, you actually have a third choice, which is already implemented in the .NET framework! Its a well-known mathematical fact that 1/0 is infinity, and the Double class actually has this. That is, 1.0/0.0 is equal to Double.PositiveInfinity -- not an exception at all! Even 0.0/0.0 is defined, although its Double.NaN, which stands for Not-A-Number.

My point is not to argue for or against exceptions, as I do generally think they are typically preferable to error code. My point is that (1) your example is flawed, and (2) this illustrates quite nicely why you should go out of your way to avoid exceptions and/or error codes. Quite often there is another way, if you're just willing to look at the problem in another way.
# October 14, 2003 10:07 PM

Philip Rieck said:

Great... If I'm using .Net. And if I'm using .NET and not exposing the function via COM. And if my library doesn't consider NAN or infinity an error -- just because .net doesn't throw an exception doesn't make it valid in all contexts. "Not an exception at all", sure... not an error at all -- perhaps.

1) Yes, it's a simplistic example off the top of my head, but I believe the point stands. If you can't see past it, change it to int f(x) { return 1/x;} or decimal f(x){ return 1.0m/x;}. I'm not perfect, so try to see the example in the context of the discussion, not as a compiler would. If you're stuck on "but floating point operations never throw, use them", try to see this as a placeholder for a function where 0 is not a valid input.

2)I disagree with avoiding exceptions and error codes by going out of my way. If it's an error, why not treat it as such? If the problem domain is such that 0 is not a valid input, why return a valid result? I would run (fast!) from code that tried to redefine the problem so that an exception or error code could be avoided.


*Yes, in the .net framework 0 _is_ a valid input... it's a general purpose math libary and supporting infinite results does make sense.
# October 14, 2003 11:03 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)