February 18, 2012
Le 19/02/2012 00:30, Andrei Alexandrescu a écrit :
> On 2/18/12 5:20 PM, Jonathan M Davis wrote:
>> On Saturday, February 18, 2012 17:13:16 Andrei Alexandrescu wrote:
>>> On 2/18/12 4:26 PM, Jonathan M Davis wrote (abridged):
>>> GetOptException
>>> FlagArgumentMissingException
>>> InvalidFlagArgumentException
>>> UnknownFlagException
>>> FileException
>>> FileNotFoundException
>>> NotFileException
>>> NotDirException
>>> AccessDeniedException
>>>
>>> I died inside a little.
>>
>> If you actually want to _handle_ exceptions, how else do you expect to
>> do it?
>> Simply put a code of some kind on the exceptions and then have switch
>> statement to handle them?
>
> The alternative is with virtuals. Do you see a lot of virtuals in base
> exceptions? Do you see dramatically different interface for different
> exception types?
>
>
> Andrei
>
>

What do you have in mind ?
February 18, 2012
Le 19/02/2012 00:26, Andrei Alexandrescu a écrit :
> On 2/18/12 5:30 PM, deadalnix wrote:
>> You know that « real life » programming is different than a tutorial.
>
> Problem is (a) all tutorials suggests you have a world of things to do;
> (b) all real code does one thing.
>
> Andrei

And usually, one thing has several way to fail.
February 18, 2012
On Saturday, 18 February 2012 at 23:26:14 UTC, Andrei Alexandrescu wrote:
> (b) all real code does one thing.
>
> Andrei

No. It's conceivable that *most* cases simply log the exception (e.g. by printing it) then either move back or move on (!). But it's most definitely not *all* and it's when you have a meaningful fall-back or recovery mechanism that exceptions shine and it's what they're designed for (because you are basically arguing against exceptions here, not their proper use, which should be obvious from how they function).

The more complex your program becomes, the farther away from uncontrollable factors like user input and otherwise unexpected environments you get, and this is when interesting catch blocks are born. Even so, exceptions are still useful in shallow parts of your program too - you could catch a GetOptException (more specific exception types than this with attached data would be even better, as noted by everyone before me) and re-query the user for new input (not very Unix-y, but that's irrelevant). You can't do that with just an Exception - what if the exception came from one of the callbacks you passed to getopt, or from a bug in std.getopt?

All that said, I don't think std.getopt is a good example because it's comparatively trivial code. std.file would be a much better area to examine for examples of interesting catch blocks.
February 18, 2012
On 2/18/12 1:41 PM, H. S. Teoh wrote:
> On Sat, Feb 18, 2012 at 12:52:05PM -0600, Andrei Alexandrescu wrote:
>> There's a discussion that started in a pull request:
>>
>> https://github.com/alexrp/phobos/commit/4b87dcf39efeb4ddafe8fe99a0ef9a529c0dcaca
>>
>> Let's come up with a good doctrine for exception defining and handling
>> in Phobos. From experience I humbly submit that catching by type is
>> most of the time useless.
> [...]
>
> It's only useless because of a poorly-designed exception hierarchy.
> Java, for example, has useful things like FileNotFoundException,
> DiskFullException, etc., that you can catch to handle specific problems
> in a specific way. They are also part of a well-designed hierarchy. For
> example, both of the above exceptions are subsumed under IOException, so
> if you wanted to handle a general I/O exception and don't care which one
> it is, you could just catch IOException.

It's great that you bring expertise from the Java world. I should note that the above does little in the way of putting an argument to the table. It appeals to subjective qualifications ("poorly designed", "well designed") so one's only recourse to getting convinced is just believing what you say without any evidence. It's equally unimpressive that FileNotFoundException and DiskFullException inherit IOException; seeing that one might say "yeah, sure" but there should be some compelling evidence that the distinction makes a difference.

> Now, granted, there are limitations to such a design, for example if you
> want to catch a category of exceptions that don't map precisely to a
> node in the inheritance hierarchy. It may be possible to deal with such
> situations by making Exception an interface instead, although that may
> introduce other issues.

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.

> Either that, or allow a list of exceptions in a catch() block, but that
> would require some further thought, because blindly allowing multiple
> arguments to catch introduces an initialization problem:
>
> 	try { ... }
> 	catch(FileNotFoundException e1, DiskFullException e2) {
> 		// which of e1, e2 is actually thrown?
> 	}

It seems exceptions still are not entirely understood, and I agree that adding some random mechanism doesn't do good.

> The problem with this approach is the proliferation of of exception
> classes, many of which differ only in fine ways that most applications
> wouldn't even care about. The basic problem here is that we are mapping
> what amounts to error codes to classes.

Yes. Types, to be more general. The question is why are various error deserving of their own types.

> C++ does allow throwing
> arbitrary objects (IIRC), so in a sense you *could* throw an error code
> instead of a class object. But that's not necessarily a good thing,
> because then you end up with different modules throwing different,
> mutually incompatible enumerated error codes, which brings us back to
> square two (not square one because at least we don't have to manually
> propagate error codes in every level of the call stack), where we have a
> bunch of incompatible error types and we don't necessarily know what to
> do with them. Having a common root to all exceptions is a good thing.

I didn't contend the opposite.


Andrei
February 18, 2012
On 2/18/12 5:47 PM, Jakob Ovrum wrote:
> you are basically arguing against exceptions here

I must have argued my question and point very poorly.

Andrei


February 19, 2012
On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
> On 2/18/12 5:20 PM, Jonathan M Davis wrote:
> > On Saturday, February 18, 2012 17:13:16 Andrei Alexandrescu wrote:
> >> On 2/18/12 4:26 PM, Jonathan M Davis wrote (abridged):
> >> GetOptException
> >> FlagArgumentMissingException
> >> InvalidFlagArgumentException
> >> UnknownFlagException
> >> FileException
> >> FileNotFoundException
> >> NotFileException
> >> NotDirException
> >> AccessDeniedException
> >> 
> >> I died inside a little.
> > 
> > If you actually want to _handle_ exceptions, how else do you expect to do it? Simply put a code of some kind on the exceptions and then have switch statement to handle them?
> 
> The alternative is with virtuals. Do you see a lot of virtuals in base exceptions? Do you see dramatically different interface for different exception types?

You mean virtual functions? The problem is that each exception type could have information specific to _it_ which makes no sense in a base class. For instance, Exception does to have errno in it. FileException does.

If we have GetOptException, it should have a variable for which flag failed. Exception doesn't have that. And what if the flag failed because it was given a bad argument? Then the exception needs a field for that argument. Then you can get something like

try
    getopt(args, ...)
catch(MissingArgumentException mae)
{
    stderr.writefln("%s is missing an argument", mae.flag);
    return -1;
}
catch(InvalidArgumentException iae)
{
    stderr.writelfln("%s is not a valid argument for %s. You must give it a
%s.", mae.arg, mae.flag, mae.expectedType);
    return -1;
}
catch(UnknownFlagException ufe)
{
    stderr.writefln("%s is not a known flag.", ufe.ufe);
    return -1;
}
catch(GetOptException goe)
{
    stderr.writefln("There was an error with %s",  goe.flag);
    return -1;
}
//A delegate that you passed to getopt threw an exception.
catch(YourException ye)
{
    //...
}
catch(Exception e)
{
    stderr.writeln("An unexpected error occured.");
    return -1;
}

You can't do _anything_ like that right now. And none of that makes sense as virtual functions. And yes, in this case, you're still printing out in every case rather than doing more dynamic handling, but you get much better error messages here, and it may be that a program would want to do something fancier than what I'm doing here (particularly if you're using delegates with getopt). And of course other exception types lend themselves better to doing extra handling rather than printing messages (e.g. parsing exceptions or file handling exceptions might make it so that the program tries again or tries something else, and the user doesn't get involved at all).

And even if all of the exception types that you were dealing with were identical save for their type, they could still be very useful. Knowing that you got an IOException when handling a stream rather than a UTFException gives you some idea of what's wrong even if the exception types aren't giving much in the way of additional useful information (though hopefully, they would).

In order to actually handle exceptions rather than just print out messages (or to print out more useful messages like in the case of getopt), you need to have exceptions with types of _some_ kind so that code can know what it's dealing with. Messages are really only of any use for humans.

So, you either end up with an exception with a field indicating its type (which really doesn't scale very well - particularly when you don't control all of the code you're dealing with and someone could screw up and reuse the same value for as an existing type) which you then switch on, or you have the exceptions in an inheritance hierarchy to indicate their type, which works much better for giving additional information, because then that information can be in fields in the derived exception type rather than the equivalent of a void* in a  field in Exception which then must be cast appropriately based on the type field.

- Jonathan M Davis
February 19, 2012
On 2/18/12 6:03 PM, Jonathan M Davis wrote:
> On Saturday, February 18, 2012 17:30:10 Andrei Alexandrescu wrote:
>> The alternative is with virtuals. Do you see a lot of virtuals in base
>> exceptions? Do you see dramatically different interface for different
>> exception types?
>
> You mean virtual functions? The problem is that each exception type could have
> information specific to _it_ which makes no sense in a base class. For
> instance, Exception does to have errno in it. FileException does.
>
> If we have GetOptException, it should have a variable for which flag failed.
> Exception doesn't have that. And what if the flag failed because it was given a
> bad argument? Then the exception needs a field for that argument. Then you can
> get something like
>
> try
>      getopt(args, ...)
> catch(MissingArgumentException mae)
> {
>      stderr.writefln("%s is missing an argument", mae.flag);
>      return -1;
> }
> catch(InvalidArgumentException iae)
> {
>      stderr.writelfln("%s is not a valid argument for %s. You must give it a
> %s.", mae.arg, mae.flag, mae.expectedType);
>      return -1;
> }
> catch(UnknownFlagException ufe)
> {
>      stderr.writefln("%s is not a known flag.", ufe.ufe);
>      return -1;
> }
> catch(GetOptException goe)
> {
>      stderr.writefln("There was an error with %s",  goe.flag);
>      return -1;
> }
> //A delegate that you passed to getopt threw an exception.
> catch(YourException ye)
> {
>      //...
> }
> catch(Exception e)
> {
>      stderr.writeln("An unexpected error occured.");
>      return -1;
> }
>
> You can't do _anything_ like that right now.

Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony.

Andrei

February 19, 2012
On Sat, Feb 18, 2012 at 9:13 PM, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> On 2/18/12 4:26 PM, Jonathan M Davis wrote (abridged):
> GetOptException
> FlagArgumentMissingException
> InvalidFlagArgumentException
> UnknownFlagException
> FileException
> FileNotFoundException
> NotFileException
> NotDirException
> AccessDeniedException
>
> I died inside a little.

Yeah. This gets even worse by the fact that some times you have to import vendor specific modules with their own set of exceptions. For example the JDBC exception model was just a nightmare. Depending on which database driver you used you could have a completely different set of exceptions you had to catch.

The Spring Framework solved this problem by just throwing just one exception: SQLException (if my memory serves me right). The developer then had to switch based on the driver type and the error code. I don't think this is perfect but I think it works much better in practice.

The other nice thing about this model is that it is easier to know which error you want to handle. Database vendor document their errors using error code not language specific exception. If the Oracle manual says that the database gives error XXXX for such and such condition how in the world do you know to which JDBC exception does that match or D exception, etc.?

I'll be the first to suggestion something. One possible solution is to have one exception and inside have the concept of a namespace and a error code.

Assuming that D had pattern matching you could do:

try {
  errorProneCode();
} catch (Exception(IONamespace, errorCode)) {
  // errorCode is a variable with the error and the exception must
match the value in IONamespace
} catch (Exception(DBNamespace, AccessDenied) {
  // matches the db namespace and the access denied error code
}


Destroy. Thanks,
-Jose


>
> Andrei
>
February 19, 2012
On Saturday, February 18, 2012 18:09:30 Andrei Alexandrescu wrote:
> Of course I can. They call it toString(). The code above pretty much proves my point with so much wonderful irony.

How?!! toString doesn't help _at all_. It just gives whatever getopt can come up with, not what's appropriate for your program. And right now, since there's no GetOptException, you just end up with a ConvException, so you don't even get a semi-decent toString. You have _no idea_ what flag had the error. The best that you can do is print out that _something_ went wrong, not _what_. And if you want to do fancier handling (_especially_ if you want recover from an exception rather than just print a message), you need _far_ more than toString. Maybe I want to look at the flag that you gave me which was invalid and then suggest what you might have meant. Without _at least_ knowing what the flag was that you used (which I can't do with the current getopt), I can't do that.

How could you do something even close to my example with getopt as it is now?

Perhaps getopt is not the best example, because it's likely to end up with you giving an error to the user regardless. But other exception handling wouldn't necessarily involve the user at all. The primary issue is giving the program the information that it needs to actually handle and potentially recover from the exception. A string doesn't do that. Giving the exception a specific type according to what went wrong and additional member variables with information on what went wrong _can_ do that.

What if something goes wrong when processing a file? Did something go wrong with the file operations or in processing the information? A FileException vs another exception type (e.g. UTFException or ParseException) would tell you that. And if you want to recover from the exception and continue processing the file, knowing what type of ParseException it was could help you do that by giving you the information that you need to continue - which could vary quite a bit depending on what went wrong.

This gets even more critical the larger your program is. The farther you get away from the code that threw the exception, the more worthless a generic exception type is (be it Exception or even something somewhat more specific like FileException), and the program can't really handle it. It's forced to either ignore it (possibly logging something), retry what it's trying to do, or give up on what it's trying to do. In general, handling the exceptions closer to their source is better, which helps, but that's not always possible.

Even a slightly more advanced exception hierarchy (e.g. having GetOptException with a flag, even if you don't actually derive any other exception types from it) would be an improvement over what we have now.

- Jonathan M Davis
February 19, 2012
On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
> On 2/18/12 5:47 PM, Jakob Ovrum wrote:
> > you are basically arguing against exceptions here
> 
> I must have argued my question and point very poorly.

You definitely seem to be arguing for a single Exception type, which does _not_ make for good error handling code, since all it tells you is that _something_ went wrong, not what. And that goes against how Exceptions are designed. They purposely are designed to have an inheritance hierarchy.

I grant you that simply having an exception type per module which is identical to Exception save for its name and the fact that it's derived from Exception rather than Throwable is not the best way to go. I do think that it's somewhat better than just using Exception everywhere, since it gives you a better idea of went wrong (e.g. UTFException rather than a TimeException), but ideally, it would give much better information than that, and we'd have a better exception hierarchy with related exceptions being derived from one another or from a common exception type rather than simply being based on what module they're in.

C++ is a horrible example of how exceptions should be done, so if you're basing what you want off of that, then that makes me think that you should be better familiar with how other, more recent languages use them (though maybe you're quite familiar with how C# and/or Java use Exceptions, I don't know).
>From using Java, I think that how it handles exceptions in general is _far_
superior to how they're frequently dealt with in C++ (though that does tend to depend on who's doing the developing, since you _can_ have a decent exception hierarchy in C++).

- Jonathan M Davis