February 19, 2012
On Sun, Feb 19, 2012 at 02:04:50AM -0600, Andrei Alexandrescu wrote:
> On 2/19/12 12:56 AM, Jonathan M Davis wrote:
> >I think that being able to have a catch block which took multiple exception types would be plenty. There are times when it would be very valuable to be able to use the same catch block for multiple exceptions without having to catch their base type (which would then potentially catch other exceptions which you didn't want to catch). So, something like that second catch block that you have there would be very valuable.
> 
> So now hierarchies are not good?
[...]

It's not a simple black-or-white decision we're dealing with here. :)

Although exception hierarchies are currently the best we have, it's not perfect, and it does have its own flaws. If we can figure out a better system that fixes those flaws without compromising the good points of using a class hierarchy, that would be even better.

The core of the problem is this: when an exception happens, it's within a specific context with the associated domain-specific data. This context and domain-specific data can be totally disparate types.

For example, a FileNotFound exception occurs in the filesystem, and is associated with a filename. A SeekError also occurs in the filesystem, is associated with a file (not necessarily equivalent to a filename), and a seek offset. By contrast, a NetworkTimeout error happens in the network stack, is associated with a particular protocol (say TCP), a particular network socket (with its own associated IP address, etc.), and a particular timeout value. It has no associated filename, or seek offset.

So you have a bunch of exceptions, each of which is associated with a bunch of info that, in general, have no relation with any info associated with another exception. The problem at hand is: how do you represent this info in a useful way? Generally speaking, the only common thing between all exceptions is the fact that they are, well, exceptions. And some of them are more closely related to each other than others -- file-related exceptions may, for example, share the fact that they are tied to a specific file.

To me, this is a giant hint that OOP is a good solution. You want a class hierarchy rooted at Exception. And groups of related exceptions probably should be rooted under their respective base classes under Exception.

If you have a better way to solve this, I'm waiting to hear it.


T

-- 
Gone Chopin. Bach in a minuet.
February 19, 2012
The issue here isn't deep vs. flat hierarchy (like the rest of the thread), it's about improving code clarity/dry.

This is much like the 'case A, B:' syntax (or fallthrough/goto case) in switches.

Even with a flat hierarchy, you might still want to handle a set of exceptions deriving from Exception the same way, but don't want to catch everything derived from Exception.

"Andrei Alexandrescu" <SeeWebsiteForEmail@erdani.org> wrote in message news:jhqaf2$tfk$2@digitalmars.com...
> On 2/19/12 12:56 AM, Jonathan M Davis wrote:
>> I think that being able to have a catch block which took multiple
>> exception
>> types would be plenty. There are times when it would be very valuable to
>> be
>> able to use the same catch block for multiple exceptions without having
>> to
>> catch their base type (which would then potentially catch other
>> exceptions
>> which you didn't want to catch). So, something like that second catch
>> block
>> that you have there would be very valuable.
>
> So now hierarchies are not good?
>
> Andrei


February 19, 2012
On Sunday, February 19, 2012 02:04:50 Andrei Alexandrescu wrote:
> On 2/19/12 12:56 AM, Jonathan M Davis wrote:
> > I think that being able to have a catch block which took multiple
> > exception
> > types would be plenty. There are times when it would be very valuable to
> > be
> > able to use the same catch block for multiple exceptions without having to
> > catch their base type (which would then potentially catch other exceptions
> > which you didn't want to catch). So, something like that second catch
> > block
> > that you have there would be very valuable.
> 
> So now hierarchies are not good?

No. Sometimes you want to catch specific types and handle a subset of them in a particular way but don't want to handle _all_ of the exceptions with the same base class the same way. For instance, if you had the following and all of them are derived from FileException (save FileException itself):

catch(e : FileNotFoundException, NotAFileException)
{
    //...
}
catch(AccessDeniedException e)
{
    //...
}
catch(FileException e)
{
    //...
}

You want to handle certain exceptions differently and you want to handle some of them the same, but you don't want to handle all FileExceptions the same way. Without a way to put multiple exception types in the same block, you tend to end up with code duplication (and no I'm not necessarily advocating that we have those specific exception types - they're just examples).

It's even worse if you don't have much of a hierarchy, since if _everything_ is derived from Exception directly, then catching the common type - Exception - would catch _everything_. For instance, what if you want to handle StringException and UTFException together but FileException differently? You can't currently do

catch(e : StringException, UTFException)
{
    //...
}
catch(FileException e)
{
    //...
}

Right now, you'd have to have separate catch blocks for StringException and UTFException.

A well-designed exception hierarchy reduces the problem considerably, because then there's a much higher chance that catching a common exception will catch what you want and not what you don't, but that doesn't mean that you're never going to run into cases where catching the common type doesn't work.

- Jonathan M Davis
February 19, 2012
On Sun, Feb 19, 2012 at 02:02:39AM -0600, Andrei Alexandrescu wrote:
> On 2/19/12 12:54 AM, H. S. Teoh wrote:
> >On Sun, Feb 19, 2012 at 12:43:58AM -0600, Andrei Alexandrescu wrote:
[...]
> >>I'm thinking an error is transient if retrying the operation with the same exact data may succeed. That's a definition that's simple, useful, and easy to operate with.
> >[...]
> >
> >But if that's the case, what's the use of an exception at all?
> 
> Centralization.
[...]

I don't understand.  So instead of providing enough information to the caller about the nature of the problem, you're essentially handing them an anonymous note saying "A generic problem occurred, which _may_ go away if you retry. I have no further information for you. Do you want to retry?"?

But without further information, how *can* you even make that decision? Without any way of determining what caused the error or even what it is, how could you know whether it makes sense to retry it?

Or is that transient flag intended to mean that it *should* be retried since it "might" succeed the next time round? How should the caller decide whether or not to go ahead with the retry? Flip a coin? Always retry?  Always fail? I can't imagine any sane application where code would say "if this operation fails with a transient error, always fail" where any arbitrary set of exceptions might potentially be transient? What's a "transient error" anyway, from the application's POV anyway? What's a "transient error" from a database app's POV? A 3D shooter? A text editor? Is it even possible to have a consistent definition of "transient" that is meaningful across all applications?

It seems to me that if an error is "transient", then the called function might as well just always retry it in the first place, instead of throwing an exception.  Such an exception is completely meaningless to the caller without further information. Yet it seems that you are suggesting that it's more meaningful than FileNotFoundException?


T

-- 
Obviously, some things aren't very obvious.
February 19, 2012
On Sunday, February 19, 2012 02:06:38 Andrei Alexandrescu wrote:
> On 2/19/12 1:12 AM, Jonathan M Davis wrote:
> > On Sunday, February 19, 2012 00:43:58 Andrei Alexandrescu wrote:
> >> On 2/18/12 8:00 PM, H. S. Teoh wrote:
> >>>>    From this and other posts I'd say we need to design the base
> >>>>    exception
> >>>> 
> >>>> classes better, for example by defining an overridable property isTransient that tells caller code whether retrying might help.
> >>> 
> >>> Just because an exception is transient doesn't mean it makes sense to try again. For example, saveFileMenu() might read a filename from the user, then save the data to a file. If the user types an invalid filename, you will get an InvalidFilename exception. From an abstract point of view, an invalid filename is not a transient problem: retrying the invalid filename won't make the problem go away. But the application in this case *wants* to repeat the operation by asking the user for a *different* filename.
> >>> 
> >>> On the other hand, if the same exception happens in an app that's trying to read a configuration file, then it *shouldn't* retry the operation.
> >> 
> >> I'm thinking an error is transient if retrying the operation with the same exact data may succeed. That's a definition that's simple, useful, and easy to operate with.
> > 
> > A core problem with the idea is that whether or not it makes sense to try again depends on what the caller is doing. In general, I think that it's best to give the caller as much useful information is possible so that _it_ can decide the best way to handle the exception.
> 
> That sounds like "I violently agree".

Then I'm confused.

"As much information as possible" is way more than a transient property. If my code is going to retry or do something else or give up, it needs enough information to know what went wrong, not just whether the function which was called think it might work on a second try.

Having an exception hierarchy provides some of that information simply with the types, and makes it easier to organize code based on what went wrong (e.g. separate catch blocks for each type of exception). And having that hierarchy also means that the derived types can have additional information beyond their type which could be useful but is specific to that problem and so wouldn't make sense on a general exception type.

I really don't see what transient buys you in comparison to that.

- Jonathan M Davis
February 19, 2012
On Sunday, February 19, 2012 00:26:57 H. S. Teoh wrote:
> On Sat, Feb 18, 2012 at 11:52:00PM -0800, Jonathan M Davis wrote: [...]
> 
> > So, while at first glance, it seems like a good idea, I think that it has too many issues as-is to work. It might be possible to adjust the idea to make it workable though. Right now, it's possible to do it via mixins or calling a function inside the catch, but doing something similar to this would certainly be nice, assuming that we could sort out the kinks.
> 
> [...]
> 
> I have an idea. What about "signature constraints" for catch, ala template signature constraints? Something like this:
> 
> 	try {
> 		...
> 	} catch(IOException e)
> 		if (e.errno in subsetYouWantToHandle)
> 	{
> 		...
> 	}
> 
> Just using IOException as an example. The idea is to allow arbitrary expressions in the constraint so whatever doesn't satisfy the constraint will be regarded as "not caught", even if the base type matches.

Interesting. But I don't think that it really buys you all that much, since normally what I'd think that you would want would simply be a list of the exception types that that particular catch block would handle. What you're suggesting would appear to end up being pretty much just ths:

try
{
    //...
}
catch(IOException e)
{
    if(e.errno in subsetYouWantToHandle)
   {
    }
    else
   {
    }
}

save for the fact that if the condition were false, in your example, you could presumably have a catch(Exception e) block following it which would catch it.

Honestly though, I think that something like Daniel's suggesting would be plenty, and it's much more explicit. Also, your suggestion might be a bit confusing in that it looks like a template constraint (which is evaluated at compile time) and yet it would have to be evaluated at runtime. That risks too much confusion IMHO. It's an interesting idea though.

- Jonathan M Davis
February 19, 2012
On Sunday, February 19, 2012 19:00:20 Daniel Murphy wrote:
> I wasn't really serious about implicit fallthrough.

Lately, it seems like I can never tell whether anyone's being serious or not online. :)

> Out of the syntaxes I could come up with:
> catch(Ex1, Ex2 e)
> catch(e : Ex1, Ex2)
> catch(Ex1 | Ex2 e) // java 7 syntax, horrible
> 
> I like (e : list) the best.  Naturally it would also accept a type tuple of
> exceptions.
> 
> http://d.puremagic.com/issues/show_bug.cgi?id=7540

LOL. Personally, I actually think that the Java 7 syntax looks great (I'd never seen it before), but catch(e : Ex1, Ex2) is just as good and more consistent with the language as a whole, since it doesn't try to give any operators a new meaning (as Java's does).

- Jonathan M Davis
February 19, 2012
Hello D community! This is my first post!! I hope I can bring clarity to all this. If not, I apologize.

Some time ago I researched the best way to classify exceptions and build a hierarchy. I came up with the following rules:


1) At the top level, there would be RecoverableExceptions and FatalExceptions (or you can call them something like CatcheableException and FatalExceptions, or Exception and Error).

2) Fatal exceptions shouldn't be catched. They imply that the program lost basic guarantees to go on (memory corruption, missing essential file, etc.). You could catch them if you wanted to, but it makes no sense other than at your top-level method (main(), etc.).

3) A RecoverableException can be converted to a FatalException by rethrowing it, once a catch decides so. You shouldn't do the reverse: a FatalException never should be converted to a RecoverableException.

4) It makes no sense to subclass FatalExceptions since there won't be a catch that groups them in a base type (since they are not catcheable).

5) Only create a new RecoverableException class type if it makes sense to write a catch for it alone. Otherwise, use an preexisting type.

6) Only group RecoverableExceptions in a category if it makes sense to write a catch for that category. Please don't group them because its fancy or "cleaner", that is a bad reason.


Who decides when an Exception is Unrecoverable? Library code almost never decides it, since an exception is only unrecoverable if the whole_program_invariant got broken, and libraries are only a part of a program. So they will tend to throw RecoverableExceptions, which can be reconverted to Unrecoverable by the program.

In some cases, it is clear that an exception is Unrecoverable. When you call a function without satisfying its arguments precondition (ie: null argument) the only way to fix that is by editing the program. You shouldn't have called it like that in the first place, why would you? So you let the UnrecoverableException bubble up to your main function, and log its stacktrace to fix it.

Unrecoverable means the program got 'tainted', basic guarantees got broken (possible memory corruption, etc.). Most exceptions will be Recoverable.

Now, expanding on the hierarchy: I said that it makes no sense to subclass UnrecoverableExceptions. Recoverable exceptions on the other hand, need to be subclassed _with_the_catch_on_your_mind_. You are passing info from the throw site to the catch site. The catch block is the interpreter of the info, the observer. You are communicating something to the catch block.

So please, do not create a new types if there is no value in writing a catch that only cathes that exception and that can recover from that exception. Otherwise, use an existing type.


I wrote these rules some time ago. Please excuse me if they come off a bit pedantic!!!!!!!!!!!! Its all only a clarifying convention.


According to all this:

* FileNotFoundException is useful. It tells you what happened. It is a RecoverableException (under my convention) because until it reaches the program, the library doesn't know if the program can recover from that (it could be a system missing file, or just a document the user asked for).

* DiskFailureException is only useful if someone can write a catch for it. If so, then it is a RecoverableException. Only the program can decide if it broke basic guarantees.

* Most argument exceptions are Unrecoverable. A function throwing shouldn't have been called like that in the first place. The only fix is to go back to editing the program. (precondition broken).


Another thing: you cannot decide whether an exception is Unrecoverable based only on whether the thing that got broken is the postcondition of a function. It is the whole_program_invariant that decides that. For instance:  findStuff(someStuff)  might not know if someStuff is important enough for the stability of the program if not found. The postcondition is broken if it doesn't return the Stuff. That might be recoverable.

And PLEASE: don't make classifications by the point of view of the cause of the problem. DO make classifications by the point of view of the fixing/recovery of the problem; the catch block is who you are talking to. FileNotFoundBecauseFilesystemUnmounted is worthless.

So, to sum up: (1) it makes no sense to subclass fatal exceptions, and (2) never subclass a RecoverableException if you are not helping a catch block with that (but please do if it aids recovery).

..so verbose and pedantic for my first post... yikes.. i beg forgiveness!!!




On Sunday, 19 February 2012 at 09:27:48 UTC, Jonathan M Davis wrote:
> On Sunday, February 19, 2012 19:00:20 Daniel Murphy wrote:
>> I wasn't really serious about implicit fallthrough.
>
> Lately, it seems like I can never tell whether anyone's being serious or not online. :)
>
>> Out of the syntaxes I could come up with:
>> catch(Ex1, Ex2 e)
>> catch(e : Ex1, Ex2)
>> catch(Ex1 | Ex2 e) // java 7 syntax, horrible
>> 
>> I like (e : list) the best.  Naturally it would also accept a type tuple of
>> exceptions.
>> 
>> http://d.puremagic.com/issues/show_bug.cgi?id=7540
>
> LOL. Personally, I actually think that the Java 7 syntax looks great (I'd never seen it before), but catch(e : Ex1, Ex2) is just as good and more consistent with the language as a whole, since it doesn't try to give any operators a new meaning (as Java's does).
>
> - Jonathan M Davis


February 19, 2012
Le 19/02/2012 08:05, Andrei Alexandrescu a écrit :
> How about a system in which you can say whether an exception is I/O
> related, network related, recoverable or not, should be displayed to the
> user or not, etc. Such is difficult to represent with inheritance alone.
>

That may sound great on the paper, but it isn't. The fact that an exception is recoverable or not depend often on your program and not on the cause of the exception. The piece of code throwing the exception have no clue if you can recover from that or not.

Exception that cannot be recovered for sure already exists in D. They are called errors.

Additionnaly, you'll find recoverable exception with different recovery point in the program.
February 19, 2012
That proposed syntax is nicer than this, but at least you can do it this way:
just call the same function from both catch blocks.

#!/usr/bin/rdmd
import std.stdio, std.utf, std.string;
void main() {
	void handleStringAndUtf(Exception ex) {
		if (typeid(ex).name == "std.utf.UTFException") {
			// .. UtfException specific handling ..
			writeln("length: ", (cast(UTFException)ex).len);
		}
		// .. handling of UtfException and StringException in common ..
		writeln(ex.toString);
		writeln(typeid(ex).name);
	}

	try {
		throw new UtfException("");
		//throw new StringException("aaaa");
	} catch (StringException ex) {
		handleStringAndUtf(ex);
	} catch (UTFException ex) {
		handleStringAndUtf(ex);
	}
}


--jm



On Sunday, 19 February 2012 at 09:12:40 UTC, Jonathan M Davis wrote:
> On Sunday, February 19, 2012 02:04:50 Andrei Alexandrescu wrote:
>> On 2/19/12 12:56 AM, Jonathan M Davis wrote:
> No. Sometimes you want to catch specific types and handle a subset of them in a particular way but don't want to handle _all_ of the exceptions with the same base class the same way. For instance, if you had the following and all of them are derived from FileException (save FileException itself):
>
> catch(e : FileNotFoundException, NotAFileException)
> {
>     //...
> }
> catch(AccessDeniedException e)
> {
>     //...
> }
> catch(FileException e)
> {
>     //...
> }
>
> You want to handle certain exceptions differently and you want to handle some of them the same, but you don't want to handle all FileExceptions the same way. Without a way to put multiple exception types in the same block, you tend to end up with code duplication (and no I'm not necessarily advocating that we have those specific exception types - they're just examples).
>
> It's even worse if you don't have much of a hierarchy, since if _everything_ is derived from Exception directly, then catching the common type - Exception - would catch _everything_. For instance, what if you want to handle StringException and UTFException together but FileException differently? You can't currently do
>
> catch(e : StringException, UTFException)
> {
>     //...
> }
> catch(FileException e)
> {
>     //...
> }
>
> Right now, you'd have to have separate catch blocks for StringException and UTFException.
>
> A well-designed exception hierarchy reduces the problem considerably, because then there's a much higher chance that catching a common exception will catch what you want and not what you don't, but that doesn't mean that you're never going to run into cases where catching the common type doesn't work.
>
> - Jonathan M Davis