4 ms·
I'm so with you. Exception handling is an awesome tool. I love the way C# offers you different constructs like try/catch/finally, exception pattern matching or
by zubspace 7y ago
I'm so with you. Exception handling is an awesome tool. I love the way C# offers you different constructs like try/catch/finally, exception pattern matching or usings, which automatically clean up objects.
While writing services I often need to centrally log an error and retry the process later on. Exception handling really reduces the mental load regarding error handling in this case.
On a semantic level, there are forseeable errors you need to check locally (for example wrong user input) or exceptional errors which can happen at any time (for example file access errors or out of memory scenarios). Exceptions are a perfect match for the second type.
I don't really understand since when or why exceptions are uncool. It hurts my eyes looking at all that error checking code in other languages.
- zahllos 7y agoThere's been a bit of a backlash against exceptions in new languages (and attempts to "ban" them in old) and I actually kind of get it, especially having been a C++ developer. You'll see stuff like the Google C++ standard saying "don't use exceptions" but then if you want to apply RAII (and you really do), you have a constructor and the only way to indicate an error is via exceptions. Also, how about this wonderful syntax: class OrangeSite { Someobject m_object; public: OrangeSite(Someobject o) : m_object(o) { try { // construct object } catch (std::exception& e) { // exceptions for anything in the body above } } catch (std::exception& e) { // exception handler for anything that failed in the initializer lists } }; I mean, kill it with fire. That is the only way to catch exceptions that occur in initializer lists and yes you have multiple catches. C# et al haven't reinvented all the same mistakes, but still, there's still two ways to control flow: return values and exceptions. What is really needed is the ability to do the same sort of filtering on error types. You could actually do it in golang with a type assertion, but it would require implementing a type that implements the error interface and type assertions. It's ugly. Regarding your errors/exceptions, I've heard this before and I agree with it. There are error conditions, from which the program can and is expected to recover, and exception conditions, from which the program cannot recover. Out of memory is a perfect exception condition. However, this semantic distinction is entirely lost - a lot of error conditions are reported as exceptions. In my above example you have absolutely no choice in the matter; having no error type, languages like Java/C# etc heavily encourage, even if they don't mean to, return by exception. The golang and rust mapping for these concepts is panic and... panic! respectively. You can catch and recover from panics if need be in golang; in Rust panic handling is bounded by thread and if a panic occurs on the main thread, the runtime triggers an exit. As far as I'm aware, the only way to recover from a panic is like this: fn unrecoverable_error() -> thread::Result<()> { return panic::catch_unwind(|| { panicking_function() }); } with panic="unwind" set in cargo.toml. If the thread::Result seems bizarre. it's because panic::catch_unwind basically spins up a thread to isolate that panic. Thus panics in Rust really are for things that should absolutely crash the program there and then. Everything else is a result type and you can shove any type you like into the error field. Golang is a bit more forgiving in that panics can be caught, but the language still encourages the use of errors rather than panics. At the end of the day, at some point there's going to need to be a translation between some of the internal errors and what the end user sees and this process is going to end up in some boilerplate, be it lots of classes implementing some exception superclass, type or interface, or a bunch of if statements to handle exceptions. I don't think this is avoidable and one line magic code makes me uneasy - I have some experience now managing developers who like magic and want to avoid, at all, costs, translating errors to have friendly user output. Lastly, there are workaround to this sort of boilerplate. Here's a simplified snippet I use to write this stuff whenever I write go: snippet errif "Call function then check error" w ${1:var}, err ${2::}= ${3:func}(${4:args}) if err != nil { fmt.Println("$5 %v", err) return err } $0 endsnippet It's 2019, so, most editors support this kind of thing. I'm using vim, but VS Code supports it too if you feel the need for a whole browser rendering engine just to edit some text ;) the placeholders allow me to fill in the template. Every time I find myself writing boilerpolate code, I write one of these and automate the task as much as possible. vim-go has a bunch of them: https://github.com/fatih/vim-go/blob/master/gosnippets/UltiSnips/go.snippets https://github.com/fatih/vim-go/blob/master/gosnippets/UltiS... and I'm not sure how I've programmed all these years without this kind of thing. I have snippets for everything.
- zubspace 7y agoYeah C++ function try blocks are really ugly. I guess just too many people used and misused exceptions in C++ or Java. Moreover there was a time where exceptions had a bad reputation because of performance. Working with different kinds of people, beginners and seniors, I learned that everyone has their own way of thinking about errors. It's hard or even impossible to get a consistent code style. Code formatters like go-fmt surely help. But do you trust everone to handle each error case appropriately? What I like about exceptions is, that our software handles each and every possible error now and in the future consistently in a fail-fast way with an accurate stacktrace for free. I can sleep well, because I can be sure our main loop won't crash. Everyone can write plugins without me moving a finger. It frees us to think about other things first and errors second.