The Right Approach to Exceptions

Juan Manuel Cabo juanmanuel.cabo at gmail.com
Mon Feb 20 14:44:30 PST 2012


> I still don't like the idea of using Variant[string], though.
> 
> (1) It doesn't allow compile-time type checking. This is a big minus, in
> my book.

When you need compile-time type checking, define a variable in your class.
Just make sure that you are creating that new exception class for a good reason.

When a user needs to add a variable to the exception, he can add it
without putting your exception class chained in a new type of exception,
that will hide your class from being selected by upstream catch blocks
in the call tree.

> 
> (2) It's overly flexible. Anyone along the call stack can insert
> (hopefully NOT delete!!) additional data into the Exception object, as
> the stack is unwound. 

As is currently the case.
Did you know that anyone can overwrite any field of the exception and
rethrow it? Such as msg field and so on?

> By the time it gets to the final catch() block,
> you cannot guarantee a particular field you depend on will be defined.

If you want to guarantee it, then use a plain old variable for that piece
of data.

I just would like a way to add data to an exception without creating a new type.
If I create a new exception type for the wrong reasons, I'm polluting the
exception hierarchy.

If I pollute the exception hierarchy, then catching by exception type
becomes convolluted. It becomes difficult for an exception to fall
in the right catch. And I think that is a worst problem than
not being sure if a piece of data is in the extra info.

Data is data. Types are types. Exceptions should be typed the best way
possible that will allow me to select them and fall in the right
catch block. And that is the *what* of the error, not the data of
the error.


> Say if your call graph looks something like this:
> 
> 	main()
> 	  +--func1()
> 	      +--func2()
> 	      |   +--helperFunc()
> 	      |   +--func3()
> 	      |       +--helperFunc()
> 	      +--func4()
> 	          +--helperFunc()
> 
> Suppose helperFunc() throws HelperException, which func1's catch block
> specifically wants to handle. Suppose func2() adds an attribute called
> "lineNumber" to its catch block, which then rethrows the exception, and
> func3() adds an attribute called "colNumber".
> 
> Now how should you write func1()'s catch block? You will get all
> HelperException's thrown, but you've no idea from which part of the call
> graph it originates. If it comes from func3(), then you have both
> "lineNumber" and "colNumber". If it comes before you reach func3(), then
> only "lineNumber" is defined. If it comes from func4(), then neither is
> present.
> 
> So your catch block degenerates into a morass of if-then-else
> conditions. And then what do you do if you're depending on a particular
> field to be set, but it's not? Rethrow the exception? Then you have the
> stack trace reset problem.
> 
> Whereas if HelperException always has the the same fields, the catch
> block is very straightforward: just catch HelperException, and you are
> guaranteed you have all the info you need.
> 
> Then if func3() wants to add more info, create a new exception derived
> from HelperException, and add the field there. Then in func1(), add a
> new catch block that catches the new exception, and makes use of the new
> field.
> 
> This does introduce a lot of little exception classes, which you could
> argue is class bloat, but I don't see how the Variant[string] method is
> necessarily superior. It comes with its own set of (IMHO quite nasty)
> problems.
> 
> 
> T
> 


HAhaha, it sometimes feel as though people are afraid that the Variant[string]
idea is to never use plain old variables and never use exception subclasses. :-)

On the contrary, the idea is so that plain old variables and exception subclasses
can be created for the right reasons, and to remove cases where they need
to be created for the wrong reasons.

--jm







More information about the Digitalmars-d mailing list