16 ms·
It's so strange seeing people who write these sorts of articles constantly praise Go's error handling. In what world is doing the whole if err != nil thing ever
by s4vi0r 9y ago
It's so strange seeing people who write these sorts of articles constantly praise Go's error handling. In what world is doing the whole if err != nil thing every 30-50 lines not ridiculous?
I get that goroutines are great, but that really seems to be all Go has going for it. The GC sucks, error handling feels like something from a bad language designed in the 70s, missing generics, math library is awful, Rob Pike being super opinionated, etc.
- Xeoncross 9y ago> doing the whole if err != nil thing every 30-50 lines not ridiculous? It's more like every 5 lines. I love it as every error is handled correctly and in scope. No error is left behind. Most languages I've used devolve into the GLOBAL MASTER DEFAULT HANDLE ALL ERRORS which is just gross.
- embwbam 9y agoThere are options between these two extremes. Haskell's Except monad is a great example. Or javascript promises. You can launch a bunch of actions in sequence and provide a single error handler for them. Each step doesn't have to worry about it, but neither is the error handling invisible and completely global.
- acjohnson55 9y agoYep. For my money, the war is over, and the monad won. I shouldn't say the m-word, as it turns people off. But that's the unifying pattern. Javascript messed it up a little bit with promises, because it's so easy to swallow errors. IMO, they should have made it so that unhandled promise rejections throw by default at the next turn of the event loop, with a method that could be called to opt out individual promises.
- girvo 9y agoThey have, sort-of! At least, V8 did IIRC. I use Creed[0] myself, which using it's `shim()` function gives you the same behaviour. It's quite lovely. [0] http://blog.briancavalier.com/creed/ http://blog.briancavalier.com/creed/
- haskellhater 9y agoError handling in Haskell is, in practice, terrible. Not only do you constantly have to wrap and unwrap potential error values to unify different error types, you also have to deal with a dozen variants on Either and related typeclasses, none of which are used consistently across the ecosystem. And as if that wasn't bad enough, there are also actual throw-catch-style exceptions, which are not checked in anyway and, combined with laziness, can explode in your face at absolutely any time whatsoever. It's a big big mess. I stopped writing Haskell maybe a year ago, but at least back then getting GHC to print a stack trace when an exception wasn't caught was a major feat that involved recompiling every single one of your hundreds of dependencies with profiling flags. Guess how well that went most of the time. Anyway, Go-style error handling's lack of any of these issues is what makes it attractive to me: it's simple, straightforward and get's the job done. Granted, with a bit more typing and the occasional bug that could have been avoided by not having to type the (almost) same three lines once again, but not having to jump through any of the hoops I keep encountering in more sophisticated languages is so, so worth it to me. I feel like Go stays out of my way, lets me focus more on actually getting things done than on dealing with the language's intricacies.
- pcwalton 9y ago> Not only do you constantly have to wrap and unwrap potential error values to unify different error types This is a legitimate issue, although in Haskell you can use one error type like Go does if you want to. > And as if that wasn't bad enough, there are also actual throw-catch-style exceptions, which are not checked in anyway This is also the case in Go (panics and recover). > I stopped writing Haskell maybe a year ago, but at least back then getting GHC to print a stack trace when an exception wasn't caught was a major feat that involved recompiling every single one of your hundreds of dependencies with profiling flags. This is also the case in Go. Errors do not retain anything about their state. You need to use a package like https://github.com/go-errors/errors https://github.com/go-errors/errors, which has the exact same issue you describe with dependencies.
- zaarn 9y ago>This is also the case in Go (panics and recover). It is considered good style to not let a panic escape your library API unless something just went very severely wrong that the library could not possibly continue to function normally. >This is also the case in Go. Errors do not retain anything about their state. Errors are an interface, you can put any state inside it you want and I've frequently done so (retain line counts in parser or network data for example)
- hota_mazi 9y ago> I love it as every error is handled correctly and in scope. Not in scope. Because you are forced to deal with the error right here right now, sometimes it's in scope and sometimes, you're simply not able to do anything with the error so you effectively mishandle it. Exceptions allow you to handle the error where it actually makes the most sense.
- deleted 9y ago[deleted]
- taneq 9y agoON ERROR RESUME NEXT
- ardi93 9y ago> No error is left behind I don't think so. You can choose to ignore the error (I know it's bad practice), but it's allowed. And sometimes you don't know what to do with the error
- lobster_johnson 9y agoIn practice, scoping becomes an issue because "err" sneaks in everywhere. For example, consider: foo, err := getFoo() Now your scope has an "err" and you open yourself to logic errors such as: foo, err := getFoo() ... do stuff ... if errr := makeBug(); errr != nil { return err } Spot the (heh) error? It's caused by ":=" allowing variables to be reassigned. You can avoid this dangerous pollution, but it's super ugly: var foo *Foo if f, err := getFoo(); err == nil { foo = f } else { return err } (You have to have the return in the second branch, otherwise "go vet" will complain. Sigh.) I have lots of these, and they're not helped by the fact that Go has no type-level mechanism to guard against the misuse of multiple-return values. That is, in the above, either "foo" or "err" are usually set, and if "err" is set, it means "foo" isn't. But the type system doesn't prevent you from accidentally ignoring "err" and using "foo", or vice versus. This gets even worse if the func returns a (non-pointer) struct as its value. The fact that Go allows shadowing (exacerbated by the convention of using "err" as every single error variable) and promotes the reuse of variables (e.g. the above ":=", or declaring "err" outside a loop) also means it's super easy to assign the wrong thing. All of the above are my single biggest source of bugs during development. That, and how accidentally using nil/zero values, including the damnable nil map/slice cases. "go vet" (whose shadowing check is disabled by default (!)) and the various linters only go some ways towards helping here. The errcheck linter (which you can enable through gometalinter) is absolutely essential.
- lowmagnet 9y agovar foo *Foo if foo, err := getFoo(); err == nil { return nil, err } // foo is usable now
- lobster_johnson 9y agoI suspect you don't know how := works. ./test.go:14:15: foo declared and not used
- flip_rect_red 9y agoit appeared to be a typo, and as you suggest the compiler caught it var foo *Foo var err error if foo, err = getFoo(); err != nil { return nil, err } //foo is usable now
- millstone 9y agoGo errors are routinely ignored. Even Hello World ignores the error return. https://gobyexample.com/hello-world https://gobyexample.com/hello-world
- thewhitetulip 9y agoNo. That is incorrect. Errors are to be handled by the programmer and every book I read says "We are ignoring errors here for the sake of simplicity, you are strongly advised not to ignore them". I have said so in the intro textbook I wrote.
- junke 9y agoError handling is what happen after you discover there was an error. In Go, "checking" for errors may be skipped (and people make mistake, even True Programmers), in which case they are handled just as if they were ignored. Even if the error can be ignored for fmt.Println, it is not clear from the example whether it was even taken into account or not. The following could be required: _ := fmt.Println("hello world") That means that the function call can return an error, and thus you have to use the return value, but the programmer decided to ignore it. Contrast it with: fmt.Println("hello world") Did the programmer know there was possibly an error? What happens if there is an error (not specifically for this example, but in general), and you don't check for it? then it is ignored. In another language, the same line could be the source of an error; but only error handling depends on the programmer, not error checking. Moreover, the default handling strategy is to abort execution. Actually ignoring errors requires more work. And errcheck is apparently no silver bullet: https://github.com/golang/go/issues/20148 https://github.com/golang/go/issues/20148
- pjmlp 9y agoThat is a fallacy because I have seen Go code with _ everywhere , thus not handling errors at all.
- zaarn 9y agoMost linters that concern themselves with errors will complain about this and I would not accept any code that does this beyond tests. Go allows you to do certain things but it's not a fallacy because it's something that's allowed but not necessarily tolerated by what most consider "good Go coding standards"
- pjmlp 9y agoMy consulting experience has shown that beyond startups, or a few Fortune 500 companies like Google and FB, linters and code reviews are seldom done, unless we are speaking about safety critical domains like aeronautics, autos, train control systems,medical devices....
- zaarn 9y agoSo because a lot of people don't use best practises that means the language in which these are to be used is worse off? It strikes me a bit like saying "because most people don't wear seatbelts, cars are unsafe because you may get ejected out of the cockpit in a crash". Wear a seatbelt and use linters, I'm not going to excuse the malpractices of the larger industry.
- junke 9y agoThat means that you better have safe defaults in a language, rather that opt-in linters.
- zaarn 9y agoMore safe defaults also means I have to think more about escape hatches. Sometimes ignoring an error is the intended way of operation, although rare. Same goes for some other parts of the go coding standards. Sometimes you need to violate them and having to first use some escape hatch like Rust's unsafe is annoying. I'd rather just keep track of linter complaints.
- aninhumer 9y agoHandling errors as values is great, but making that value a product type rather than a sum type is not good design. It means you have to return a result even when you have an error, and so nothing enforces that the user actually handles the error properly.
- mmargerum 9y agoExplicit error handling in go is wonderful. I consider it a feature
- mappu 9y agoWhat don't you like about Go's GC? Compared to Hotspot, it doesn't STW; it makes a different throughput/latency tradeoff that is more suited for its application domain; and it's actually needed a lot less often because the compiler can put many objects on the stack. A generational collector is currently under development (but won't be in 1.10).
- openasocket 9y agoHotspot has several GC algorithms available, FYI. And the term "stop the world" is misleading, it doesn't fully capture the nuance of garbage collection. Any GC algorithm that does compaction will require some sort of blocking. You can do compaction incrementally, so you only lock small sections of the heap at a time, but that would still be considered "stop the world" despite being orders of magnitude faster than the naive case. Go GC gets to make the claim that it doesn't "stop the world" because it doesn't do heap compaction. Which will become a major problem when dealing with large in-memory data structures or certain usage patterns. And the fact that they still don't have a generational collector is concerning.
- tychver 9y agoErlang has a really nice solution here by using a separate heap per Erland "process", and because they're owned by that process, can do a copying/compacting GC without having to take any kind of locking. Ruby's Guilds could adopt a similar idea and avoid STW GC there too.
- openasocket 9y agoAgain, the term "stop the world" is misleading, there are a variety of compaction strategies that greatly minimize locking. There are even realtime GC implementations for the JVM. Erlang's solution is interesting, but remember in Erlang processes can't share memory, they only have message passing. So you never have to worry about multiple threads writing to the same data structure at once, which is what makes compaction hard.
- geodel 9y agoNothing ridiculous about it. I work on a Java code base where there is try catch every 4-5 lines. The application is working fine and nobody is unduly worried about fine taste in exception handling. If application were in Go it would have err != nil every few line that would also be fine for us. With all the things that you mention Go does not offer helps it keep simple. It has already proven much useful for many of of my projects where a 'better' language would fail for variety of reasons.
- hota_mazi 9y ago> I work on a Java code base where there is try catch every 4-5 lines. There is a huge difference. The Java code you are reading is caused by bad programmers who are not using exception handling correctly. Proper exception use means sometimes catching and sometimes ignoring the exception and declaring it in your `throws` clause. In Go, testing against `nil` every twenty lines is not only common, it's the ONLY way to write robust code. You simply don't have a choice.
- geodel 9y agoConsidering so many projects in last 10 years where code was not even formatted even though it was one command away in IDE, ridiculous variable names, useless comments like 'method getA() returns A" I am not sure it is result of bad programmers or just natural state of IT industry. Java succeeds despite lot of bad code mean immense hard work on performance/ libs side by JDK teams. I think same is going to be true for Go. I have written few projects in Go and they have produced much more value than time taken to write them. At the end of day companies I worked for prefer mediocre code that is written and used vs excellent code in brilliant programing language that was never completed.
- e3b0c 9y agoInterestingly it makes me a better programmer after being used to the Go style. Now I don't allow unused headers/variables in my C/C++ code and feel natural to check every return error. 'Optional' warnings don't happen anymore either since -Werror feels familiar. Overall my C/C++ code becomes more reliable and easier to debug.
- valuearb 9y agoIf you aren't checking errors more than once every 30 lines you probably aren't doing it right.
- juhanima 9y agoNo, it is not ridiculous, it is meticulous. That's one style and approach in programming and not without merits. I personally like exceptions, as they enable focusing into what's supposed to happen instead of what all could go wrong. And the abstraction power that comes with this approach. But I wouldn't rush to say I'm right and the meticulous party is wrong. There are many instruments in the orchestra. In the end all that matters is the skill of the player.
- TheDong 9y agoExceptions are a strawman. I don't like exceptions. Go's error handling is still ridiculous. It doesn't have sum-types, it has the incredibly stupid interface/struct nil punning so you must return an interface and type-assert down, it doesn't have a tuple object being returned but actually multiple return which is a semi-broken thing, it doesn't have an either/result type or an optional type to express mutual-exclusivity / optionality.... Yeah, the whole thing is disgustingly bad. That doesn't mean I prefer exceptions. I do prefer meticulous "errors as value" error handling. Go's implementation is the worst implementation of that because the type system doesn't support various features that make handling error values better.
- 013a 9y agoI really think error handling could be solved by adding some simple syntactic sugar. For example: "if err" being compiled directly to "if err != nil" would be a quick win. Second more complex idea: add a new keyword like "res := throw MyFunction()", being allowed if "MyFunction()" returns an "error" as its last argument and the calling function returns an "error" as its last argument, simply return the zero value or named return value of any other return values + the error if there is one, otherwise continue. Point being, the core of Go is really good. When I come across a command line tool written in Go, my first thought is "fuck yeah, this is going to be so easy to install", versus something like Python which is a bitch unless the developers have taken the time to brew it or something. Countless planet-scale infrastructure tools have been written in it; Kubernetes and Docker to start. I can name a dozen startups, both large and pre-seed, which use it with resounding success. Yes, it has warts. Every language has warts. Go is just "different" because it tries to be simple. Nearly no other language in the world has "simplicity" as one of its core values, and a lot of people don't fully respect what that means.
- TheDong 9y agoNone of that helps with the real problem of golang errors. The real problem with go's errors is complex and significant. There's no Either/Result type so the multiple return doesn't actually tell you that you get a zero value when there's an error (and in fact there are functions in the stdlib that do this). There's no good way to check whether an error is a given thing because error chaining isn't built in and errors rarely are typed as sum-type-enums as they are in rust or other decent functional languages. For example, `os.IsNotExist(err)` will not work for a caller if I did `return fmt.Errorf("error opening file: %v", err)` in a function. The stdlib encourage a dozen really bad methods of doing error handling; there's `io.EOF` (which is a mutable variable btw). There's PathError. There's os.IsNotExist. There's random panics in some functions. There's nil returns with no further info. The stdlib has multiple examples of 3 or 4 different error handling styles, and they're all horrific because they don't chain and don't allow for exhaustive matching. Go may be good compared to C or other languages that were designed way back when. In modern times, go is a massive step backwards with its incredibly underwhelming type system, absolutely asinine community, lack of good abstractions, and so on.
- alien_at_work 9y agoYes, it's amazing that a language made when Go was is basically a version 1.5 of languages made 40 years ago. C had the worst possible error handling and Go has swooped in to take the second spot. The very best error handling I know of is in CLOS. Instead of exceptions, they have "conditions". A condition is signaled like an exception but with a big difference described below. The thing about errors is, the code that detects the error can never know how to deal with that error. If it did, then it wouldn't be an error it would be a case within that function. However, that code may know ways the error could be dealt with, it's just not in a position to choose. So what conditions do is, you can define a condition and so called "restarts" along with that condition. What the restart does is say "here is a way I could handle this error" and code higher up the stack has the option to (1) use the restart, (2) define additional restarts on that condition or let the condition propogate upwards. The condition signalling does not necassarily unwind the stack: if you choose a restart, the code continues in the stack that restart was defined. So this means every part in the stack that knows how a condition could be recovered from can provide the option but it is left to code higher in the stack to chose the actual strategy. This is superior to the exception strategy of unwinding the stack until you find a handler because sometimes you really want the code to continue execution where it is. But, of course, such a system is really expensive to implement. If you can't have that my preference is the Haskell style of using a sum type where errors can happen but writing as much of your code as possible with any error conditions prohibited by the type system.
- steveklabnik 9y agoFun fact, Rust had conditions a very long time ago https://doc.rust-lang.org/0.9/guide-conditions.html https://doc.rust-lang.org/0.9/guide-conditions.html Nobody used them, so we removed them.
- flavio81 9y agoWell, that's really bad, because I think that things should be implemented in the best way possible. "Nobody used them", yeah, probably because no other language (apart from CL) had it, so nobody knew how to leverage them. But leave it in place and at some point in future time, some blogger would write a nice article on using them in Rust and developers would catch up in this cool feature. I develop in Common Lisp and conditions&restarts are so much superior to the other error handling paradigms i've seen, that it's not fun being left without them.