The Right Approach to Exceptions

H. S. Teoh hsteoh at quickfur.ath.cx
Mon Feb 20 11:15:08 PST 2012


On Mon, Feb 20, 2012 at 01:52:15PM -0500, Jonathan M Davis wrote:
[...]
> So, instead of
> 
> catch(SpecificException1 e)
> {
>  //use fields specific to this exception to do whatever you need to do
> }
> catch(SpecificException2 e)
> {
>  //use fields specific to this exception to do whatever you need to do
> }
> catch(GenericException e)
> {
>  //handle the generic exception as best you can given the lack of
>  //a specific one
> }
> 
> you end up with something effectively along the lines of
> 
> catch(GenericException e)
> {
>  if(/* e is SpecificException1 */)
>  {
>  //use fields specific to this exception to do whatever you need to do
>  }
>  else if(/* e is SpecificException2 */)
>  {
>  //use fields specific to this exception to do whatever you need to do
>  }
>  else
>  {
>  //handle the generic exception as best you can given the lack of
>  //a specific one
>  }
> }
[...]

I think what Andrei wants to do is more along these lines:

	catch(Exception e) {
		writeln(formatLocaleString(e.fmt, e.data));
	}

I think there's some merit to this idea. However, I'm still unsure about
storing stuff in a Variant.

For one thing, you either need some sort of format string in the
exception object (as I have above), which is bad, as somebody else
pointed out, because now you're mixing i18n code into exception code, or
you need some way of figuring out what format to use for which
exception. So ultimately, you'll still end up with with a huge switch
statement, or a global table of all exceptions (not good for
maintenance, now every time someone changes an exception he has to
remember to update the table).

One solution, perhaps, is to have an i18n file containing mappings of
exception types to message formats. So that you can have:

	class Exception : Error {
		Variant[string] info;
		...
	}

	string[string] exceptionFormats = loadLocaleData();

	string formatExceptionMsg(LocaleInfo li, Exception e) {
		if (typeid(e).name in exceptionFormats) {
			return format(exceptionFormats[typeid(e)],
				e.info);
		}
		return e.msg;
	}

This may be acceptable, if the catching code knows which formats are
actually defined in the locale data, so it will only call
formatExceptionMsg() if there's actually a format defined for it. This
way, you don't need to have *every* exception translated, just those
that your catching code will actually use.

The one objection I have with this, though, is that if the catching code
wants to specifically catch, say, FileNotFoundException, and extract the
offending filename from the exception object, it would have to do a
string lookup in Exception.info, rather than just accessing
FileNotFoundException.filename directly.

Then if the Phobos maintainer renames the field, the code still compiles
since the compiler has no way to know that Exception.info["filename"] is
no longer set by the ctor of FileNotFoundException, so the problem will
go undetected until the exception actually occurs, at which time the
catching code gets a runtime error (very bad).

Having the field directly in FileNotFoundException is better, because
you get a compile-time error when the field is renamed, so the developer
can fix it before it ships, rather than have the customer run into the
runtime error.

That's why I proposed to use runtime reflection to scan the exception
object for applicable fields. Then you get the best of both worlds: the
message formatter doesn't need to know what the fields are, and you get
full compile-time type checking for catching code that directly accesses
the fields.


T

-- 
Never wrestle a pig. You both get covered in mud, and the pig likes it.


More information about the Digitalmars-d mailing list