I would strongly advise against #1, because just ignoring errors is a dangerous anti-pattern. It can lead to hard-to-analyze bugs. Setting the result of a division by zero to 0 makes no sense whatsoever, and continuing program execution with a nonsensical value is going to cause trouble. Especially when the program is running unattended. When the program interpreter notices that there is an error in the program (and a division-by-zero is almost always a design error), aborting it and keeping everything as-is is usually preferred over filling your database with garbage.
Also, you will unlikely be successful with thoroughly following this pattern through. Sooner or later you will run into error situations which just can't be ignored (like running out of memory or a stack overflow) and you will have to implement a way to terminate the program anyway.
Option #2 (using NaN) would be a bit of work, but not as much as you might think. How to handle NaN in different calculations is well-documented in the IEEE 754 standard, so you can likely just do what the language your interpreter is written in does.
By the way: Creating a programming language usable by non-programmers is something we've been trying to do since 1964 (Dartmouth BASIC). So far, we've been unsuccessful. But good luck anyway.
First, I feel obliged to point out that std::exception
and its children were designed a long time ago. There are a number of parts that would probably (almost certainly) be different if they were being designed today.
Don't get me wrong: there are parts of the design that have worked out pretty well, and are pretty good examples of how to design an exception hierarchy for C++ (e.g., the fact that, unlike most other classes, they all share a common root).
Looking specifically at logic_error
, we have a bit of a conundrum. On one hand, if you have any reasonable choice in the matter, the advice you quoted is right: it's generally best to fail as fast and noisily as possible so it can be debugged and corrected.
For better or worse, however, it's hard to define the standard library around what you should generally do. If it defined these to exit the program (e.g., calling abort()
) when given incorrect input, that would be what always happened for that circumstance--and there are actually quite a few circumstances under which this probably isn't really the right thing to do, at least in deployed code.
That would apply in code with (at least soft) real-time requirements, and minimal penalty for an incorrect output. For example, consider a chat program. If it's decoding some voice data, and gets some incorrect input, chances are a user will be a lot happier to live with a millisecond of static in the output than a program that just shuts down completely. Likewise when doing video playback, it may be more acceptable to live with producing the wrong values for some pixels for a frame or two than have the program summarily exit because the input stream got corrupted.
As for whether to use exceptions to report certain types of errors: you're right--the same operation might qualify as an exception or not, depending on how it's being used.
On the other hand, you're also wrong--using the standard library doesn't (necessarily) force that decision on you. In the case of opening a file, you'd normally be using an iostream. Iostreams aren't exactly the latest and greatest design either, but in this case they get things right: they let you set an error mode, so you can control whether failing to open a file with result in an exception being thrown or not. So, if you have a file that's really necessary for your application, and failing to open it means you have to take some serious remedial action, then you can have it throw an exception if it fails to open that file. For most files, that you'll try to open, if they don't exist or aren't accessible, they'll just fail (this is the default).
As for how you decide: I don't think there is an easy answer. For better or worse, "exceptional circumstances" isn't always easy to measure. While there are certainly cases that are easy to decide must be [un]exceptional, there are (and probably always will be) cases where it's open to question, or requires knowledge of context that's outside the domain of the function at hand. For cases like that, it may at least be worth considering a design roughly similar to this part of iostreams, where the user can decide whether failure results in an exception being thrown or not. Alternatively, it's entirely possible to have two separate sets of functions (or classes, etc.), one of which will throw exceptions to indicate failure, the other of which uses other means. If you go that route, chances are pretty good that one should be a wrapper around the other, or both act as wrappers around a shared set of functions that implement the real guts of the work.
Best Answer
Exceptions were invented to help make error handling easier with less code clutter. You should use them in cases when they make error handling easier with less code clutter. This "exceptions only for exceptional circumstances" business stems from a time when exception handling was deemed an unacceptable performance hit. That's no longer the case in the vast majority of code, but people still spout the rule without remembering the reason behind it.
Especially in Java, which is maybe the most exception-loving language ever conceived, you shouldn't feel bad about using exceptions when it simplifies your code. In fact, Java's own
Integer
class doesn't have a means to check if a string is a valid integer without potentially throwing aNumberFormatException
.Also, although you can't rely just on UI validation, keep in mind if your UI is designed properly, such as using a spinner for entering short numerical values, then a non-numerical value making it into the back end truly would be an exceptional condition.