3 ms·
> I understand were you are coming from but I actually like the explicit error handling in Golang. Things being explicit reduces complexity for me a lot and I f
by takeda 2y ago
> I understand were you are coming from but I actually like the explicit error handling in Golang. Things being explicit reduces complexity for me a lot and I find it easier to spot and resolve potential issues. It's definitely something that I can understand not working for everyone.
This sound a lot of like Apple user arguments about iPhone 1 missing copy & paste over a decade ago.
I am very pedantic about checking responses for errors, but from my experience when working with a team and existing project I see that people notoriously forget to check the result. TBH it is a pain to essentially repeating the boilerplate `if err !=nil ...`.
What's worse is that even documentation skips checks. For example `Close()` method. It's almost always returning error, but I almost never seen anyone check it.
The reason for it, is if you want to use `defer` (which most people do) you would end up with very ugly code.
The other alternative would be to then making sure you place (and properly handle error) close in multiple places (but then you risk of missing a place).
And other solution would be using `goto` in similar way as it is used in Linux Kernel, but there are people who have big problem with it. I had a boss who religiously was against goto (who did not seem to understand Dijkstra's argument), and asked me to remove it even though it made the code more readable.
- xyzzy123 2y agoI think go makes more sense if you imagine spending more time reading MRs and code than writing it. Standard go error handling maximises for locality. You don't see many "long range" effects where you have to go and read the rest of the code to understand what's going to happen. Ideally everything you need is in the diff in front of you. Stuff like defer() schedules de-alloc "near" to where things get allocated, you don't have to think about conditionals. If an MR touches only part of a large function you don't have to read the whole thing and understand the control flow. The relative lack of abstraction limits the "infrastructure" / DSLs that ICs can create which renders code impenetrable to an outside reader. In a lot of C++ codebases you basically can't read an MR without digging into half the program because what looks like a for loop is calling down into a custom iterator, or someone has created a custom allocator or _something_ that means code which looks simple has surprising behaviour. A partial solution for that problem is to have a LOT of tests, but it manifests in other ways, e.g. figuring out the runtime complexity of a random snippet of C++ can be surprisingly hard without reading a lot of the program. I personally find these things make go MRs somewhat easier to review than in other languages. IMHO people complaining "it's more annoying to write" (lacking stronger abstractions available in many other languages) are correct but that's not the whole story. P.S: For Close(), you're right that most examples skip checking the error and maybe it would be better if they didn't. It only costs a few lines to have a function that takes anything Closable and logs an error (usually not much else you can do) but people like to skip that in examples. type Closable interface { Close() error } func checkedClose(c Closable, resourceName string) { if err := c.Close(); err != nil { log.Printf("failed to close %s: %v", resourceName, err) } }
- takeda 2y agoThanks for the Close() example, that's a nice solution, although would it work if you wanted to handle an error (not just log it?) > Standard go error handling maximises for locality. You don't see many "long range" effects where you have to go and read the rest of the code to understand what's going to happen. Ideally everything you need is in the diff in front of you. I'm assuming you're comparing to exceptions. I don't know about that. I think this relies on discipline of the software engineer. I can see for example someone who is strict and only uses exceptions on failures and returns normal responses during usual operation. With Go you can use errors.Is and errors.As which take away that locality. Or what's worse, you could have someone actually react based on the string of the error message (although with some packages, this might be the only way). I still see your point though, but I also think Rust implemented what Go was trying to do. You get a Result type, which you can either match to get the data and check the error, you can also pass it downward (yes, this will take away that locality, but then compiler will warn you if you have a new unhanded error downstream), or you can chose to unwrap without checking error, which will trigger panic on error.
- xyzzy123 2y agoGood points, I think it's fair to claim Result and Option are technically better (when combined with the necessary language features and compile-time checks). Re: Close() errors yeah most times you would be better off writing the code in place if you really need to handle them. You can make a little helper if you find yourself repeating the same dance a lot. Usually there's not much you can do about close errors though.
- NomDePlum 2y agoNot really understanding the iPhone reference or how it relates here. Sounds like the problem you have with the error checking relates more to development practice of colleagues than the language. We used defer frequently. Never considered it ugly. 'goto' (hypothesising here as not used it) and use of exception handling that is expected to be handled at edge of boundary points in codebase can be elegant but does need careful thought and design is my experience. Can hide all sorts of issues, lead to a lot of spurious error handling for those that don't understand the intent. That's the biggest issue I have with implicit (magical) error handling - too many people do it poorly.