I've been writing code for 15 years (in Java, C++ and more recently JavaScript and Go). I have finally given up on exceptions and settled on return values (I particularly like Go's system). Exceptions, while theoretically superior, simply tempts even good programmers to just kick errors down the callstack. I prefer guard clauses [1].
I like the functional approach with Try, basically the exception becomes part of the return type, and the code chooses to either return an exception or the actual value.
fun doSomething(arg: X): Try<Y>
Since the return signature needs adjusting, this leads to developers very consciously making the choice to either handle the error in the function, therefore avoiding adjusting the return type, or let the caller deal with it if it isn't logical to handle the error there.
Is this the same as `Result<Y>` (where `Result` is a sum type that contains either a value of `Y` or an "Error")? I haven't see it called `Try` before.
Why wouldn’t you want errors kicked down the call stack? There are only two types of exceptions in the grand scheme of things.
1. Things go wrong that are out of your control - network down, database down, etc.
2. Coding mistakes. Either in your code or input arguments.
In either case, why not let the end user decide how to handle the error? Sometimes it’s some type of retry pattern, others it just to have a big try catch block that logs the fatal error and alerts someone.
I’m not really in love with that either. The equivalent paradigm in C# is using the “Inner Exception” what value did you add by wrapping the exception? The stack trace already has the line number and the method that caused the error all the way down the stack. In Python, the code is right there. You can just open up the Python file in a text editor and see everything.
> what value did you add by wrapping the exception?
By wrapping the exception, the user of “thismodule” can simply call it by writing
try:
thismodule.do_thing()
except thismodule.ThisModulesException:
logging.exception("Failed to do thing")
othermodule.do_other_thing_instead()
And this user of “thismodule” is free from having to know that thismodule calls dangerous_operation() and/or raises DangerousException (which are probably both from a different module). This information will be shown automatically in the exception’s backtrace, including all line numbers of all wrapped exceptions, so it is not lost. But the code which uses the module is both shorter, simpler, and has less knowledge about internals of the module it is using.
If a low-level module has an unexpected ValueError or ZeroDivisionError, I don’t want that to be inadvertently caught and hidden by my except clause. In general, I want truly unexpected errors to propagate to the top level and generate a proper crash.
Only in the case of code where I really need to do something if a specific operation fails, for whatever reason, do I use “except Exception:” or its even more catch-all variant, the bare “except:” clause. And even then, I very often just use it to log a message or send an e-mail, and re-raise the exception again afterwards.
[1] http://wiki.c2.com/?GuardClause