I was recently told that I'm abusing exceptions to control the flow in my applications, so I this is my attempt to somehow clarify the situation.
To my mind, a method should throw an exception, when it encounters a situation, which can't be handled internally or might be handled better by the calling side.
So - does any particular set of rules exist, which can be used to answer the following set of question when developing your applications:
When should I throw an exception and when should I write code with strong nothrow guarantee, which might simply return bool
to indicate success or failure?
Should I try to minimize the number of situations, when the method throws an exception or , on the contrary, should it be maximized to provide flexibility when handling these situations?
Should I stick to the exception throwing convention set by the frameworks / runtimes I use when developing my applications or should I wrap all these calls so that they match my own exception throwing strategy?
I was also adviced to use error codes for error handling, which seems pretty efficient, but ugly from the syntactical point of view (also, when using them a developer loses the ability to specify the output for a method). What do you think about this?
Example for the third question (I was using an I/O framework and encountered the following situation):
The described framework does not use exceptions to handle errors, but the other code does use them. Should I wrap every possible failure indicated with
'???'
and throw an exception in this case? Or should I change the signature of my method tobool PrepareTheResultingOutputPath
and only indicate whether the operation was successful or not?
public void PrepareTheResultingOutputFile(
String templateFilePath, String outputFilePath)
{
if (!File.Exists(templateFilePath))
// ???
if (!Directory.MakePath(outputFilePath))
// ???
if (File.Exists(outputFilePath))
if (!File.Remove(outputFilePath))
// ???
if (!File.Copy(templateFilePath, outputFilePath)
// ???
}
Another example - even the .NET Framework
doesn't follow some strict exception throwing strategy. Some methods are documented to throw 10+ different exception types, including trivial exception types like NullArgumentException
, but some of them simply return bool
to indicate success or failure of the operations.
Thank you!
One of these common bad practices is using exceptions as the control flow. This should be avoided for two reasons: It reduces the performance of your code as a response per unit time, and it makes your code less readable.
Flow reports applications errors by throwing specific exceptions. Exceptions are structured in a hierarchy which is based on base exception classes for each component. By default, PHP catchable errors, warnings and notices are automatically converted into exceptions in order to simplify the error handling.
Exceptions and control flow. When an exception occurs within a method, the control flow defined by Java can take several forms. If an exception occurs outside a try.. catch block, the exception simply propagates "up" to the caller.
Ideally, your code should not return errors, but in cases where it does or must, exceptions appear to be the simplest, most reliable way to implement an error return. In my experience, it is best to avoid exceptions whenever possible. They complicate your thinking, complicate the code, and make debugging a nightmare.
The problem with exceptions is that they are essentially glorified gotos that have the ability to unwind the program's call stack. So, if you are "using exceptions for flow control," you are probably using them as gotos rather than as indications of an exceptions condition. That's exactly the point of exceptions, and the reason for their name: they are supposed to be used only in exceptional cases. So, unless a method is designed not to throw an exception (an example is .NET's int.TryParse
), it's OK to throw an exception in response to exceptional circumstances.
The nice thing about C# as opposed to Java is that in C# you can essentially return two or more values, by returning a tuple type or by using out parameters. So, there isn't much ugliness in returning an error code as the method's main return value, since you can use out parameters for the rest. For example, the common paradigm for calling int.TryParse
is
string s = /* Read a string from somewhere */;
int n;
if (int.TryParse(s, out n))
{
// Use n somehow
}
else
{
// Tell the user that they entered a wrong number
}
Now for your third question, which seems to be the most substantial. In reference to your example code, you ask if you should return bool
to indicate success/failure or if you should use exceptions to indicate failure. There is a third option, though. You can define an enum to tell how the method could fail, and return a value of that type to the caller. Then, the caller has a wide choice: the caller doesn't have to use a bunch of try/catch statements, or an if that gives little insight into how the method failed, but can choose to write either
if (PrepareTheResultingOutputFile(templateFilePath, outputFilePath) == Status.Success)
// Do something
else
// It failed!
or
switch (PrepareTheResultingOutputFile(templateFilePath, outputFilePath))
{
case Status.Success:
// Do something
break;
case Status.FileNotPresent:
// Do something else
break;
case Status.CannotMakePath:
// Do something else
break;
// And so on
default:
// Some other reason for failure
break;
}
You can find more on this issue here and here, but especially in Joel Spolsky's post, which I highly recommend.
There's nothing inherently evil about exceptions. When properly used they can greatly ease error handling in your code. The problem with exceptions, particularly in Java, is that they are to easily abused and overused, leading to a variety of anti patterns.
As to your specific questions, I will provide my own opinion on each one.
When should I throw an exception and when should I write code with strong nothrow guarantee, which might simply return bool to indicate success or failure?
You cannot write a method in Java with a 'no throw' guarantee. At minimum the JVM can throw a runtime error at any time, say for example an OutOfMemoryError. It's not your responsibility to suppress these, just let them bubble up your call heirarchy till you reach the most appropriate location to handle them. Changing a method's return type to bool to indicate success or failure is actually an antithesis of good design, your methods return type should be dictated by their contract (what they are supposed to do), as opposed to how they did it.
Should I try to minimize the number of situations, when the method throws an exception or , on the contrary, should it be maximized to provide flexibility when handling these situations?
Neither! Your method should throw exactly the number of exceptions as expected, given its contract (i.e what its supposed to do). Here are some general rules:
PrepareTheResultingOutputFile
method is a valid point to thrown an exception on failure of the desired
result.Should I stick to the exception throwing convention set by the frameworks / runtimes I use when developing my applications or should I wrap all these calls so that they match my own exception throwing strategy?
If both the method and method caller are using the same framework then it is totally unnecessary to wrap the frameworks exceptions before rethrowing them. The converse is also true - if you are using a framework in your method that your caller does not know about, then you should hide that implementation detail by wrapping the exceptions the framework throws.
I was also adviced to use error codes for error handling, which seems pretty efficient, but ugly from the syntactical point of view (also, when using them a developer loses the ability to specify the output for a method). What do you think about this?
I have not seen a lot of successful error code frameworks in Java, and to be honest in most cases its total overkill. A greater argument could be made for internalization and localization of error messages.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With