I can check for the input and if it's an invalid input from the user, I can use a simple "if condition" which prints "input invalid, please re-enter" (in case there is an invalid input).
This approach of "if there is a potential for a failure, verify it using an if condition and then specify the right behavior when failure is encountered..." seems enough for me.
If I can basically cover any kind of failure (divide by zero, etc.) with this approach, why do I need this whole exception handling mechanism (exception class and objects, checked and unchecked, etc.)?
Suppose you have func1
calling func2
with some input.
Now, suppose func2
fails for some reason.
Your suggestion is to handle the failure within func2
, and then return to func1
.
How will func1
"know" what error (if any) has occurred in func2
and how to proceed from that point?
The first solution that comes to mind is an error-code that func2
will return, where typically, a zero value will represent "OK", and each of the other (non-zero) values will represent a specific error that has occurred.
The problem with this mechanism is that it limits your flexibility in adding / handling new error-codes.
With the exception mechanism, you have a generic Exception
object, which can be extended to any specific type of exception. In a way, it is similar to an error-code, but it can contain more information (for example, an error-message string).
You can still argue of course, "well, what's the try/catch
for then? why not simply return this object?".
Fortunately, this question has already been answered here in great detail:
In C++ what are the benefits of using exceptions and try / catch instead of just returning an error code?
In general, there are two main advantages for exceptions over error-codes, both of which are different aspects of correct coding:
With an exception, the programmer must either handle it or throw it "upwards", whereas with an error-code, the programmer can mistakenly ignore it.
With the exception mechanism you can write your code much "cleaner" and have everything "automatically handled", wheres with error-codes you are obliged to implement a "tedious" switch/case
, possibly in every function "up the call-stack".
Exceptions are a more object-oriented approach to handling exceptional execution flows than return codes. The drawback of return codes is that you have to come up with 'special' values to indicate different types of exceptional results, for example:
public double calculatePercentage(int a, int b) {
if (b == 0) {
return -1;
}
else {
return 100.0 * (a / b);
}
}
The above method uses a return code of -1 to indicate failure (because it cannot divide by zero). This would work, but your calling code needs to know about this convention, for example this could happen:
public double addPercentages(int a, int b, int c, int d) {
double percentage1 = calculatePercentage(a, b);
double percentage2 = calculatePercentage(c, c);
return percentage1 + percentage2;
}
Above code looks fine at first glance. But when b or d are zero the result will be unexpected. calculatePercentage will return -1 and add it to the other percentage which is likely not correct. The programmer who wrote addPercentages is unaware that there is a bug in this code until he tests it, and even then only if he really checks the validity of the results.
With exceptions you could do this:
public double calculatePercentage(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Second argument cannot be zero");
}
else {
return 100.0 * (a / b);
}
}
Code calling this method will compile without exception handling, but it will stop when run with incorrect values. This is often the preferred way since it leaves it up to the programmer if and where to handle exceptions.
If you want to force the programmer to handle this exception you should use a checked exception, for example:
public double calculatePercentage(int a, int b) throws MyCheckedCalculationException {
if (b == 0) {
throw new MyCheckedCalculationException("Second argument cannot be zero");
}
else {
return 100.0 * (a / b);
}
}
Notice that calculatePercentage has to declare the exception in its method signature. Checked exceptions have to be declared like that, and the calling code either has to catch them or declare them in their own method signature.
I think many Java developers currently agree that checked exceptions are bit invasive so most API's lately gravitate towards the use of unchecked exceptions.
The checked exception above could be defined like this:
public class MyCheckedCalculationException extends Exception {
public MyCalculationException(String message) {
super(message);
}
}
Creating a custom exception type like that makes sense if you are developing a component with multiple classes and methods which are used by several other components and you want to make your API (including exception handling) very clear.
(see the Throwable class hierarchy)
Let's assume that you need to write some code for some object, which consists of n different resources (n > 3) to be allocated in the constructor and deallocated inside the destructor.
Let's even say, that some of these resources depend on each other.
E.g. in order to create an memory map of some file one would first have to successfully open the file and then perform the OS function for memory mapping.
Without exception handling you would not be able to use the constructor(s) to allocate these resources but you would likely use two-step-initialization.
You would have to take care about order of construction and destruction yourself
-- since you're not using the constructor anymore.
Without exception handling you would not be able to return rich error information to the caller -- this is why in exception free software one usually needs a debugger and debug executable to identify why some complex piece of software is suddenly failing.
This again assumes, that not every library is able to simply dump it's error information to stderr. stderr is in certain cases not available, which in turn makes all code which is using stderr for error reporting not useable.
Using C++ Exception Handling you would simply chain the classes wrapping the matching system calls into base or member class relationships AND the compiler would take care about order of construction and destruction and to only call destructors for not failed constructors.