April 01, 2013
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
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
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
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
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
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
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
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
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
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 9 10 11 12 13 14
Top | Discussion index | About this forum | D home