4 ms·
Go's error handling is really easy to poke fun at. Is there a defense of littering your code with `if err != nil` branches?
by macintux 4y ago
Go's error handling is really easy to poke fun at. Is there a defense of littering your code with `if err != nil` branches?
- netr0ute 4y agoI don't know why "if err != nil" is such an issue, as C programs have been doing the exact same thing for decades and it works just fine.
- Smaug123 4y agoDunno about "just fine" - I rarely use Go programs, but I found a bug in the one I work with the most which turned out to be precisely a failure to write "if err != nil". It just feels kind of embarrassing when we… already know how to prevent these problems.
- OkayPhysicist 4y agoThat's kinda Go's whole schtick: Google couldn't be assed to hire developers who had the baseline knowledge of their field, so they needed a language devoid of any of the useful advancements in language design made over the last 40 years except garbage collection. Its focus is to be written by the grossly inexperienced, and anyone who doesn't fit that category will miss basic functionality from every other language they've ever used.
- anyfoo 4y agoIf we could add Monads to C to stop having to do that, we would. This would require so much revamping of the type system that it wouldn’t be C anymore, in a non-compatible way. I’ve been writing C for at least 25 years, I am extremely comfortable with it and know most of its nooks and crannies, and I do it daily as my job. God I wish it had Monads just enough to stop cluttering my code with error checking boilerplate. There are many things in computing and everywhere else that we have done for decades, before we got far along enough to be able to stop.
- not2b 4y agoIt doesn't work fine if the check is omitted, which is easy to do, and if an error is extremely rare the missing check won't be noticed until much later. For rare errors exceptions are an alternative, but something has to catch and handle the exception.
- Jtsummers 4y ago"works just fine"? Like having a single global `errno` that you need to clear manually? Put this in a file `main.c` (or change the line opening `main.c` to a file known to exist): #include <errno.h> #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; printf("%d\n", errno); fp = fopen("does-not-exist", "r"); printf("%d, %p\n", errno, fp); fp = fopen("main.c", "r"); printf("%d, %p\n", errno, fp); errno = 0; fp = fopen("main.c", "r"); printf("%d, %p\n", errno, fp); } The first `fopen` call sets `errno` to 2 (edit: really ENOENT, which happens to be 2). The second one should not have an error code but it's still 2 (ditto other edit). The third one demonstrates that the call does not, in fact, change `errno` since the file exists.
- deleted 4y ago[deleted]
- satvikpendem 4y agoThe point about all these languages that came after C that have exhaustive pattern matching on a Result type is that there are better ways of doing things. Why have the user have to handle cases like this by remembering to put in an `if err != nil` when the computer can literally compute where it's not being used and yell at you or even add it for you? Even in C it was not "just fine," it caused all sorts of bugs. But the bigger issue is that PL theory has improved since then, why be constrained to the same patterns as before?
- klodolph 4y agoIn my experience, the code inside the `if err != nil` branch is often different. At least, when I write code. It takes me longer to write Go code because of all the error handling stuff I'm putting in. Then it takes me less time to debug and fix run-time errors, because the error messages that my code generates are informative. I like this tradeoff for production code. This tradeoff is usually a bad tradeoff for throwaway scripts. I have plenty of experience in other languages, including languages with exceptions and languages with enforced error checking (like Rust). My experience with these other languages has made Go feel a less clumsy. For languages with exceptions, I often end up with code like this: SomeType aVar; try { aVar = SomeMethod(); } catch (SomeError ex) { throw new OtherError("description", ex); } For languages like Rust, I often end up with this: let aVar = match SomeFunction() { Ok(x) => x, Err(err) => return Err(OtherError::EnumValue(someContext, err)), }; (Yes, I know about map_err.) And in Go, I end up with: aVar, err := SomeFunction() if err != nil { return nil, &Error{"some context", err} } This is not meant as a statement about “the correct” way to write code, it’s just meant as an illustration of the kind of code I often end up with, in projects that I work on. It depends on how you write code, what you're doing, and your approach to solving errors. Do note that you don’t HAVE to use the if err != nil approach in all of your Go code. It is not the only method for bubbling errors up. There’s a whole discussion to be had about when it is appropriate to panic()/recover() and why you would choose to panic() in Go, even though people say you’re “not supposed to do that”.
- steveklabnik 4y ago> (Yes, I know about map_err.) I am curious to hear more about why you prefer what you wrote to map_err, if you're willing to share. I have my own guess, but I'm always interested in things like this. (For those that don't know, you could write the above code as something like: let aVar = SomeFunction().map_err(|err| OtherError::from(someContext, err))?; or if it becomes too long, let aVar = SomeFunction() .map_err(|err| OtherError::from(someContext, err))?; and in some cases you don't even need the |err| but since we're taking two arguments here you can't 'curry' it in a sense.)
- klodolph 4y ago