February 19, 2012
"Walter Bright" <newshound2@digitalmars.com> wrote in message news:jhpqdj$30t6$1@digitalmars.com...
> (Also, having a large number of exception types is going to produce a lot of program size bloat. Remember, for EVERY class type, you've got the vtbl[], the .init data, and the TypeInfo. Going to town on exception types can really add this up.)

As a counterpoint: With a standard exception heirachy in some cases executable size will be decreased, because it will reduce the number of libraries inventing their own similar heirachy.


February 19, 2012
On Sat, 18 Feb 2012 20:21:46 -0600, Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Saturday, February 18, 2012 19:59:52 Robert Jacques wrote:
>> But you _always_ know what went wrong:
>
> No you don't. Take getopt for instance. If I want to appropriately handle what
> went wrong that caused getopt to throw an exception, I need information on
> what went wrong. I need to know what flag failed and why it failed (e.g.
> unknown or it was given an invalid value). Exception doesn't give you any of
> that. You need a more specific exception type to get that information.

There's a difference between knowing what went wrong and knowing how to fix it. That said, I fully agree with your excellent post. My intent wasn't to propose or support the use of error codes, but to observe that effective use typed exceptions tends to look a lot like error code. For instance, the function calling getop knows how to use getop, what getop's exceptions are and can (hopefully) use the exception's extra information to recover gracefully. But now consider the function that calls the function that calls getop. It probably doesn't even know that getop can be called and that its exceptions can be thrown. Even if it did, it probably doesn't have the access to getop needed utilize that information in the recovery process. Worse, if function A could leverage the type exception, it probably means A and B's implementations are heavily coupled leading to long term maintenance issues.

With the number of times 'bad exception hierarchy' has come up in this discussion and the number of times I've seen MyClassException be a simple wrapper around whatever happens to throw inside MyClass, I guess I'm just ready to explorer solutions out of the box. All my brains have been able to come up with so far is a) beef up exception to aid generic recovery code and b) add a TupleException/VariantException that would allow you to express enforce(isFlagGood[i], i, flags[i], "My flag error message"); or something. i.e. a way to provide extra info when available in the leanest possible manner.
February 19, 2012
"Nick Sabalausky" <a@a.a> wrote in message news:jhprac$2aj$1@digitalmars.com...
> The only problem I've ever had with them is that there's no templated catch blocks, so you can't handle two different exceptions with the same code without violating DRY or worse: catching the common base type and rethrowing when it's not what you wanted. Toss in templated catch blocks, and I've have no problem at all.
>

Do you mean something like this?
try
{
    something();
}
catch (e : ThisException, ThatException, OtherException)
{
    static assert(is(typeof(e) == CommonType!(ThisException, ThatException,
OtherException));
}
catch (Exception e)
{
    // Every other type derived from Exception
}

Or do you think the full power to be able to template catch blocks as if they were functions would be useful for something?


February 19, 2012
On Sat, 18 Feb 2012 23:09:17 -0600, Jim Hewes <jimhewes@gmail.com> wrote:
> On 2/18/2012 5:59 PM, Robert Jacques wrote:
>> But you _always_ know what went wrong: An unexpected error occurred
>> while trying to do X, where X is whatever is inside the try-catch block.
>> Exceptions are for exceptional situations...
>
> Not to jump on you in particular for using the phrase "exceptions are
> for exceptional situations”, but I've always heard people say this in
> arguments about exceptions vs. returning error codes. Unfortunately
> those people usually never go on to define what exceptional situations
> are, so those trying to learn about using exceptions are left no better
> off. If exceptional cases are like divide-by-zero or hardware failures,
> then surely a bad parameter to a function is not “exceptional”. Then
> what? Is out-of-memory exceptional or something you should expect might
> happen given that memory is finite?
>
> I think of exception handling as tied to contract programming. A
> function has a specific job that it's supposed to do. If for any reason
> it cannot do that job successfully, an exception should be thrown. That
> can include even bad parameters (although if you have bad parameters to
> internal functions I'd think that is a design bug and could be handled
> by asserts). Look at the .NET library; it seems to work this way. So I
> think the term 'exceptional situations' has been kind of useless.

Not to jump on you in particular :) but bad user parameters should never be treated as exceptional. Even bad 'internal' parameters that are passed via the external API aren't exceptional. Programmers being lazy about input parameter checking is how hackers make their money.

[snip]

>> I see typed exceptions as having a strong
>> coupling problem; exception handling that is aware enough to handle a
>> typed exception, is so close to the exception generation code that most
>> of the information conveyed by the 'type' is also conveyed by the
>> context. Now, the further away exception handling gets from exception
>> generation, the less information context coveys, but the ability of the
>> handler to do anything smarter than log the exception and retry / cancel
>> becomes less and less.
>
> I can't say agree there, if I'm understand you right. Not every
> exception coming out of a function was generated by that function. It
> may have come from several levels below that. Maybe you can handle the
> former because it is more immediate but not the latter. So without
> exception types how would you know the difference between them? You
> could use error codes and switch on them but it defeats one of the main
> purposes of exception handling. So I think if there are exceptions
> generated from further away, it's an argument _for_ exception types
> rather than against it.
>
> I can think of a example. At work I wrote a driver that communicates to
> a device over USB. If the device is suddenly unplugged, the lowest level
> of code throws a “connection” exception. And this can happen in several
> places. Most levels can't do anything about this (other than just be
> exception safe) and the exception just percolates up where things get
> closed and cleaned up. However, it would be possible, though not
> currently implemented, to recover from this such that the driver waits
> for the device to get reconnected and refreshes handles and all that,
> and the application and user above never need to know. Where that
> recovery happens isn't necessarily at the lowest level but somewhere in
> a middle layer. So I think having a particular exception type is useful
> here. But I don't want to have to check error codes because under normal
> circumstances there are just too many places to check for this type of
> error.

Yes, the USB stack has a high-level layer that can recover from a connection loss, but the rest of the protocol stack above and below it can't understand it and presumably ignore it. And you have just introduced a fairly long range dependency between the lowest level of your code and middle layer. (To say nothing of the implicit dependences implied with the intervening layers). This might be the best/only solution to the problem, but it also makes the code base larger and more fragile. I wasn't speaking in absolutes, but in relative difficulties.

>  > The other issue I see with typed exceptions is
>> that the classic 'is a' relationship seems weak; to be informative and
>> useful and exception must map to a specific or small set of
>> error/unexpected conditions. However, this specificity naturally leads
>> to large number of final classes, which isn't really leveraging OO
>> principals. It's hard to see what, if any, benefit the intermediate base
>> classes give over a generic exception.
>
> Well, I think what H.S Tech has been saying in this thread makes sense
> to me. You can have an exception hierarchy where base classes represent
> groups or classes of errors that you can check for without having to
> catch all exception. But you don't have thousands of classes where you
> map each possible error code to a unique class. You can just have more
> general types and then put the specific information in the exception.
> For example, just have one BadParameter exception and then store
> information about which parameter is bad and why in the exception.
>
> Jim

Traditional error codes are enums and so what your describe as BadParameter is an exception wrapping an error code. I really hope no one has been proposing to map error code values to exceptions on a 1:1 basis. But your making many of my points; exceptions are (better, faster, stronger) error codes. My issue regarding the weak 'is a' relationship stems from the extra information in any given final typed exception being very specific to that particular exception. So its hard for me to see the rational between treating some of these as a group while not all of them: if MyGroupException is providing no more information then Exception, why can a function recover a MyGroupException and not a general Exception?
February 19, 2012
On Sat, 18 Feb 2012 23:08:21 -0600, Sean Cavanaugh <WorksOnMyMachine@gmail.com> wrote:
> On 2/18/2012 11:07 PM, Walter Bright wrote:
>> On 2/18/2012 8:08 PM, bearophile wrote:
>>> To improve this discussion a small benchmark is useful to see how much
>>> bloat this actually causes.
>>
>> It'll increase with reflection and perfect garbage collection.
>
> Are these coming?  :)
>

Yes. Heap-perfect GC has had patches in Bugzilla, though they've suffered bit-rot. I'd really like a fully precise collector, but that is more work for Walter. Reflection is part of the variant refactoring I've been working on. I've kept the design very limited so far, but it does result in a lot of codegen and slow compile times in certain situations (mostly due to type tuple bugs and Algebraic).
February 19, 2012
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.

Andrei

February 19, 2012
On 2/18/12 11:09 PM, Jim Hewes wrote:
> I think of exception handling as tied to contract programming. A
> function has a specific job that it's supposed to do. If for any reason
> it cannot do that job successfully, an exception should be thrown. That
> can include even bad parameters (although if you have bad parameters to
> internal functions I'd think that is a design bug and could be handled
> by asserts). Look at the .NET library; it seems to work this way. So I
> think the term 'exceptional situations' has been kind of useless.

I think there's a bit of a confusion there. In fact, I dedicated two distinct chapters to error handling and contract programming in TDPL in an attempt to dispel it.

Andrei

February 19, 2012
On Sun, Feb 19, 2012 at 12:43:58AM -0600, 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.
[...]

But if that's the case, what's the use of an exception at all? Why doesn't the function concerned simply retry on its own? Network stack code does that. It would be nightmarish to program network applications if you always have to implement retry on your own (much less use *exceptions* to handle them)!


T

-- 
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry
February 19, 2012
On Sunday, February 19, 2012 16:59:49 Daniel Murphy wrote:
> "Nick Sabalausky" <a@a.a> wrote in message news:jhprac$2aj$1@digitalmars.com...
> 
> > The only problem I've ever had with them is that there's no templated catch blocks, so you can't handle two different exceptions with the same code without violating DRY or worse: catching the common base type and rethrowing when it's not what you wanted. Toss in templated catch blocks, and I've have no problem at all.
> 
> Do you mean something like this?
> try
> {
>     something();
> }
> catch (e : ThisException, ThatException, OtherException)
> {
>     static assert(is(typeof(e) == CommonType!(ThisException, ThatException,
> OtherException));
> }
> catch (Exception e)
> {
>     // Every other type derived from Exception
> }
> 
> Or do you think the full power to be able to template catch blocks as if they were functions would be useful for something?

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.

- Jonathan M Davis
February 19, 2012
On 2/18/12 8:45 PM, Jonathan M Davis wrote:
> On Saturday, February 18, 2012 20:28:32 Andrei Alexandrescu wrote:
>> On 2/18/12 6:36 PM, H. S. Teoh wrote:
>>> Note also, that an elaborated exception hierarchy lets you attach
>>> additional specific information that might be useful in error recovery.
>>> For example, FileException may have a field containing the filename that
>>> caused the error, so that if the program is processing a list of
>>> filenames, it knows which one went wrong. You would not want such
>>> information in Exception, because it only applies to FileException's.
>>
>> If an exception adds state and/or interface to the table, I agree that
>> may justify its existence as a distinct type.
>
> If all you're arguing is that something like StringException shouldn't exist
> because it doesn't add any additional member fields, then that's a much more
> reasonable thing to debate. I'm not quite sure I agree, but some of your
> responses seem to indicate that you don't want a rich exception hierarchy.

Ideally we'd have the right number of types - not more, not less. The main purpose of this thread is to figure how many exception types are "just right". There has been a tendency in Phobos to just add a module-specific exception to certain modules, without a good reason. I'm trying to figure out the reasons.

> In the case of getopt, at _least_ adding a GetOptException with a field for the
> failed flag would be very valuable, and having additional derived types which
> indicate _why_ it failed would also be valuable.

The additional state sounds fine, but I'm not so sure about adding one type per failure reason.

> And in some cases at least,
> that would lead to more member fields in the derived exceptions (like the value
> of the flag if it were given a bad value).

And aside from that? The universe of possible errors in getopt is finite, closed, and actually fairly small. Can't we do with fewer types?

> I would argue however that there _are_ times when having a derived exception
> which has no additional data beyond its type rather than simply Exception can
> be useful - _especially_ when it's at the base of a larger hierarchy. For
> instance, if we had an IOException, you could know that whatever operation you
> were trying to do went badly because of an IO problem. You would probably need
> to handle it as a subclass of IOException to get any particularly useful
> information on how to handle the problem, but just knowing that it was an I/O
> problem could be enough in some cases.

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.

> I really think that whether adding an exception type which adds no additional
> member fields is a good idea should be evaluated on a case-by-case basis. We
> don't want to needlessly create a bunch of useless, uninformative exception
> types, but be we also want a rich enough exception hierarchy that it's
> possible to intelligently recover from exceptions.

I think right now we're erring a bit on the side of defining useless and uninformative exception types. As far as the originating module goes, it makes perfect sense to make that a field in the base class.

Anyway, a simple action items right now is improve Exception's offering with things like e.g. custom formatting for i18n, more origin information, cloning, attributes and capabilities (transitory, user-visible etc).


Andrei
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18