September 15, 2004
> After writing this post, I recognized that the base class  Throwable  is a bit pointless for runtime generated Errors. Since these Errors neither
have a
> user message nor a nested exception.
>
> So here's another, less java-like exception hierarchy.
> The presented guidelines for using Exceptions/Errors remain valid for this
> hierarchy.
>
>
> Object
> |
> |TooManyThreads
> |OutOfMemory
> |AssertionError
> |   |ArrayBoundsError
> |   |AssertError
> |   |SwitchError
> |
> |Exception
>     |
>     |CheckedException    // should be catched
>     |    |
>     |    |FileException
>     |    ...
>     |UncheckedException  // safe to catch
>     |    |
>     |    |FormatException
>     |    ...
>     |
>     |UnrecoverableError  // unsafe to catch
>         |
>         |NullArgumentError
>         ...

Very nice post! You've convinced me of the general ideas. Some comments, though.

It seems wierd to have Error at the top level and under UnrecoverableError. Also I'd like to toss around some more names (Checked and Unchecked don't mean too much to me). Finally I'd like ArrayBoundsError to subclass the more general (IllegalIndexException) and made into a MidSeverityException. Any class or container that wants to overload opIndex and is passed a bogus index can throw IllegalIndexException.

Exception (has optional msg and cause. The message can contain the module
and/or fcn )
 |
 |LowSeverityException: sh*t happens
 |   |
 |   | FileException
 |   | ArithmeticException
 |   |   |OverflowException
 |   ...
 |MidSeverityException: most like a coding error to be tracked during
debugging
 |   |
 |   | FormatException (maybe? I don't know what this is)
 |   | IllegalIndexException: throw this when opIndex/Assign gets an illegal
index
 |   |   ArrayBoundsException
 |   | ArgumentException: throw this when someone passes you a bad input
 |   | NotImplementedException: throw in a stub function that isn't done yet
 |   | NotSupportedException: same as Java's - throw when something isn't
supported.
 |   ...
 |HighSeverityException: dangerous to continue program
 |   |
 |   | TooManyThreadsError
 |   | OutOfMemoryError
 |   | AssertError
 |   ...

To illustrate how to use the classification, let's take FormatException.
Right now FormatException (actually it's currently called FormatError) is
thrown in many contexts in std.format with a message that says what went
wrong. So a bad input is FormatError("too few inputs") and int overflow is
FormatError("int overflow"). I think rather than doing that format should
throw ArgumentException("std.format: too few inputs") and
OverflowException("std.format integer format overflow").

I'm a bit up in the air about putting the module or function in the message but as long as it is standardized it would be easy to trim that info out if one wanted to show the error message to a user. If we could get the stack trace out of an exception it would be much better, but getting the stack is non-trivial.

-Ben


September 15, 2004
Just wanted to thank you for a very detailed and thought-provoking analysis. I'll be examining this a lot more next week when I do my analysis, and may have some questions to level at you at that time.

Cheers

Matthew


"Farmer" <itsFarmer.@freenet.de> wrote in message news:Xns8FB0B405185D2itsFarmer@63.105.9.61...
> "Matthew" <admin.hat@stlsoft.dot.org> wrote in news:chrebn$12f7$1@digitaldaemon.com:
>
>> I've persuaded big-W that the exception hierarchy refactoring can wait no longer, but to do so I've had to volunteer to marshal the efforts.
>>
>> My plan is as follows:
>>
>> 1. Solicit *on-topic* criticisms from the newsgroup
>>
>> 2. Put on my code reviewer's hat, and do a detailed analysis and criticism of the current situation. I'll do this *before* I read any of these criticisms, so as to be unbiased in the review. This means you've a week or so before I'll be digesting and collating any responses from the community, so please take your time to provide considered comments.
>>
>>[snip]
>
>
> I propose this (over-engineered) java-like exception hierarchy:
>
> Object
> |
> |Throwable
>    |
>    |Error
>    |    |
>    |    |TooManyThreadsError
>    |    |OutOfMemoryError
>    |    |AssertionError
>    |        |
>    |        |ArrayBoundsError
>    |        |AssertError
>    |        |SwitchError
>    |
>    |Exception
>        |
>        |CheckedException
>        |    |
>        |    |FileException
>        |    ...
>        |UncheckedException
>             |
>             |FormatException
>             ...
>
>
>
> What's a Throwable?
> -------------------
>
> By convention all exceptions derive from Throwable. It's still possible to throw any object, though. This way you can create your own exception hierarchy if needed.
>
> // Throwable contains an exception message and keeps track of
> // nested exceptions.
> class Throwable
> {
> private char[] msg;
> private Throwable next;
>
> this(char[] msg="", Throwable next=null);
>
> // returns a (user) message that describes the cause of the exception;
> // returns an empty string, if no message is available
> char[] getMessage();
>
> // returns the nested exception or null if there is none
> Throwable getNext();
>
> // returns a (somewhat cryptic) string
> char[] toString()
> {
>    char[]rs=this.classinfo.name~": "~this.getMessage();
>    if (next)
>        rs~="\n"~next.toString();
>    return rs;
> }
> }
>
>
>
> What are Errors?
> ----------------
>
> Errors are thrown for exceptions that *might* require drastic means to recover from; like terminating the process or the thread, switching over to a backup system, rebooting computer (for Win9x), etc.
>
> When should an exception be derived from Error?
>
> 1.) when there are no guarantees when the exception occurs:
> At minimum
>    a) there must be no resource leaks
>    b) the destructor of an instance must be safely callable
> if an exception is thrown.
> If these minimum guarantees cannot be met, the exception is an Error.
> Furthermore, classes often have stronger guarantees, e.g. instance remains
> in a valid state if an exception is thrown. For cases that don't permit
> these stronger guarantees, an Error should be thrown instead of an
> Exception.
>
> 2.) when the exception might not be thrown in release builds:
> Although these exceptions could be safely catched in debug builds, the
> conditions that caused them would likely cause havoc for release builds.
>
> What about out of memory errors?
> -------------------------------
>
> I argue that out of memory conditions are Errors, since in the days of
> virtual memory, most programmers assume that there is always enough memory
> available, unless a very large amount of memory is requested.
> Consequently, the vast majority of libraries don't come with exception
> guarantees when memory is running low.
> For the same reasons  std.thread.Thread.start()  should throw a
> TooManyThreadsError exception instead of a TooManyThreadsException.
>
>
> Why AssertionError?
> -------------------
>
> Switch errors, array index errors and failed assert statements, mean all but one thing: A bug was revealed.
>
> // AssertionError doesn't provide an exception message, but stores
> // the location where the exception was thrown.
> class AssertionError : Error
> {
> private uint linnum;
> private char[] filename;
>
> this(char[] filename, uint linnum)
> {
>    super();
>    this.filename=filename
>    this.linnum=linnum
> }
>
> uint getLinenum();
> char[] getFilename();
>
> char[] toString()
> {
>    return this.classinfo.name~" exception source: "
> ~filename~" ("~toString(linnum)~")";
> }
> }
>
>
> Speaking of AssertionErrors, it would be useful if DMD offered an option
> "-spoiledprogrammer"
> for spoiled programmers.
> If this option is enabled, AssertionErrors would additionally contain
>    * the function name for all AssertionErrors
>    * the expression of an assert statement
>    * the index that causes an ArrayBoundsError
>    * the actual value that causes a SwitchError
>
>
>
> What are Exceptions?
> --------------------
>
> Exceptions are thrown for conditions that leave the process in a
> defined state. Since Exceptions are thrown for predictable situations, it
> is usually easy to safely recover from the exceptional situation.
>
>
>
> We don't need no stinking checked exceptions!
> ---------------------------------------------
>
> Agreed, no checked exceptions. While the constant nagging of the Java compiler about undeclared checked exceptions isn't particularly useful, the concept behind them is. So, borrow the concept from Java, but not it's implementation.
>
> By convention, CheckedExceptions are thrown for errors that the programmer should carefully think about.
>
>
> Exceptions that derive from CheckedException
>    * cannot be avoided by the programmer. For instance, opening a file,
>    might fail: Even if you ensure that the file exists before opening the
>    file,  the file might have been deleted by the time it is actually
>    opened!
>    * might occur during the intended operation of a programm;
>    they do not necessarily indicate bugs.
>    * should be documented for all functions in the call hierarchy that
>    might throw them
>
>
>
> When to use UncheckedExceptions
> -------------------------------
>
> UncheckedExceptions are thrown for errors that don't require the programmer's attention, since these errors *usually* never happen. UncheckedExceptions usually indicate that a function is used in the wrong way by the caller.
>
>
> UncheckedExceptions
>    * can be avoided for most use-cases. E.g. std.format.doFormat(...)
>    throws a FormatException if the passed arguments do not match the
>    format pattern. The exception can be avoided by passing a correct
>    format pattern string.
>    * usually indicate bugs, but not always
>    * might not be documented in the function contract. Of course, it's
>    good practice to do so, especially for functions that directly throw
>    UncheckedExceptions.
>
>
> Errors vs. UncheckedExceptions
> ------------------------------
>
> UncheckedExceptions
>    * are part of the function's contract. As long as the contract isn't
>    changed, the caller can rely on UncheckedExceptions beeing thrown.
>    * leave the process in a defined state (exception guarantees like:
>    no resource leaks, object instances remain valid, etc. permit that the
> the process can safely recover from the exception )
>
>
> Errors
>    * are not necessarily part of the function contract: Checks that
>    throw Errors, might be absent in release builds or future versions of
>    the function.
>    * might leave the programm in an undefined state;
>    should the Error be ignored, unpredictable follow-up errors might
>    occur.
>
>
>
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
>
>
> I also like Ben's idea to include a bunch of generic exception classes in the standard library. This would be particularly handy for quick and dirty programming.
>
>
>
> Farmer.
>
>
>
>
> After writing this post, I recognized that the base class  Throwable  is a bit pointless for runtime generated Errors. Since these Errors neither have a user message nor a nested exception.
>
> So here's another, less java-like exception hierarchy.
> The presented guidelines for using Exceptions/Errors remain valid for this
> hierarchy.
>
>
> Object
> |
> |TooManyThreads
> |OutOfMemory
> |AssertionError
> |   |ArrayBoundsError
> |   |AssertError
> |   |SwitchError
> |
> |Exception
>    |
>    |CheckedException    // should be catched
>    |    |
>    |    |FileException
>    |    ...
>    |UncheckedException  // safe to catch
>    |    |
>    |    |FormatException
>    |    ...
>    |
>    |UnrecoverableError  // unsafe to catch
>        |
>        |NullArgumentError
>        ...
> 


September 15, 2004
First, to clear up one misunderstanding:
Unlike Matthew, I don't imply any mandatory way to recover from any kind of
errors. It should be possible to freely choose how to deal with errors to the
maximum extend that the given hardware permits.


Sean Kelly <sean@f4.ca> wrote in news:cia1l4$1jfe$1@digitaldaemon.com:

> In article <Xns8FB0B405185D2itsFarmer@63.105.9.61>, Farmer says...
>>
>>What are Errors?
>>----------------
>>
>>Errors are thrown for exceptions that *might* require drastic means to recover from; like terminating the process or the thread, switching over to a backup system, rebooting computer (for Win9x), etc.
>>
>>When should an exception be derived from Error?
>>
>>1.) when there are no guarantees when the exception occurs:
>> At minimum
>>    a) there must be no resource leaks
>>    b) the destructor of an instance must be safely callable
>>if an exception is thrown.
>>If these minimum guarantees cannot be met, the exception is an Error.

If I think more about it, destructors of instances must be callable at *any rate*. Otherwise use of auto classes (RAII) would become an unsafe practice.


> 
> In this case, I would argue that TooManyThreadsError and OutOfMemoryError should be derived from Exception, since they both meet these requirements.
> 
>>Furthermore, classes often have stronger guarantees, e.g. instance remains in a valid state if an exception is thrown. For cases that don't permit these stronger guarantees, an Error should be thrown instead of an Exception.
> 
> Could you go into this a bit more?  Are you just saying that, basically, if a class invariant is violated then an Error should be thrown?  If so, I *think* I agree but I'll have to think about it.

Yes. But this isn't restricted to class invariants. For example a function that adds an element to a container guarantees transactional behaviour in case of Exceptions. If for some reasons the function cannot ensure this guarantee, it could throw an Error as a last resort. E.g. A programmer puts an assert statement between statements that must never cause an exception. And now the assert statement actually fails.


>>2.) when the exception might not be thrown in release builds:
>>Although these exceptions could be safely catched in debug builds, the
>>conditions that caused them would likely cause havoc for release builds.
> 
> I'm having trouble thinking of an exception that might be recoverable in a debug build but not in a release build.  Or do you mean only that they could be thrown and caught safely?

For example:

void foo(Object o)
{
    debug
    {
        if (o is null)
            throw new NullArgumentException();
    }

    o.toString();
}
main()
{
    foo(null);
}

For debug builds an Exception is thrown that is definitely recoverable. But
in release builds a seg fault is likely.
What I meant is, that you should throw an NullArgumentError for such cases.


>And if this is the case, then I
> would argue that they shouldn't be Errors *or* Exceptions, but rather that the application should terminate with a core dump.

Terminating the application might be the right thing for most cases, but it shouldn't be the only option.



>>What about out of memory errors?
>>-------------------------------
>>
>>I argue that out of memory conditions are Errors, since in the days of virtual memory, most programmers assume that there is always enough memory available, unless a very large amount of memory is requested.
> 
> For a specific class of applications, yes.  But what about systems programming, embedded systems, etc?  I might argue that such applications should be designed such that an out of memory condition should never occur, but they certainly aren't designed as if memory were unlimited.  I think OutOfMemory should be an Exception because it's a situation that some applications may want to recover from, not because that is expected of all applications.
> 

It certainly depends on D's target audience:
Is the target audience limited to pedantic programmers that do life critical
applications? Or does it include programmers that rely on GCs to release
resources?
If you're doing systems programming and you designed your programm really
correctly and use libraries that are equally well designed. Nothing shall
stop you from catching an OutOfMemoryError and just continue.


>>Consequently, the vast majority of libraries don't come with exception guarantees when memory is running low.
> 
> I would argue that any library that is not exception safe is incorrect. It shouldn't matter what kind of exception it is.  So I assume that any library that doesn't document otherwise at least provides the basic guarantee.
> 

I agree, but still argue that we programmers are suppossed to write *correct* programms (or at least working programms), based on *incorrect* libraries. Actually, the whole point of the exception hierarchy is, to make it easier to accomplish this.


>>For the same reasons  std.thread.Thread.start()  should throw a TooManyThreadsError exception instead of a TooManyThreadsException.
> 
> For the same reason, I think this should be an exception :)  The user should be able to choose whether this constitutes an application failure.
> 
That's *one* option: provide a user setting that specifies what happens for a given kind of application failure.


> As for checked vs. unchecked exceptions, I'm not sure I understand the difference.  Are you saying that checked exceptions should be handled explicitly while unchecked exceptions can be caught via the Exception base class, the error reported, and that's it?  Obviously, all exceptions must be handled somewhere or the application will terminate.
> 
No. While there might be a certain trend how checked vs. unchecked exceptions are typically handled. It is not the definite decision criteria. It's more important what the cause for an exception is:

CheckedExceptions: cause is external: Caller cannot avoid them, consequently a programmer should usually focus on how to *handle* the exception.

UncheckedExceptions: cause is internal: Caller can avoid them, consequently a programmer should usually focus on how to *prevent* the exception.


Whether an exception is checked or unchecked depends on the anticipated use
of an API.
For example the function
    int atoi(char[] number)
converts a string to an integer. If the passed string isn't a valid number
should a CheckedException or an UncheckedException be thrown?
It depends on the function's intended purpose:
If its purpose is to convert a number then it should throw an
UncheckedException. But if its purpose is to "check if a string is a number
and convert it" then the function should throw a CheckedException.



Farmer.
September 15, 2004
In article <Xns8FB1A4F92693itsFarmer@63.105.9.61>, Farmer says...
>
>First, to clear up one misunderstanding:
>Unlike Matthew, I don't imply any mandatory way to recover from any kind of
>errors. It should be possible to freely choose how to deal with errors to the
>maximum extend that the given hardware permits.

Ah, thanks for clearing this up.  That distinction between Exceptions and Errors is one I'd been assuming.

>> Could you go into this a bit more?  Are you just saying that, basically, if a class invariant is violated then an Error should be thrown?  If so, I *think* I agree but I'll have to think about it.
>
>Yes. But this isn't restricted to class invariants. For example a function that adds an element to a container guarantees transactional behaviour in case of Exceptions. If for some reasons the function cannot ensure this guarantee, it could throw an Error as a last resort. E.g. A programmer puts an assert statement between statements that must never cause an exception. And now the assert statement actually fails.

Ah okay.  Then I do agree :)

>> I'm having trouble thinking of an exception that might be recoverable in a debug build but not in a release build.  Or do you mean only that they could be thrown and caught safely?
>
>For example:
>
>void foo(Object o)
>{
>    debug
>    {
>        if (o is null)
>            throw new NullArgumentException();
>    }
>
>    o.toString();
>}
>main()
>{
>    foo(null);
>}
>
>For debug builds an Exception is thrown that is definitely recoverable. But
>in release builds a seg fault is likely.
>What I meant is, that you should throw an NullArgumentError for such cases.

Gotcha.  So it's an Error to make the programmer more likely to notice and fix the problem during development.  In practice I would probably use asserts for this, which would classify them as errors anyway.

>>And if this is the case, then I
>> would argue that they shouldn't be Errors *or* Exceptions, but rather that the application should terminate with a core dump.
>
>Terminating the application might be the right thing for most cases, but it shouldn't be the only option.

True enough.  I suppose even memory corruption is recoverable in some scenarios (segmented memory) so perhaps it's inadvisable to have the language mandate behavior in this regard.  Though I suppose we're still stuck with termination if 2+ exceptions are thrown simultaneously.

>>>For the same reasons  std.thread.Thread.start()  should throw a TooManyThreadsError exception instead of a TooManyThreadsException.
>> 
>> For the same reason, I think this should be an exception :)  The user should be able to choose whether this constitutes an application failure.
>> 
>That's *one* option: provide a user setting that specifies what happens for a given kind of application failure.

This was due to my (incorrect) assumption that Errors necessitated program
termination.

>> As for checked vs. unchecked exceptions, I'm not sure I understand the difference.  Are you saying that checked exceptions should be handled explicitly while unchecked exceptions can be caught via the Exception base class, the error reported, and that's it?  Obviously, all exceptions must be handled somewhere or the application will terminate.
>> 
>No. While there might be a certain trend how checked vs. unchecked exceptions are typically handled. It is not the definite decision criteria. It's more important what the cause for an exception is:
>
>CheckedExceptions: cause is external: Caller cannot avoid them, consequently a programmer should usually focus on how to *handle* the exception.
>
>UncheckedExceptions: cause is internal: Caller can avoid them, consequently a programmer should usually focus on how to *prevent* the exception.

Ah, that makes perfect sense.  In that case, I think this is a valuable distinction to make.  Good work!


Sean


September 16, 2004
Sean Kelly <sean@f4.ca> wrote in news:ciajpr$1s3t$1@digitaldaemon.com:

> In article <Xns8FB1A4F92693itsFarmer@63.105.9.61>, Farmer says...
[snip]
>>
>>> I'm having trouble thinking of an exception that might be recoverable in a debug build but not in a release build.  Or do you mean only that they could be thrown and caught safely?
>>
>>For example:
>>
>>void foo(Object o)
>>{
>>    debug
>>    {
>>        if (o is null)
>>            throw new NullArgumentException();
>>    }
>>
>>    o.toString();
>>}
>>main()
>>{
>>    foo(null);
>>}
>>
>>For debug builds an Exception is thrown that is definitely recoverable.
>>But in release builds a seg fault is likely.
>>What I meant is, that you should throw an NullArgumentError for such
>>cases.
> 
> Gotcha.  So it's an Error to make the programmer more likely to notice and fix the problem during development.  In practice I would probably use asserts for this, which would classify them as errors anyway.

Yes, it's an Error, but making errors sometimes is a good thing.

Asserts have one inconvenience: You can't tell at a glance what caused them. And who is responsible for the failure? The callee or the caller?

That's why a programmer might prefer to throw an Error that contains
additional information. But if an AssertError included:
    * the assertion expression
    * the location of the assertion: in-contract, out-contract, class-
invariant or function body
    * stacktrace

then in most cases, you could tell what causes an assertion, without using a debugger (or even looking at your code if you know it well).



>>>And if this is the case, then I
>>> would argue that they shouldn't be Errors *or* Exceptions, but rather that the application should terminate with a core dump.
>>
>>Terminating the application might be the right thing for most cases, but it shouldn't be the only option.
> 
> True enough.  I suppose even memory corruption is recoverable in some scenarios (segmented memory) so perhaps it's inadvisable to have the language mandate behavior in this regard.  Though I suppose we're still stuck with termination if 2+ exceptions are thrown simultaneously.
> 

I'm not sure how D is supposed to handle this situation. I tried to simply throw an exception within a destructor (just 1 exception was active) but the programmed crashed with an exception error e0440001H. I shall take a look at the bug forum.

I don't know if it's technically possible, but how about throwing a SimultanousExceptionError that contains both exceptions?


Regards,
   Farmer.
September 16, 2004
"Ben Hinkle" <bhinkle@mathworks.com> wrote in news:cia7sd$1n6u$1@digitaldaemon.com:

>> After writing this post, I recognized that the base class  Throwable is a bit pointless for runtime generated Errors. Since these Errors neither
> have a
>> user message nor a nested exception.
>>
>> So here's another, less java-like exception hierarchy.
>> The presented guidelines for using Exceptions/Errors remain valid for
>> this hierarchy.
>>
>>
>> Object
>> |
>> |TooManyThreads
>> |OutOfMemory
>> |AssertionError
>> |   |ArrayBoundsError
>> |   |AssertError
>> |   |SwitchError
>> |
>> |Exception
>>     |
>>     |CheckedException    // should be catched
>>     |    |
>>     |    |FileException
>>     |    ...
>>     |UncheckedException  // safe to catch
>>     |    |
>>     |    |FormatException
>>     |    ...
>>     |
>>     |UnrecoverableError  // unsafe to catch
>>         |
>>         |NullArgumentError
>>         ...
> 
> Very nice post! You've convinced me of the general ideas. Some comments, though.
> 
> It seems wierd to have Error at the top level and under UnrecoverableError.

I felt exactly the same way. And then I thought that the hierarchy is weird and asymmetrical: Could this be the D way?

Here's the hierarchy again, with two changes (TooManyThreadsError was in the wrong place and everything ends with "Error" now):

 Object
 |
 |OutOfMemoryError
 |AssertionError
 |   |ArrayBoundsError
 |   |AssertError
 |   |SwitchError
 |
 |Error
     |CheckedError    // should be catched
     |   |FileError
     |   ...
     |
     |UncheckedError  // safe to catch
     |   |FormatError
     |   ...
     |
     |UnrecoverableError  // unsafe to catch
         |TooManyThreadsError
         ...


I don't think, that it is too weird, as the only exceptions that are at the top level are truely special: They all belong to D's minimal RTL. These exceptions are directly mentioned in the D spec. For ArrayBoundsError, AssertError and SwitchError the constructor is even private, so only the compiler can create them.

Also, this design has the advantage that it avoids any implication whether OutOfMemoryErrors and contract-violations are unrecoverable or not. Especially the opinions about OutOfMemoryError/Exception are quite opposite. (E.g: post #6227 claims, that in Java all java.lang.Errors are unrecoverable, except for OutOfMemoryError which had better been classified as an Exception) [From a purily philosophical point I even agree: OutOfMemoryError should be a checked exception!]


> Also I'd like to toss around some more names (Checked and Unchecked
> don't mean too much to me).

They don't mean anything to me, either. I thought about other names, but
always came up with names that I feel are too specific and provoke wrong ad-
hoc decisions on the part of the programmer.
As an mnemonic aid I think of "Checked/Unchecked" as "I should check the
exception" and "I should leave it unchecked".


> Finally I'd like ArrayBoundsError to
> subclass the more general (IllegalIndexException) and made into a
> MidSeverityException. Any class or container that wants to overload
> opIndex and is passed a bogus index can throw IllegalIndexException.

I disagree, since Errors vs. Exception cannot be mixed.
If you wanted to throw an IllegalIndexError, then there's the problem that
ArrayBoundsError already inherits from AssertionError. The other way round
doesn't work too well either, since in general it is not possible to create
an IllegalIndexException that contains a sensible filename linenumber
context.


> 
> Exception (has optional msg and cause. The message can contain the
> module and/or fcn )
> |
> |LowSeverityException: sh*t happens
> |   |
> |   | FileException
> |   | ArithmeticException
> |   |   |OverflowException
> |   ...
> |MidSeverityException: most like a coding error to be tracked during
> debugging
> |   |
> |   | FormatException (maybe? I don't know what this is)
> |   | IllegalIndexException: throw this when opIndex/Assign gets an
> |   | illegal
> index
> |   |   ArrayBoundsException
> |   | ArgumentException: throw this when someone passes you a bad input
> |   | NotImplementedException: throw in a stub function that isn't done
> |   | yet NotSupportedException: same as Java's - throw when something
> |   | isn't
> supported.
> |   ...
> |HighSeverityException: dangerous to continue program
> |   |
> |   | TooManyThreadsError
> |   | OutOfMemoryError
> |   | AssertError
> |   ...
> 
> To illustrate how to use the classification, let's take FormatException.
> Right now FormatException (actually it's currently called FormatError)
> is thrown in many contexts in std.format with a message that says what
> went wrong. So a bad input is FormatError("too few inputs") and int
> overflow is FormatError("int overflow"). I think rather than doing that
> format should throw ArgumentException("std.format: too few inputs") and
> OverflowException("std.format integer format overflow").

I tend to agree about the ArgumentException, but I would prefer a MidSeverityException for all kinds of conversion errors, here.

Initially I thought, that the more general exceptions, should be available as Low-, Mid- and High- Severity exceptions. The problem is that one would either have to make up 3 different names for one exception or have 3 classes that have the same name, but are in different modules. So, it seems this doesn't work so well with generic exceptions :-(

So, It doesn't work!

My conclusion is that the exception could be laid out in (at least) two ways:
either
    * build up a deep class hierarchy in a "natural and object-oriented" way
or
    * build up a rather shallow hierarchy around the notion of CheckedError,
UncheckErrors and UnrecoverableErrors as virtually the only decision criteria


Also we could simply forget about un/recoverable, un/checked and severities levels and just go with a simpler hierarchy like.

Object
 |
 |OutOfMemoryError
 |AssertionError
 |   |ArrayBoundsError
 |   |AssertError
 |   |SwitchError
 |
 |Error
     |PhobosError
          |...

Probably, add a standard exception UnrecoverableError, if really needed.


> I'm a bit up in the air about putting the module or function in the message but as long as it is standardized it would be easy to trim that info out if one wanted to show the error message to a user. If

That's a good idea. A standard way to include the code source of an exception, that is really needed: Why does doFormat() throw an FormatException instead of an ArgumentException, after all? I bet it is simply to know that the exception was caused by doFormat() or writef() and not by one of the zillons of other functions that all throw ArgumentException.

But better don't put it into the exception message. Just store it in the separate property "source" and let Error.toString() include it. I'd like to have the exception message understandable for a technical-savy user, that knows nothing about programming. Things, that have only meaning to programmers should only be included by toString().


Farmer.





September 16, 2004
In article <Xns8FB1F10AFEB2AitsFarmer@63.105.9.61>, Farmer says...
>
>Sean Kelly <sean@f4.ca> wrote in news:ciajpr$1s3t$1@digitaldaemon.com:
>
>> Gotcha.  So it's an Error to make the programmer more likely to notice and fix the problem during development.  In practice I would probably use asserts for this, which would classify them as errors anyway.
>
>Yes, it's an Error, but making errors sometimes is a good thing.
>
>Asserts have one inconvenience: You can't tell at a glance what caused them. And who is responsible for the failure? The callee or the caller?

Combined with DBC, this should hopefully be pretty obvious, as I'd expect most asserts to be inside in/out blocks.

>That's why a programmer might prefer to throw an Error that contains additional information. But if an AssertError included:
>    * the assertion expression
>    * the location of the assertion: in-contract, out-contract, class-
>invariant or function body
>    * stacktrace
>
>then in most cases, you could tell what causes an assertion, without using a debugger (or even looking at your code if you know it well).

Agreed.  It would definately be nice if AssertError were more robust.

>> True enough.  I suppose even memory corruption is recoverable in some scenarios (segmented memory) so perhaps it's inadvisable to have the language mandate behavior in this regard.  Though I suppose we're still stuck with termination if 2+ exceptions are thrown simultaneously.
>
>I'm not sure how D is supposed to handle this situation. I tried to simply throw an exception within a destructor (just 1 exception was active) but the programmed crashed with an exception error e0440001H. I shall take a look at the bug forum.

I posted a bunch of bugs re: exceptions earlier this week.  Basically, exceptions currently mess with stack unwinding such that auto classes aren't destructed when they should be.  This makes testing the double-exception problem between difficult and impossible.

>I don't know if it's technically possible, but how about throwing a SimultanousExceptionError that contains both exceptions?

Good question.  I think this is possible but perhaps it's technically difficult to implement.  I'll have to do some digging and see if I can find the reasoning behind current C++ exception behavior (or perhaps Walter knows?).


Sean


September 19, 2004
Farmer wrote:
> 
> Here's the hierarchy again, with two changes (TooManyThreadsError was in the wrong place and everything ends with "Error" now):
> 
>  Object
>  |
>  |OutOfMemoryError
>  |AssertionError
>  |   |ArrayBoundsError
>  |   |AssertError
>  |   |SwitchError
>  |
>  |Error
>      |CheckedError    // should be catched
>      |   |FileError
>      |   ...
>      |
>      |UncheckedError  // safe to catch
>      |   |FormatError
>      |   ...
>      |
>      |UnrecoverableError  // unsafe to catch
>          |TooManyThreadsError
>          ...
> 
> I don't think, that it is too weird, as the only exceptions that are at the top level are truely special: They all belong to D's minimal RTL. These exceptions are directly mentioned in the D spec. For ArrayBoundsError, AssertError and SwitchError the constructor is even private, so only the compiler can create them.
> 
> Also, this design has the advantage that it avoids any implication whether  OutOfMemoryErrors and contract-violations are unrecoverable or not.

I like having the system-generated exceptions at the top-level rather than derived from Error.  Doing such allows them to be caught but it has to be done explicitly.  This prevents unintentional recovery from an out of memory condition by catching Error.  I also like the basic idea of checked and unchecked errors, but I'm still not sure I like building the error heirarchy around them.

> So, It doesn't work!
> 
> My conclusion is that the exception could be laid out in (at least) two ways:
> either     * build up a deep class hierarchy in a "natural and object-oriented" way
> or
>     * build up a rather shallow hierarchy around the notion of CheckedError, UncheckErrors and UnrecoverableErrors as virtually the only decision criteria
> 
> Also we could simply forget about un/recoverable, un/checked and severities levels and just go with a simpler hierarchy like. 
> 
> Object
>  |
>  |OutOfMemoryError
>  |AssertionError
>  |   |ArrayBoundsError
>  |   |AssertError
>  |   |SwitchError
>  |
>  |Error
>      |PhobosError
>           |...
> 
> Probably, add a standard exception UnrecoverableError, if really needed.

I'm a bit more inclined to go this route, though I can't say why exactly.  Maybe it's just my pragmatic nature assuming that programmers won't inherit from the proper exception with the other inheritance design.


Sean
September 20, 2004
Here's an interesting proposal of an refactored exception hierarchy for Java(TM):

"Messy Exception Hierarchy" http://c2.com/cgi/wiki?MessyExceptionHierarchy



I found it when I googled a bit to widen my view about exceptions. More links follow:

*) "Standard Exception Classes in Python 1.5" http://www.sourcekeg.co.uk/www.python.org/doc/essays/stdexceptions.html

<quote>
We looked into introducing more groups of related exceptions, but couldn't
decide on the best grouping. In a language as dynamic as Python, it's hard to
say whether TypeError is a "program error", a "runtime error" or an
"environmental error", so we decided to leave it undecided. It could be
argued that NameError and AttributeError should be derived from LookupError,
but this is questionable and depends entirely on the application.
</quote>

Comment:
   * 3 classes of errors:  program errors, runtime errors and environmental
errors
   * deciding about an exception hierarchy is very difficult:
     both, grouping according to the 3 classes of errors and grouping by
specialization was desired, but not wasn't manageable.



*) "Best Practices for Exception Handling" http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html

Definitely, a good article about Exceptions for Java developers; though not everyone agrees with some of the recommended practices.


*) "Beware the dangers of generic Exceptions" http://www.javaworld.com/javaworld/jw-10-2003/jw-1003-generics.html

An article that shows how dangerous (top-level?) generic Exceptions can be.



*) "Exceptional Java by Alan Griffiths" http://www.octopull.demon.co.uk/java/ExceptionalJava.html

Report about difficulties that arised with exceptions in one real word project. The report also presents *one* solution for the encountered problems.



*) proto pattern "Generalize On Exception Behavior" http://www.c2.com/cgi/wiki?GeneralizeOnExceptionBehavior

A good wiki page about the advantages and disadvantages of generalized exceptions. This page is informative and contains lots of sample code, but it also requires a lot of reading.


My insight about (Java) exceptions is, that in practise, Java programmers use exceptions very differently. This is somewhat surprising, since Java progammers usually share a very uniform way of dealing with problems. Indeed recommendations of actual Java programmers often contradict each other.




September 20, 2004
Farmer <itsFarmer.@freenet.de> wrote in news:Xns8FB1F10BEFD73itsFarmer@63.105.9.61:

> "Ben Hinkle" <bhinkle@mathworks.com> wrote in news:cia7sd$1n6u$1@digitaldaemon.com:
> 
>> Also I'd like to toss around some more names (Checked and Unchecked
>> don't mean too much to me).
> 
> They don't mean anything to me, either. I thought about other names, but
> always came up with names that I feel are too specific and provoke wrong
> ad- hoc decisions on the part of the programmer.
> As an mnemonic aid I think of "Checked/Unchecked" as "I should check the
> exception" and "I should leave it unchecked".
> 

Just some alternative names for "checked/unchecked", that I made up myself or came across in articles, forums or wiki pages.

unchecked exception:
--------------------
ProgrammingError
UnexpectedError
UnpredictableError
UnrecoverableError
AvoidableError
LogicalError

checked exception:
------------------
GeneralError
ExternalError
ExpectedError
PredictableError
RecoverableError
UnavoidableError
EnvironmentError
EnvironmentalError


My favorites are ProgrammingError and EnvironmentError or GeneralError.