- Is catching generic exceptions that wrong an approach? Had a lot of cases (been burnt trying to catch specific ones) where we did not know
what to anticipate, while the behavior would be the same for all, e.g.
log and continue with execution.
No. "Wrong" is a matter of context. It is wrong to catch generic exceptions when your code flow and subsequent decisions depend on the type, but when you simply don't care, there is no cleaner way.
Consider an application that reads a couple of file formats. When the user tries to open a file and every single parser you have fails to recognize the format (based on whatever, built-in heuristics, expected fields, signature bytes, etc.), you don't need to explain to the user what happened. Hundreds of file formats are out there, plus the file may be on a USB drive that just got disconnected, a file was locked by another application, dozens of things can go wrong. You can just catch everything and report that the file could not be opened, the format might not be recognizable or a problem occurred with the file system. Yes, you will annoy the user by not explaining everything but time is money and you cannot just spend it fine-tuning a response to every single thing that can go wrong. Besides, the users will be equally amused when they do manage to open their files after all, and do some important time-and-money-saving processing on their precious data.
- Wrapping everything in try..except: log blocks is code repetition. Or is it? Solving this in any way that would not seem like
over-engineering is beyond me.
Only wrap something in a
try block when the stack trace is going to be useful to you. Unwrapping the stack is costly and should be avoided in time-sensitive blocks, obviously. Consider a method that draws a 3-point circle on some canvas:
void drawCircle(Point p1, Point p2, Point p3)`
What use could the stack trace possibly be of here? You know in advance almost anything that could go wrong. Invalid
Point instances? Impossible, you catch them in their constructor anyway. Besides, the user is probably entering coordinates in textboxes where you gracefully forbid alphanumeric or invalid values. Or they create points by clicking on your Canvas, which means they cannot click on a point with invalid coordinates. Three points may be collinear, so the circle radius tends to infinity. Check for that in advance and inform the user appropriately (or draw an infinite straight line). Two (or all 3) points may be coincident. Check for that in advance and let the user know this cannot be done.
The point is that there is hardly anything that the stack trace in such a method would tell you, that you cannot predict in advance (unless your software is running on computers exposed to serious amounts of cosmic radiation).
- Handling nested try blocks could maybe be solved by separating them to their own scope (e.g. a function), however this hasn't proved
itself as a reliable solution as oftentimes the caller might be
desiring a different result on exception (empty result, alternative
result, the exception itself etc...)
Every time a meaningful exception gets caught, you get the logs, see what happened and refactor your software in a way that what happened is no longer an exception. If you don't, then the logging part of the
catch/except block is practically only for show. After you manage to handle 99% of what can go wrong, you won't really care anymore about the rest 1%, and you don't really care much about the
except/catch block at all, you just leave it be, for the 1% of the stuff that can go wrong. Trying to "scope" specific exception cases smells like you are trying to use exceptions to control code execution flow.
A caller that "might be desiring a different result on exception", sounds like one that knows what to expect or want. Find a way to write the method(s), so that they return what is desired/expected then, why would you need exceptions? An exception is, by definition, an unexpected event that disrupts flow. If you know what to expect and what to do when you observe some sort of behavior, it's not an exception anymore. When you are calling code that you cannot change, neither know what exactly it does, documentation is your only hope and, of course,
trying and catching exceptions would be advisable, but in those cases, you are simply doing code execution flow control, nothing is truly unexpected when you know what to expect (and, of course, do prepare for all that you can expect).
Want a typical example of when a
try block would be absolutely necessary? Well, when you "try" to open a file, of course. Check this out:
content = read(filepath);
No matter how many times you check whether
read(filepath) is on a different line of code and, therefore, semantically happens at a different time. Yes, the file may have disappeared in between. Will it, though? Well, in reality, it might disappear by any one of a series of very obscure reasons… the user may be moving a bunch of files and it happens to coincide with this check, or the filename was only just changed, or this is a remote filesystem and each request takes much time to be served, thus allowing for more time in-between, for obscure things to happen, etc.
99.9999% (or more) of the time, this won't be a problem, of course. When it happens, though, it will truly be an
exception and, depending on the circumstances, the application may crash. For precisely that reason, you definitely need to wrap this around a try-except block. In a purely exceptional situation, such as this, you will find it impossible to know what on earth has happened, you will not really care about it anyway (you can get all the log details in the world but you will not be able to do anything to prevent the error from occurring out of very bad luck in another 10 years from now) and you will have absolutely no way of recovering whatever was going on in the
try part of the block. This is the only meaningful situation that try-catch blocks are going to help, i.e. exogenous exceptions:
Always handle exceptions that indicate unexpected exogenous
conditions; generally it is not worthwhile or practical to anticipate
every possible failure. Just try the operation and be prepared to
handle the exception.