View mode: basic / threaded / horizontal-split · Log in · Help
April 01, 2013
Re: DIP33: A standard exception hierarchy
On Monday, April 01, 2013 13:08:15 Lars T. Kyllingstad wrote:
> It's time to clean up this mess.
> 
> http://wiki.dlang.org/DIP33

The basic idea is good, but of course, some details need to be sorted out. As 
I explained in another response, I really think that we should have derived 
exceptions for many of the "kind"s so that they can be explicitly caught. And 
of course, in some cases, extra information can be put into the subclasses 
(e.g. UTFException contains extra data specific to it), so having derived 
exceptions is very valuable. Having the kinds is great, but I don't think that 
that should preclude having derived classes which can be explicitly caught, 
thereby allowing users to choose whether they want to catch the base type (and 
possibly use the kind field, possibly not) or to catch the derived types when 
they want error handling specific to the errors that those types represent. 
Using only kind fields rather than derived exception types makes it harder to 
use try-catch blocks to separate out error handling code like they were 
designed to do.

Another concern I have is InvalidArgumentError. There _are_ cases where it 
makes sense for invalid arguments to be an error, but there are also plenty of 
cases where it should be an exception (TimeException is frequently used in 
that way), so we may or may not want an InvalidArgumentException, but if you 
do that, you run the risk of making it too easy to confuse the two, thereby 
causing nasty bugs. And most of the cases where InvalidArgumentError makes 
sense could simply be an assertion in an in contract, so I don't know that 
it's really needed or even particularly useful. In general, I think that 
having a variety of Exception types is valuable, because you catch exceptions 
based on their type, but with Errors, you're really not supposed to catch 
them, so having different Error types is of questionable value. That doesn't 
mean that we shouldn't ever do it, but they need a very good reason to exist 
given the relative lack of value that they add.

Also, if you're suggesting that these be _all_ of the exception types in 
Phobos, I don't agree. I think that there's definite value in having specific 
exception types for specific sections of code (e.g. TimeException for time-
related code or CSVException in std.csv). It's just that they should be put in 
a proper place in the hierarchy so that users of the library can choose to 
catch either the base class or the derived class depending on how specific 
their error handling needs to be and on whatever else their code is doing. We 
_do_ want to move away from simply declaring module-specific exception types, 
but sometimes modules _should_ have specific exception types.

The focus needs to be on creating a hierarchy that aids in error handling, so 
what exceptions we have should be based on what types of things it makes sense 
to catch in order to handle those errors specifically rather than them being 
treated as a general error, or even a general error of a specific category. 
Having a solid hierarchy is great and very much needed, but I fear that your 
DIP is too focused on getting rid of exception types rather than shifting them 
into their proper place in the exception hierarchy. In some cases, that _does_ 
mean getting rid of exception types, but I think that on the whole, it's more 
of a case of creating new base classes for existing exceptions so that we have 
key base classes in the hierarchy. The DIP focuses on those base classes but 
seems to want to get rid of the rest, and I think that that's a mistake.

One more thing that I would point out is that your definition of 
DocParseException won't fly. file and line already exist in Exception with 
different meanings, so you'd need different names for them in DocParseException.

- Jonathan M Davis
April 01, 2013
Re: DIP33: A standard exception hierarchy
On Monday, April 01, 2013 19:02:52 Steven Schveighoffer wrote:
> On Mon, 01 Apr 2013 18:26:22 -0400, Jonathan M Davis <jmdavisProg@gmx.com>
> 
> wrote:
> > On Monday, April 01, 2013 21:33:22 Lars T. Kyllingstad wrote:
> >> My problem with subclasses is that they are a rather heavyweight
> >> addition to an API. If they bring no substance (such as extra
> >> data), I think they are best avoided.
> > 
> > Except that they're extremely valuable when you need to catch them.
> > Being able
> > to do something like
> > 
> > try
> > {
> > 
> > ...
> > 
> > }
> > catch(FileNotFoundException fnfe)
> > {
> > 
> > //handling specific to missing files
> > 
> > }
> > catch(PermissionsDeniedException pde)
> > {
> > 
> > //handling specific to lack of permissions
> > 
> > }
> > catch(FileExceptionfe
> > {
> > 
> > //generic file handling error
> > 
> > }
> > catch(Exception e)
> > {
> > 
> > //generic error handling
> > 
> > }
> > 
> > can be very valuable. In general, I'd strongly suggest having subclasses
> > for
> > the various "Kind"s in addition to the kind field. That way, you have the
> > specific exception types if you want to have separate catch blocks for
> > different
> > error types, and you have the kind field if you just want to catch the
> > base
> > exception.
> > 
> > If anything, exceptions are exactly the place where you want to have
> > derived
> > classes with next to nothing in them, precisely because it's the type
> > that the
> > catch mechanism uses to distinguish them.
> 
> In general, this is not enough. Imagine having an exception type for each
> errno number. Someone may want that!

Obviously, there are limits. You don't want exceptions for absolutely every 
possible error condition under the sun, but a lot of errnos are quite rare and 
likely wouldn't be caught explicitly very often anyway. And something like 
FileNotFoundException _would_ be caught and handled differently from other file 
exceptions often enough to merit its own exception IMHO. What's being 
presented in this DIP is very sparse, and at least some portion of the kinds 
should be represented by derived types in addition to the kinds.

> Note that there are two categories of code dealing with thrown exceptions:
> 
> 1. whether to catch
> 2. what to do
> 
> Right now, we have the super-basic java/c++ model of matching the type for
> item 1. D could be much better than that:
> 
> catch(SystemException e) if(e.errno == EBADF)
> {
> ...
> }
> 
> For item 2, once you have the caught exception, you have mechanisms to
> deal with the various fields of the exception. So even without
> improvements to #1, you can rethrow the exception if it's not what you
> wanted. Just the code isn't cleaner:
> 
> catch(SystemException e)
> {
> if(e.errno != EBADF)
> throw e;
> }

Adding new features to the language would changes things a bit, but without 
that, having specific exception types is generally the way to go. Otherwise, 
you get stuck doing things like putting switch statements in your catch blocks 
when we already have a perfectly good catch mechanism for separating out error 
types by the type of the exception being caught.

- Jonathan M Davis
April 01, 2013
Re: DIP33: A standard exception hierarchy
On Mon, Apr 01, 2013 at 03:25:48PM -0700, Walter Bright wrote:
> On 4/1/2013 2:20 PM, Simen Kjærås wrote:
> >I am reminded of Therac-25[1]. though the situation there was
> >slightly different, similar situations could arise from not turning
> >off hardware.
> 
> Relying on a program running correctly in order to avoid disaster is a
> terrible design. Even mathematically proving a program to be correct
> is in no way, shape, or form sufficient to deal with this.

"Beware of bugs in the above code; I have only proved it correct, not
tried it." -- Donald Knuth


T

-- 
Кто везде - тот нигде.
April 01, 2013
Re: DIP33: A standard exception hierarchy
On Mon, 01 Apr 2013 19:19:31 -0400, Jonathan M Davis <jmdavisProg@gmx.com>  
wrote:

> On Monday, April 01, 2013 19:02:52 Steven Schveighoffer wrote:

>> In general, this is not enough. Imagine having an exception type for  
>> each
>> errno number. Someone may want that!
>
> Obviously, there are limits. You don't want exceptions for absolutely  
> every
> possible error condition under the sun, but a lot of errnos are quite  
> rare and
> likely wouldn't be caught explicitly very often anyway. And something  
> like
> FileNotFoundException _would_ be caught and handled differently from  
> other file
> exceptions often enough to merit its own exception IMHO. What's being
> presented in this DIP is very sparse, and at least some portion of the  
> kinds
> should be represented by derived types in addition to the kinds.

I admit I haven't read the DIP yet, but I was responding to the general  
debate.  I agree with Lars that exceptions that add no data are hard to  
justify.

But I also hate having to duplicate catch blocks.  The issue is that class  
hierarchies are almost never expressive enough.

contrived example:

class MyException : Exception {}
class MySpecificException1 : MyException {}
class MySpecificException2 : MyException {}
class MySpecificException3 : MyException {}

try
{
   foo(); // can throw exception 1, 2, or 3 above
}
catch(MySpecificException1 ex)
{
   // code block a
}
catch(MySpecificException2 ex)
{
   // code block b
}

What if code block a and b are identical?  What if the code is long and  
complex?  Sure, I can put it in a function, but this seems superfluous and  
verbose -- exceptions are supposed to SIMPLIFY error handling, not make it  
more complex or awkward.  Basically, catching exceptions is like having an  
if statement which has no boolean operators.

Even if I wanted to write one block, and just catch MyException, then  
check the type (and this isn't pretty either), it's not exactly what I  
want -- I will still catch Exception3.  If this is the case, I'd rather  
just put an enum in MyException and things will be easier to read and  
write.

That being said, this is the mechanism we have, and the standard library  
shouldn't fight that.  I will have to read the DIP before commenting  
further on that.

-Steve
April 02, 2013
Re: DIP33: A standard exception hierarchy
On Monday, 1 April 2013 at 23:52:52 UTC, Steven Schveighoffer 
wrote:
> contrived example:
>
> class MyException : Exception {}
> class MySpecificException1 : MyException {}
> class MySpecificException2 : MyException {}
> class MySpecificException3 : MyException {}
>
> try
> {
>    foo(); // can throw exception 1, 2, or 3 above
> }
> catch(MySpecificException1 ex)
> {
>    // code block a
> }
> catch(MySpecificException2 ex)
> {
>    // code block b
> }
>
> What if code block a and b are identical?

I was thinking about this too.  And the most obvious answer in D 
is not that great.

try {
    foo(); // can throw 1, 2, or 3
}
catch ( Exception ex )
{
    if ( cast( Exception1 ) ex !is null || cast( Exception2 ) ex 
!is null )
    {
        // recovery code
    }
    else throw ex;
}


Ew. The first thing that comes to mind is separating the variable 
from the condition, thus allowing multiple matches.

catch ex ( Exception1, Exception2 )
{
    // recovery code
}

The necessary semantic caveat being that the type of 'ex' would 
be the nearest common base type to the named exception types.  
(The syntax is similar to some languages that have built-in error 
types and the like.)

Combined with the previous proposal of being able to attach an 
if-constraint to catch blocks, I suppose it could be rather 
elaborate (powerful though?).
April 02, 2013
Re: DIP33: A standard exception hierarchy
On 2013-04-02 01:52, Steven Schveighoffer wrote:

> I admit I haven't read the DIP yet, but I was responding to the general
> debate.  I agree with Lars that exceptions that add no data are hard to
> justify.
>
> But I also hate having to duplicate catch blocks.  The issue is that
> class hierarchies are almost never expressive enough.
>
> contrived example:
>
> class MyException : Exception {}
> class MySpecificException1 : MyException {}
> class MySpecificException2 : MyException {}
> class MySpecificException3 : MyException {}
>
> try
> {
>     foo(); // can throw exception 1, 2, or 3 above
> }
> catch(MySpecificException1 ex)
> {
>     // code block a
> }
> catch(MySpecificException2 ex)
> {
>     // code block b
> }
>
> What if code block a and b are identical?  What if the code is long and
> complex?  Sure, I can put it in a function, but this seems superfluous
> and verbose -- exceptions are supposed to SIMPLIFY error handling, not
> make it more complex or awkward.  Basically, catching exceptions is like
> having an if statement which has no boolean operators.
>
> Even if I wanted to write one block, and just catch MyException, then
> check the type (and this isn't pretty either), it's not exactly what I
> want -- I will still catch Exception3.  If this is the case, I'd rather
> just put an enum in MyException and things will be easier to read and
> write.

The obvious solution to that would be to be able to specify multiple 
exception types for a single catch block:

catch (MySpecificException1, MySpecificException2 ex)
{
}

-- 
/Jacob Carlborg
April 02, 2013
Re: DIP33: A standard exception hierarchy
On Monday, 1 April 2013 at 22:46:49 UTC, Ali Çehreli wrote:
> On 04/01/2013 02:01 PM, Dmitry Olshansky wrote:> 02-Apr-2013 
> 00:34, Ali Çehreli пишет:
>
> >> The failed assertion may be the moment when the program
> detects that
> >> something is wrong. A safe program should stop doing
> anything else.
> >
> > And that's precisely the interesting moment. It should stop
> but the
> > definition of "stop" really depends on many factors. Just
> pretending
> > that calling abort is a panacea is totally wrong IMO.
> >
> > BTW what do you exactly mean by "safe" program?
>
> I meant a program that wants to produce correct results. I was 
> indeed thinking about Therac-25 that Simen Kjærås mentioned. I 
> agree that there must be hardware fail-safe switches as well 
> but they could not protect people from every kind of software 
> failure in that example.
>
> Having said that, I can see the counter argument as well: We 
> are in an inconsistent state, so trying to do something about 
> it could be better than not running a cleanup code. But I also 
> remember that an AssertError may be thrown by an assert() call, 
> telling us that a programmer put it in there explicitly, 
> meaning that the program cannot continue. If there was any 
> chance of recovery, then the programmer could have thrown an 
> Exception or remedy the situation right then.
>

Yes, this is definitively a per case issue.

Not running cleanup code can transform a small issue in a big 
disaster as running can make the problem worse.

I don't think wiring in the language the fact that error don't 
run the cleanup code is rather dangerous.

If I had to propose something, it would be to handle error the 
same way exception are handled, but propose a callback that is 
ran before the error is throw, in order to allow for complete 
program stop based on user logic.
April 02, 2013
Re: DIP33: A standard exception hierarchy
On Monday, 1 April 2013 at 11:08:16 UTC, Lars T. Kyllingstad 
wrote:
> It's time to clean up this mess.
>
> http://wiki.dlang.org/DIP33

Several things.

First the usage of enums isn't the right path. This makes it hard 
to extend in general, and it is a poor man replacement for sub 
classes in general.

As a rule of thumb, when you use switch in OOP code, you are 
likely to do something wrong.

Second, many of you error are recoverable here. It isn't quite 
satisfying.

RangeError is a very bad thing IMO. It completely hides why the 
range fails in the first place. Trying to access front when not 
possible for instance, is an error for a reason (which is range 
dependent). That reason must be the source of the error/exception.

In general the hierarchy is weird. Why isn't NetworkingException 
(why not NetworkException ?) a subclass of IOException ?

OutOfMemoryError on its own isn't good IMO. The Error hierarchy 
is made for error that aren't recoverable (or may not be 
recoverable). It include a whole class of problem, and OOM is 
only one of them (another example is Stack overflow errors).
April 02, 2013
Re: DIP33: A standard exception hierarchy - why out-of-memory is not recoverable
On Monday, 1 April 2013 at 20:58:00 UTC, Walter Bright wrote:
> On 4/1/2013 4:08 AM, Lars T. Kyllingstad wrote:
> 5. Although a bad practice, destructors in the unwinding 
> process can also allocate memory, causing double-fault issues.
>

Why is double fault such a big issue ?

> 6. Memory allocation happens a lot. This means that very few 
> function hierarchies could be marked 'nothrow'. This throws a 
> lot of valuable optimizations under the bus.
>

Can we have an overview of the optimization that are thrown under 
the bus and how much gain you have from them is general ? Actual 
data are always better when discussing optimization.

> 7. With the multiple gigs of memory available these days, if 
> your program runs out of memory, it's a good sign there is 
> something seriously wrong with it (such as a persistent memory 
> leak).
>

DMD regularly does.
April 02, 2013
Re: DIP33: A standard exception hierarchy
02-Apr-2013 14:23, deadalnix пишет:
> On Monday, 1 April 2013 at 22:46:49 UTC, Ali Çehreli wrote:
[snip]
> Not running cleanup code can transform a small issue in a big disaster
> as running can make the problem worse.
>
> I don't think wiring in the language the fact that error don't run the
> cleanup code is rather dangerous.
>
> If I had to propose something, it would be to handle error the same way
> exception are handled, but propose a callback that is ran before the
> error is throw, in order to allow for complete program stop based on
> user logic.

It's exactly what I have in mind as removing the exception handling is 
something user can't recreate easily. On the other hand "die on first 
signs of corruption" is as easy as a hook that calls abort before unwind 
of Error.

Time to petition Walter ;)

-- 
Dmitry Olshansky
1 2 3 4 5 6 7 8
Top | Discussion index | About this forum | D home