To throw or not to throw? Non-exceptional error conditions
Posted at 08:00 on 03 May 2018
I've said before that the general advice that "you should only throw exceptions in response to exceptional conditions" is bad advice, because it is ambiguous and unclear as to what exactly constitutes an exceptional condition. But nevertheless, there is a valid point behind it.
The point is that exceptions are (a) slow, and (b) painful to step through in the debugger.
This means that if there are error conditions that crop up frequently, you will want to handle them without an exception being thrown wherever possible.
The classic example here is, of course, attempting to parse an integer. This is why .NET gives you the Int32.TryParse
method. If it is provided with an invalid input string, it returns false instead of throwing an exception.
Now there are two important points to note about Int32.TryParse
.
First: it returns false only in response to a single, specific error condition: namely, bad user input. Any other failure mode should still throw an exception. This is the case with the overload of Int32.TryParse
that also takes some localisation and formatting parameters, for example.
Second: its name tells us that it returns an error code in response to this specific condition. If it didn't have that Try...
prefix, it would, as Yoda said, be a case of "Do or do not. There is no try." This means that the meaning of exceptions -- that the method can not do what its name says that it does -- is preserved.
Third: it is provided alongside an equivalent method, Int32.Parse
, that throws an exception as normal. This gives consumers of your API a choice about which method to use.
Fourth, and most importantly, it is not implemented by throwing and re-catching exceptions. If you write a method like this, you are doing it wrong:
public bool TryParse(string input, out int output) { try { output = Int32.Parse(input); return true; } catch { return false; } }
Rather, you should do it the other way round:
public int Parse(string input) { if (input == null) { throw new ArgumentNullException("input"); } int result; if (Int32.TryParse(input, out result)) { return result; } else { throw new FormatException("The parameter 'input' was not in the correct format."); } }
Why should this be? Remember that exceptions are expensive both in terms of execution time and stepping through them in the debugger. If you are implementing Try...
methods by wrapping other methods in a try ... catch
block, you will be losing the benefits of having a Try...
method in the first place.
Recommendation: Use this pattern whenever you are taking user input that may or may not be valid. But do not use this pattern for error conditions that are not a direct result of invalid user input.