February 19, 2012
On Sunday, February 19, 2012 01:43:27 Ben Davis wrote:
> I can assure you they get misused in Java too. Most people write this:

Oh yes. It's not like the fact that their exception hierarchy is good means that people don't misuse it. Far too many people try and ignore errors in any language. And a solid exception hierarchy does nothing to stop people from being stupid and simply catching Exception to ignore. But if you have a good exception hierarchy, then you _can_ properly handle errors if you want to.

My point was that because C++ doesn't have a standard exception hierarchy (even worse, they let you throw literally anything), it's much rarer to see one in C++ programs, let alone a good one. So, it's something that many C++ programmers have not been exposed to. Java programmers do not have that excuse. So, it's arguably that much worse when they misuse exceptions.

> - distinguish between 'bug' exceptions (e.g. null) and 'you're more likely to want to catch this' exceptions (e.g. IO). Maybe the bug ones should have been Errors, since people *usually* don't catch those (and are certainly never required to). As it stands, Errors are reserved for corrupt class files and other VM panic scenarios.

Well, in D, you'd use exceptions derived from Error (though null pointers still result in a good ol' segfault). Java's situation is marred by the whole thing with checked exceptions. D's basic is better in that regard, I think. It's the lack of a proper exception hierarchy where we're worse.

> - make sure the 'lazy' approach is a good one: the less you type, the fewer exceptions you catch, and the less likely you are to silence those exceptions at runtime. Probably the main problem with the exception hierarchy is that the short names tend to be the more general ones that are more dangerous to catch indiscriminately.

You mean, make the standard exception names really long and ugly, and the more specific ones short? e.g. Exception becomes something like StandardExceptionThatYouReallyShouldBeCatchingCatchTheDerivedType whereas specific exceptions are more like SpecifcEx (though that's a bit extreme)?

Well, while that's a good sentiment, the very nature of more specific exceptions means that their namesj are likely to be more specific and therefore longer (especially when you have descriptive names). So, I'm not sure that that's ultimately all that reasonable, even if the basic idea is a good one.

- Jonathan M Davis
February 19, 2012
On Sunday, 19 February 2012 at 01:29:40 UTC, Nick Sabalausky wrote:
> Another one for the file of "Crazy shit Andrei says" ;)
>
> From experience, I (and clearly many others here) find a sparse, flat exception hierarchy to be problematic and limiting. But even with a rich detailed exception hierarchy, those (ie, Andrei) who want to limit themselves to catching course-grained exceptions can do so, thanks to the nature of subtyping. So why are we even discussing this?

How about we revisit ancient design decisions which once held true... but no longer is the case due to our "god-language" being more expressive?

In my experience an "exception hierarchy" is never good enough, it suffers from the same problems as most frameworks also do... they simplify/destroy too much info of from the original error.

ex why don't we throw a closure? Of course we could go crazy with mixins and CTFE,CTTI,RTTI aswell... imho the goal should not be to do as good as java, the goal is progress! Java should copy our design if anything... we could have a very rich exception structure... without the need for a hierarchy.

try(name) // try extended to support full closure syntax
{
  DIR* dir = opendir(toStringz(name));

  if(dir==0 && errno==ENOTDIR)
    throw; // throws the entire try block as a closure
}

February 19, 2012
On Sat, Feb 18, 2012 at 05:47:58PM -0600, Andrei Alexandrescu wrote:
> On 2/18/12 1:41 PM, H. S. Teoh wrote:
[...]
> >It's only useless because of a poorly-designed exception hierarchy. Java, for example, has useful things like FileNotFoundException, DiskFullException, etc., that you can catch to handle specific problems in a specific way. They are also part of a well-designed hierarchy. For example, both of the above exceptions are subsumed under IOException, so if you wanted to handle a general I/O exception and don't care which one it is, you could just catch IOException.
> 
> It's great that you bring expertise from the Java world.

You flatter me, but I should make it clear that I'm no Java expert. I haven't used it in a significant way for years. But I do know enough about it to know, at least in principle, the merits of its exception class hierarchy.


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

I wanted to talk about the principles of using an exception hierarchy, not get lost in the details of it, that's why I didn't go into the details of how exactly the hierarchy should be designed.

As for this particular case, I was only trying to illustrate the point with a concrete example; I wasn't suggesting that we must implement those exact three exception classes. Nevertheless, I would say the decision to make FileNotFoundException and DiskFullException inherit from IOException can be based on:

1) The fact that they are based on a common set of OS primitives, i.e., the filesystem, thereby providing a logical set of related exceptions;

2) It makes sense to put errno in IOException, whereas it doesn't make sense to put errno in Exception, since not all Exception's have any relation to errno.

3) From a more abstract POV, it makes sense to group file-related exceptions under a common root, command-line parsing exceptions under another root, numerical exceptions under yet another root, etc.. And how do we decide which exception belongs where? By checking whether an application might want to catch all exceptions in a group as a whole, e.g., a numerical app wants to handle any numerical errors, but don't care about I/O errors (which should just propagate). So it wouldn't make sense to put, say, ArithmeticOverflowException under IOException.


> >Now, granted, there are limitations to such a design, for example if you want to catch a category of exceptions that don't map precisely to a node in the inheritance hierarchy. It may be possible to deal with such situations by making Exception an interface instead, although that may introduce other issues.
>
> From this and other posts I'd say we need to design the base exception classes better, for example by defining an overridable property isTransient that tells caller code whether retrying might help.

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.

The bottomline is, you can't expect a couple of properties in the base class (most generic level) will be helpful in all cases. In some cases, you *want* to know exactly what the exception was programmatically, so that the program knows how to react properly.


[...]
> >The problem with this approach is the proliferation of of exception classes, many of which differ only in fine ways that most applications wouldn't even care about. The basic problem here is that we are mapping what amounts to error codes to classes.
> 
> Yes. Types, to be more general. The question is why are various error deserving of their own types.

I have to say, I'm not wedded to the idea of Exception class hierarchies, but currently it's the best choice on the table.

Basically you *need* some kind of polymorphic object to represent an exception, because errors come in all shapes and forms, and it's simply not good enough to have what amounts to a binary system (fatal uncatchable exception and non-fatal exception that you may catch and attempt to continue if you dare). Whether it's a class, a template, or whatever, doesn't really matter in the end, but you do want to have:

1) Genericity: if I don't care what error it is, just that an error happened, I should be able to catch exceptions in general and deal with them.

2) Specificity: if I have a very precise error (or a very precise set of errors) that I can handle, I want to be able to catch those, and only those, errors, and deal with them. The ones I can't handle, I want to just propagate.

3) Programmatic information: the fact that I want to handle a certain set of errors means that I know how to deal with them, provided I have enough information about them. Which means the exceptions I catch need to come with domain-specific information (such as errno for OS-related exceptions, or Oracle error numbers for the Oracle API library, etc.). Having toString() as the only way to extract information out of the exception is not good enough for recovering from errors programmatically. (And obviously sticking errno, Oracle errors, etc., into a giant union in Exception isn't the right solution either.)

Currently, an elaborated exception class hierarchy, for all its warts and the objections against it, best fits the bill.  If you have a better way of doing this, I'm all ears. But the current "fatal"/"nonfatal" dichotomy is simply too coarse to be of use in large applications.


T

-- 
Food and laptops don't mix.
February 19, 2012
On Sat, 18 Feb 2012 19:13:02 -0600, Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Saturday, February 18, 2012 19:01:31 Robert Jacques wrote:
>> That an argument of an internationalization module as part of the standard
>> library, not for or against a particular exception module. I don't know
>> what a good/robust module would look like, but many open source projects
>> simply use a global string[string] and it seems to work okay for them.
>
> It's an argument against making it so that the only way to get the error
> information from an exception is toString.
>
> Exception handling is far more flexible when the exception's type and member
> variables gives you the information you need to actually handle the exception
> - or in the case of internationalization, generate your own error message. But
> ideally, users wouldn't see the result of an exception's toString regardless
> (especially since that includes stuff like the stack trace). Not even the msg
> field is really acceptable for that IMHO. It's great for debugging and maybe
> log messages, but not dealing with the user. To deal with the user
> appropriately, you need to know _what_ went wrong in a way that your program
> can process it and react appropriately, which strings just don't do.
>
> - Jonathan M Davis

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 and not supposed to be used as a form of out of band information return (or at least that's what every programming book tells me). 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. 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.
February 19, 2012
On 19.02.2012 02:17, H. S. Teoh wrote:
>
> The basic problem with C++ exceptions is that it allows you to throw
> literally *anything*. Which is nice and generous, but not having a
> common root to all exceptions cripples the system, because there is no
> functionality that can be depended upon, like toString when what you
> want is an error message.

No, that is a misunderstanding (completely).

However, C++ exceptions do have many problems, including:

  * Until C++11, no support for propagating exceptions through
    non-exception-aware code (in particular, from C callbacks).

  * No support for wchar_t exception text, i.e. *nix-specific.

  * A nonsensical exception class hierarchy with e.g.
    std::logic_error and std::bad_exception.

  * No differentiation between recoverable (soft, failure) and
    unrecoverable (hard, error) exceptions, although some people have
    argued that in the latter case one is screwed anyway.

  * Involving std::string arguments, so that in low memory
    conditions throwing an exception can itself throw.

Especially the last point is pretty silly. :-)

And then when C++11 added support for propagating error codes, it was designed as *nix-specific (char-based messages only) and overly complex.

I haven't heard about anyone actually using that stuff.


> Furthermore, not having a properly designed skeletal hierarchy to
> inherit from also adds to the problem. With no existing standard
> hierarchy to serve as a reference point, library writers just invent
> their own systems, most of which don't interoperate properly with each
> other. And thus the mess that is the C++ exception hierarchy.
>
> I certainly hope D can do better than that.

Agreed.


Cheers,

- Alf
February 19, 2012
On 2/19/12, Ben Davis <entheh@cantab.net> wrote:
> That would then make it an entirely separate issue and completely not Exception-specific.

Yes I meant in the general case not just Exception. Exception is just a regular class.
February 19, 2012
On 02/19/2012 02:23 AM, Jonathan M Davis wrote:
> One potential issue though is that not all base classes necessarily share the
> same constructors. Which ones would be grabbed? The ones from the immediate
> base class? All of them? Only the ones common to all?
>

There is only one base class. Inheriting constructors from anywhere further up the hierarchy cannot work, because then the base class cannot be constructed.
February 19, 2012
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.

> An unexpected error occurred while
> trying to do X, where X is whatever is inside the try-catch block.
> Exceptions are for exceptional situations and not supposed to be used as a
> form of out of band information return (or at least that's what every
> programming book tells me).

Exceptions are for handling when something goes wrong in your program - frequently from interacting with users or I/O. It's much cleaner to write code which assumes that opening a file and reading for it will work than to check every operation to see whether it succeeded or not. Then you can just set up a try-catch block around it, and handle the error if it occurs. Whether the exception is "unexpected" is debatable. It _is_ expected in the sense that if something goes wrong with opening and reading the file, you're going to get an exception, and that's not a bug in your program, but it's not expected to happen in the common case. It's still expected to happen at least some of the time though. It's not like "something went wrong and we don't know what to do." If that's the case, you're moving towards Error territory, though that depends on if you can recover from a failed operation even if you have no clue what went wrong.

When you avoid using exceptions is when there's a high chance that the operation will fail. Then you have the function return whether it succeeded or not, and exceptions would just slow the program down (I believe that a number of socket operations fall in this category for instance). But if the normal case is that it will work but occasionally it won't, an exception is the better way to go, since it leads to cleaner code.

What's truly horrible is when you throw exceptions for normal situations (an extreme example being that you throw when an operation succeeds).  _That_ is completely misusing exceptions, but most people don't do that. Exceptions are for error handling. If anything, I think that talk about "exceptional" cases leads people to completely misuse exceptions, because they often end up using them only for cases where Error should be used - that is unrecoverable situations.

Personally, I'm against the use of error codes and returning whether an operation succeeded instead of using exceptions in the general case and that those should only be used when failure is _likely_. As long as it's at all reasonable to assume that an operation will succeed, using an exception to handle when it doesn't is far better than using the return value to indicate failure.

- Jonathan M Davis
February 19, 2012
On 2/18/12 6:28 PM, Jonathan M Davis wrote:
> On Saturday, February 18, 2012 17:53:52 Andrei Alexandrescu wrote:
>> On 2/18/12 5:47 PM, Jakob Ovrum wrote:
>>> you are basically arguing against exceptions here
>>
>> I must have argued my question and point very poorly.
>
> You definitely seem to be arguing for a single Exception type

No.

Andrei
February 19, 2012
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.

Andrei