Yes because other language just hide errors from the user.
I think the reason people find go a bit annoying with the error condition is because go actually treats errors as a primary thought, not an after thought like Python, Java.
I assume you're talking about languages with exceptions when saying "other language just hide errors from the user." I think that's a gross over-simplification of exception-based error handling. I generally do prefer explicit, but there are plenty of cases where exceptions are clearly elegant and more understandable.
My preference is a language like Elixir where most methods have an error-code returning version and a ! version that might raise an exception. Then you (the programmer) can choose what you need. If you're writing a controller method that is for production important code, use explicit. If you're writing tests and just want to catch and handle any exception and log it, use exceptions. Or whatever makes the most sense in each situation.
I've never gotten the explicit argument. Java checked exceptions are also part of the function signature/interface and nothing prevents one from making a language where all exceptions are checked then just doing
I get at the end of the day it's all semantics, but personally I kinda like the error-specific syntax. If you want to do the normal return path, that's fine, but I prefer the semantics of Rust's Result type (EITHER a result OR an error may be set).
To each their own, it's not something I really worry about.
Yeah same, Go's explicit argument never resonated with me either. In Elixir it's similar to a Result type, being a tuple such as either `{:ok, return_val}` or `{:err, err_msg}`, which is perfect for using with `case` or `with` depending on your situation.
You can't hide an exception if it crashes your program. You can definitely ignore a return from a function, essentially swallowing it. It's the definition of an anti-pattern.
In most web applications I write, I have one error-handling block.
Access forbidden? Log a warning and show a 403 page. Is is JSON? Then return JSON.
Exception-handling in general is a pretty small part of most applications. In Go, MOST of the application is error-handling, often just duplicate code that is a nightmare to maintain. I just don't get why people insist it's somehow better, after we "evolved" from the brute-force way.
Errors usually happen during IO, but not in the main business logic and those two can be neatly separated.
But If you are coming from java I can understand the single error handling block is more comfortable, but coming from JavaScript/Typescript it's much more easy to check if err != nil, than to debug errors I forgot to handle, during runtime.
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.
I agree on the logging point but my experience was the explicit error handling and with good test coverage meant we rarely got into situations were we had non-deterministic situation were we relied extensively on logging to resolve. But we also went through several iterations of tuning how we logged errors. It's definitely a rough edge in what is readily available in the language.
> 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.
I 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)
}
}
Thanks 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.
Good 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.
Not 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.
Everything is explicit until someone decides to introduce a panic() somewhere... (I get that exists in more or less any language)
That said, in practice I see it following a similar philosophy to java checked exceptions, just with worse semantics.
Personally, I don't like high-boilerplate languages because they train me to start glossing over code, and it's harder for me to keep context when faced with a ton of boilerplate.
I don't hate go. I don't love it either. It's really good at a few things (static binaries, concurrency, backwards and forwards compatibility). I hate the lack of a fully-fleshed out standard library, the package management system is still a bit wonky (although much improved), and a few other aesthetic or minor gripes.
That said there's no language I really love, save maybe kotlin, which has the advantage of the superb java standard library, without all the structural dogma that used to (or still does) plague the language (OOO only, one public class per file, you need to make an anonymous interface to pass around functions, oh wait now we have a streaming API but its super wonky with almost c++ like compilation error messages, hey null pointers are a great idea right oh wait no okay just toss some lombok annotations everywhere).
End of the day though a lot of talented people are golang first and sometimes you just gotta go where the industry does regardless of personal preference. There's a reason scientists are still using FORTRAN after all these years, and why so much heavy math is done in python of all things (yeah yeah I know Cython is a thing and end of the day numpy etc abstract a lot of it out of the way, but a built in csv and json module combined with the super easy syntax made it sticky for data scientists for a reason)
> I am not used to writing code where 2/3 of it is "if err" statements.
I don't write Go, but I have seen this a lot when reading Go. It seems hard to escape. The same is true for pure C. You really need to check every single function output for errors, else errors compound, and it is much harder to diagnose failures. When I write Java with any kind of I/O, I need careful, tight exception handling so that the exception context will be narrow enough to allow me to diagnose failures after unexpected failures. Error handling is hard to do well in any language.
Also, refactoring my logging statements so I could see the chain of events seemed like work I rarely had to do in other languages.
It's a language the designers of which - with ALL due respect - clearly have not built a modern large application in decades.