Program logic bugs vs input/environmental errors (checked exceptions)

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Tue Oct 7 15:44:12 PDT 2014


On Tue, Oct 07, 2014 at 02:25:24PM -0700, Jeremy Powers via Digitalmars-d wrote:
> On Mon, Oct 6, 2014 at 6:19 PM, Andrei Alexandrescu via Digitalmars-d <
> digitalmars-d at puremagic.com> wrote:
> 
> > On 10/6/14, 4:46 PM, Jeremy Powers via Digitalmars-d wrote:
> >
> >> On Mon, Oct 6, 2014 at 7:50 AM, Andrei Alexandrescu via Digitalmars-d
> >>     I'm thinking a simple key-value store Variant[string] would
> >>     accommodate any state needed for differentiating among
> >>     exception kinds whenever that's necessary.

We've argued about this before. I don't think it's a good solution. Who
decides which string keys will map to what types? And how does the
catcher know if the thrower is actually using a specific string key to
mean that specific thing, and not something else? For example, a catch
block receives an Exception with the key/value pair "name" = "abc". How
does it know the correct interpretation of this value? A "file not
found" error might use "name" as the offending filename, and a database
error might use "name" to mean data from a column called "name". How is
the catcher supposed to act on this information?

The only reasonable way to determine the "true" meaning of a field is if
there is an agreement between the thrower and catcher as to what it
means. Boiled down to the essentials, what this is saying is, here we
have a blob of data, and this data may be divided into chunks with
specific names and types, and the thrower and catcher must agree on the
meaning of each chunk when presented in a specific combination (e.g., a
"file not found" error might contain a field for storing the errno value
from the OS). The simplest way to decide if a particular blob has a
particular meaning, is to standardize on an ID field that assigns unique
identifiers to specific combinations of field names/values with specific
meanings.  If we look at the essentials of this, it's basically
describing what the type system does.

So why not just *use* the type system we already have in the first
place??!


[...]
> > I've used "kinds" intentionally there. My basic thesis here is I
> > haven't seen any systematic and successful use of exception
> > hierarchies in 20 years. In rare scattered cases I've seen a couple
> > of multiple "catch"es, and even those could have been helped by the
> > use of a more flat handling.  You'd think in 20 years some good
> > systematic use of the feature would come forward. It's probably time
> > to put exception hierarchies in the "emperor's clothes" bin.

Sure, but my contention is that Variant[string] is an even worse form of
emperor's clothes. You can't do *anything* with it that you can't
already do with the existing type system. Not anything meaningful,
anyway. The most you can do with it is to print out the key/value pairs,
which toString already does today. You need to establish some universal
key names whose interpretation everyone agrees on, before your catch
block can even begin to do anything meaningful with it. But if so, why
not just add these universal fields as members in class Exception? They
serve the same purpose, once you strip away the guises of genericity
that Variant is supposed to provide -- and do so in a more type-safe
manner.

As for key names that are non-standard, your catch block basically has
no way to act on it, except proceeding by blind assumption (oooh, field
"xyz" has type int, so it must mean the line number!). The only way to
act on it is if you already established an agreed-on interpretation of
field names between the thrower and the catcher. Which, again, is what
the type system does. You just subclass Exception, give it the fields
that both thrower and catcher agree to, and that use that subclass in
the catch block.

I don't see any way you can meaningfully act on a generic
Variant[string] blob without any established conventions of what each
key is supposed to mean. (And again, as soon as you establish an
agreed-on convention for these key names, they become equivalent to
member fields in an Exception subclass, so you gain absolutely nothing
in terms of functionality by replacing the class hierarchy with a poor
man's form of runtime-struct.) If you have a counterexample, I'd really
like to see it. Believe me, I am skeptical about exception class
hierarchies too, but I haven't been able to come up with anything that
isn't inferior.


[...]
> > Oh yah I know the theory. It's beautiful.
> >
> >
> I'm not talking theory (exclusively).  From a practical standpoint, if
> I ever need information from an exception I need to know what
> information I can get.  If different exceptions have different
> information, how do I tell what I can get?  Types fits this as well
> as/better than anything I can think of.

Exactly. There's nothing Variant[string] gains for us that Exception
types don't already give us. A catch block that has no idea what each
key in the Variant[string] *means*, cannot meaningfully do anything with
the values. I mean, for all you know, it could be a value of a type that
the catch block doesn't even know about. How are you supposed to do
anything with it? You can't. Except trivial things like printing it out,
which is what toString already does anyway.

Basically, the catch block cannot do anything with a completely unknown
Exception type (or kind, whatever) -- because, by definition, the person
who writes the catch block doesn't know anything about that type, so
there is no way to make use of any info it may have. There has to be
some kind of prior agreement between the thrower and the catcher, for
any meaningful processing to take place. This kind of agreement is
precisely what the type system was invented to solve -- think of the
analogy with calling a function -- you pass the function an opaque blob
of binary data, and it really can't do very much with it. The type
system is what assigns meaning to individual segments in this blob,
i.e., individual function parameters, and that's how the function can
make sense of what the caller intends to convey, and how to act on it.
In a sense, the catch block is a "remote function" that gets "called" by
the throw statement. In order for it to do something useful, the meaning
of what the throw statement throws must be agreed upon by the catch
block.  This is precisely what the type system does. Using
Variant[string] gains nothing -- you've basically just reinvented
Javascript function calls where parameters have arbitrary type.


> >>     It's commonly accepted that the usability scope of OOP has
> >>     gotten significantly narrower since its heydays. However,
> >>     surprisingly, the larger community hasn't gotten to the point
> >>     to scrutinize object-oriented error handling, which as far as I
> >>     can tell has never delivered.
> >>
> >>
> >> Maybe, but what fits better?  Errors/Exceptions have an inherent
> >> hierarchy, which maps well to a hierarchy of types.  When catching
> >> an Exception, you want to guarantee you only catch the kinds
> >> (types) of things you are looking for, and nothing else.
> >>
> >
> > Yah, it's just that most/virtually all of the time I'm looking for
> > all.  And nothing else :o).

Fallacy: I have never needed X, therefore nobody else needs X either.


> Most/virtually all of the time I am looking only for the kind of
> exceptions I expect and can handle.  If I catch an exception that I
> was not expecting, this is a program bug (and may result in undefined
> behavior, memory corruption, etc).  Catching all is almost _never_
> what I want.

The only thing useful about catching all is to print an error message
and exit (or restart whatever operation it is you're doing). There's not
much else you can do with it, because it's too generic. To act on a
specific exception, you need to have narrower exception types, IOW, a
subclass of Exception.


> I have not found a whole lot of use for deep exception hierarchies,
> but some organization of types/kinds of exceptions is needed.  At the
> very least you need to know if it is the kind of exception you know
> how to handle - and without a hierarchy, you need to know every single
> specific kind of exception anything you call throws.  Which is not
> tenable.

Exactly.


On Tue, Oct 07, 2014 at 02:33:26PM -0700, Jeremy Powers via Digitalmars-d wrote:
[...]
> As mentioned, I'm not a defender of hierarchies per se - but I've not
> seen any alternate way to accomplish what they give.  I need to know
> that I am catching exceptions that I can handle, and not catching
> exceptions I can't/won't handle.  Different components and layers of
> code have different ideas of what can and should be handled.
> 
> Without particular exception types, how can I know that I am only
> catching what is appropriate, and not catching and swallowing other
> problems?

Exactly.

Having said all that, though, an idea occurred to me. While it's
certainly true that the catch block must catch a *specific* type of
exception -- otherwise there's no meaningful way to act upon it -- I
haven't seen any strong evidence that a *class hierarchy* is required.
Having been a skeptic of the OO craze where everything and everyone is
shoehorned into the OO mode of thinking, misfits be damned, and having
experienced the true power of metaprogramming in D, I wonder if a more
flexible approach would be to use a templated, duck-typing kind of
approach to exceptions instead of a class hierarchy.

Basically, this boils down to the thought that the relationship between
the throw statement and the corresponding catch block is in many ways
analogous to a function call: the thrower (caller) has some pieces of
info to pass on to the catcher (callee), and the type system allows the
two to communicate and agree on the interpretation of this info. The
current exception class hierarchy approach is akin to requiring all
catch blocks to have signature function(Exception e). But what if we
allow catch blocks to be "templated"?

We could then introduce signature constraints to catch blocks, that
determine generically whether some unknown incoming exception type
matches certain required characteristics (in the same way range
algorithms accept any type that satisfy the range API), and then the
catch block would be able to operate on that exception without actually
being tied down to a concrete exception type.

For classification purposes, of course, we still require exceptions to
subclass Exception. But a catch block could potentially accept multiple
unrelated Exception subclasses via ducktyping, e.g.:

	class Exception : Throwable { ... }
	class JSONParseError : Exception {
		string filename;
		size_t line;
		JSONParser parser;
	}
	// N.B. there is no common base class between ScriptRunnerError
	// and JSONParseError.
	class ScriptRunnerError : Exception {
		string filename;
		size_t line;
		Interpreter interp;
	}
	...
	void main() {
		try {
			doStuff();
		} catch (E : Exception)(E e)
			if (is(typeof(e.filename) : string) &&
			    is(typeof(e.line) : size_t))
		{
			// can use .filename and .line here.
		}
	}

This way, things don't have to be in a hierarchy, yet the catch block
can meaningfully process any Exception subclass that has the requisite
attributes.

(The caveat, of course, being that the current language may not be
powerful enough to support such a feature, since implementing the catch
block sig constraints will require runtime ducktyping.)


T

-- 
You have to expect the unexpected. -- RL


More information about the Digitalmars-d mailing list