Why exceptions for error handling is so important

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Mon Jan 12 10:28:09 PST 2015


On Sun, Jan 11, 2015 at 02:48:29AM -0800, Walter Bright via Digitalmars-d wrote:
[...]
> What I'm pretty sure is happening is those programs use error codes
> for error reporting, and then don't check the error codes. This is
> common practice for C code. I'm a little surprised that with Windows'
> long history, it still has problems detecting when it runs out of disk
> space.
> 
> However, if exceptions are thrown for errors instead, the programmer
> has to deliberately add code if he wishes to ignore the error.

While I agree with the general sentiment, I think the current convention
of using a class hierarchy to implement exceptions is suboptimal.

The problem with using a class hierarchy is that, like anything put into
a hierarchy, some things just don't fit very well in a single-rooted
hierarchy. This is especially true in D because there is no multiple
inheritance (and for good reason too; multiple inheritance brings with
it a whole set of nasty problems).

A recent example was what to do with an exception that wraps around
OS-level errors. From an implementor's POV, it makes sense to segregate
exception types by implementation, that is, ErrnoException for Posix
systems and SystemErrorCodeException (or some such) for Windows.
However, this is totally useless to the end user: when you're traversing
the filesystem, under this scheme you'd have to catch ErrnoException or
catch SystemErrorCodeException (with a static-if on OS type, aka utter
ugliness), and then ferret out the specific error code(s) you wish to
handle, like what to do with an I/O error vs. a permission-denied error.
Why should the *user* have to work with low-level implementation details
like mapping Posix errno's and their corresponding Windows error codes
to the semantic categories of real interest: i.e., access error /
hardware failure / network error, etc.?

So from the user's POV, the exception hierarchy ought to be semantically
driven, rather than implementationally driven. Instead of ErrnoException
and SystemErrorCodeException, one ought to have semantic categories like
FileNotFoundException, AccessDeniedException, NetworkException, etc..
However, this is burdensome on the implementor, because now a single
underlying implementation like ErrnoException now has to be split across
multiple unrelated semantic categories (FileNotFoundException must have
an errno field on Posix and a systemErrorCode field on Windows, ditto
for NetworkException, IOException, etc. -- and you can't factor it out
into a base class because the class hierarchy is semantics driven, so
ErrnoException doesn't fit anywhere in the hierarchy).

Recently Dmitry (IIRC) came up with the rather clever idea of using
interfaces instead of a single-rooted hierarchy for marking up semantic
categories instead. Instead of trying to decide on whether we should
have implementationally-driven OSException with subclasses
ErrnoException and SystemErrorCodeException, or semantically-driven
FileSystemException with subclasses FileNotFoundException and
AccessDeniedException, we can have both: the class hierarchy itself
(i.e. without the interfaces) uses base classes for factoring out
implementation details, but tag each exception type with semantic
categories derived from a distinct interface hierarchy. So we could have
something like this:

	// Implementational hierarchy
	class Throwable { ... }
	class Exception : Throwable { ... }
	class ErrnoException : Exception {
		int errno;
		...
	}
	class SystemErrorCodeException : Exception {
		int systemErrorCode;
		...
	}

	// Semantic tags
	interface FilesystemException;
	interface FileNotFoundException : FilesystemException;
	interface AccessDeniedException : FilesystemException;
	..

	// Actual implementations
	static if (Posix) {
		// Note: you can even have multiple exception types that
		// inherit from FileNotFoundException, e.g., one thrown
		// by a manual stat() check, and one from a common
		// routine that interprets OS error codes. The user
		// wouldn't need to know the difference.
		class FileNotFoundExceptionImpl :
			ErrnoException,
			FileNotFoundException { ... }
		... // other Posix-specific exceptions here
	} else static if (Windows) {
		class FileNotFoundExceptionImpl :
			SystemErrorCodeException,
			FileNotFoundException { ... }
		... // other Windows-specific exceptions here
	}

So now, user code doesn't have to worry about implementation details
like whether the exception came from errno or a Windows error code, you
can just catch semantic categories like FileNotFoundException instead.

Currently, however, this scheme doesn't quite work because the compiler
only allows catching classes derived from Throwable, and interfaces
can't derive from non-interface base classes. Besides, I'm not sure the
druntime exception catching code can deal with interfaces correctly as
opposed to just base classes. But this is definitely an area that D
could improve on!


T

-- 
Why have vacation when you can work?? -- EC


More information about the Digitalmars-d mailing list