12 ms·
Error Handling in Go (2016)
- wahern 8y agoFWIW, persistence of I/O errors is precisely how stdio works in C. When fwrite encounters an error it will continue to return failure (for the same FILE handle) until invoking clearerr. ferror is used to query the error value.
- Groxx 8y agoAnd I dislike the persistent-error setup in Rob Pike's example for the same reason as disliking global errs in C: they're easy to forget, especially because they're abnormal. And history keeps showing these things being forgotten in large numbers, so I don't see why it wouldn't repeat with Go.
- bdamm 8y agoIt's totally on point. I've been writing a fair bit of Go lately and consequently am firmly in the Generics! Now! camp. The pace of writing in this language is almost snail like, and a lot of that is due to re-implementing common idioms like popping and pushing from/to a stack and then debugging the little typos that creep in around the edges. It's freaking annoying, and it's a shame because the language is otherwise nice to work with. If it weren't for the testing workflow I'd have given up on Go entirely, just because of how annoying it is to re-implement the basics, over, and over, and over...
- skybrian 8y agoDo you really need to debug errors from implementing a stack? If you mean compile-time errors, I would look into getting your editor to report them while you type.
- z0r 8y agoPeople constantly screw up the simplest and easiest things, especially when there are hundreds of simple and easy things in a program. And Go makes sure that you have to write a hundred simple and easy things every time you work with it to solve problems.
- kjksf 8y agoI just finished porting 50k loc from Java to Go: https://github.com/ravendb/ravendb-go-client https://github.com/ravendb/ravendb-go-client You won't find hundreds of purported simple/easy things that are exclusive to Go code. There's probably less than 10 cases where I implemented some simple thing (like a set of strings) that Java gave for free because of library function that used generics. Among 50k loc that's background noise.
- maximente 8y agodon't forget the defer keyword: from README: // ... use session session.Close() can be changed to this right after you get a session: defer session.Close()
- linuxftw 8y ago> Go makes sure that you have to write a hundred simple and easy things every time you work with it to solve problems. This is precisely how I feel, could never capture it in words myself.
- newen 8y agoPoint is you shouldn't have to implement a stack. I have never had to do that in any language I used except C. Sounds like a nightmare.
- courtf 8y agoLuckily you can implement a stack in 10 very simple lines. And if you don't want to do that, there are many existing implementations.
- tsimionescu 8y agoWell, not ones as efficient as the one showed here, since those can't be expressed in the language as it currently stands.
- tonyedgecombe 8y ago>there are many existing implementations Sounds like JavaScript/Node all over again, which shouldn't surprise anybody as many of the prominent Node developers hopped over to Go.
- kjksf 8y agopush: a = append(a, el) pop: n := len(a) el := a[n-1] a = a[:n-1] It took me, literally, 10 seconds to write.
- newen 8y agoIs that an O(n) implementation of pop? I hope you realize my point.. I looked it up and it's not but the point is that you shouldn't have to worry about these things, including implementation details of trees and hundreds of other data structures.
- oconnor663 8y agoIt isn't. It re-slices the backing array and retains its capacity. (Maybe that's what you looked up at the end?)
- johan_larson 8y agoI don't know. I've worked in three different languages for years on end, and they all have different pain points. C++ is too complicated, and generics are part of that complexity. Java has an ok basic language, but the ecosystem is weird and fad-prone, jumping from beans to patterns to annotations to injection, searching for the One True Way. Go is a little too simple, which sometimes leads to rewriting fairly straightforward stuff that a few clever general mechanisms would let us write once. This experience makes me suspect that there will always be some pain point with the language. We'll never be happy; that's impossible. The only thing we can do is choose what type of unhappiness we are willing to live with. The thing I love about Go is its fundamental clarity. It's very upfront and literal. I find it easy to understand what is happening in any particular bit of code. And I suspect that whatever complexities we add would compromise this clarity in the name of brevity. I'd spend less time being bored, and more time being puzzled or incredulous. And fundamentally, I'd rather be bored.
- paulddraper 8y ago> the ecosystem is weird and fad-prone Won't dispute that but did that materially affect your chosen paradigms and patterns?
- johan_larson 8y agoWhen I had the opportunity to choose, I felt I could find useful ways of doing things. The problem was that I often had to accommodate myself to someone else's choices that had been made years before, and I had to understand what the heck they had done before I could make useful upgrades. And with many ways of doing things, some of which are hugely reliant on really non-obvious behavior controlled by cryptic settings in control files or annotations, that can be hard.
- snarfy 8y agoHave you tried C#?
- johan_larson 8y ago
- courtf 8y agoI don't agree with this, and I find the pace of writing in Go to be quite quick. I've recently been working on a server that performs arbitrary work pre-defined in DAGs, with an API, full tests and several fancy features. The initial bulk of the work took me about 3 weeks and the only dependency is a test mocking library.
- chessturk 8y agoMy work has handled this with autogeneration of things like DB models. I don't know if that would be applicable to what you're doing.
- int_19h 8y agoSo, basically, textual, non-syntax-aware macros? It's not uncommon to use such in C to define generic data structures and operations, but it's very painful to write and maintain... and besides, it has been widely considered a pain point in C for decades. It shouldn't be necessary in a modern well-designed PL.
- JyB 8y agoI've been working with Go full time for 2+ years. After forcing myself to understand its philosophy and idioms, I surprised myself with not missing generics at all. The language seem to have found a nice spot, hence its success. Strangely, I feel the day Go has generics is the day its attractiveness will start to fade away.
- lenticular 8y agoThis kind of reminds me of the story of promises in javascript, which are almost but not quite monads. Among other things, this has made implementing cancellation difficult. In addition, if it were monadic, the async/await syntax could have been trivially generalized to arbitrary monads. That would have been nice.
- BreakfastB0b 8y agoYou can get async / await for arbitrary monads in JavaScript using generator functions and coroutines.
- michaelsbradley 8y agoAlso, IxJS and RxJS provide some really nice sugar for working with higher-level abstractions around "pull" and "push" flows. The protocols associated with `Symbol.Observable` and `Symbol.asyncIterator` have small surface areas, so it's not difficult to hand-write implementations (using generators and async generators) to adapt code that wouldn't otherwise fit in the flow.
- lenticular 8y agoThanks for the tip!
- _InTheMonads 8y agoExcept no one cares about monads.
- vldo 8y agoso, what's the resolution, 3 years later?
- coldacid 8y agoThere hasn't been one.
- stouset 8y ago"Write `if res, err := foo(); err != nil { return nil, err }` everywhere all the time", apparently.
- Groxx 8y agoN years later, maybe we'll have the handle/check proposal[1] implemented. Which I'm actually fairly optimistic about - much of my error handling would be simplified by it, since in many cases (90%+?) any error can be treated as fatal. There are, fortunately or unfortunately, a lot of very good counter-arguments and alternate proposals and specific implementation debates. So I'm not sure who long it'll take, and as much as I Want It Now™ I do think it's important to be careful for the long-term health of the language. [1]: https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md https://go.googlesource.com/proposal/+/master/design/go2draf... , or https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback for the current state / counterpoints / etc
- userbinator 8y agoBoth this and Rob's article seem to be hiding an inefficiency: if one of the early functions in the chain (hint) fails, the rest of them are still called, just to do nothing. It also hinders understandability and debugging, since you're continually bombarded with "if(err)" after the first failure. I've thought about this problem too (error handling in general), and with that hint of this being a "chain", arrived at this solution which I think is extremely concise and elegant: if((err = func1()) || (err = func2()) || (err = func3()) || (err = func4()) || ... ) { /* handle the error */ } or if you want to get more fancy and know which step failed, int step = 0; if((step++, err = func1()) || (step++, err = func2()) || (step++, err = func3()) || (step++, err = func4()) ) { ... Of course this can be modified suitably if your error vs. success values are different, but the structure is the same: a chain of actions one after another, broken only by short-circuit evaluation. It's surprising that this pattern isn't seen more, because I've shown this to a few others and the response has almost always been an initial puzzlement followed by "wow, that's extremely neat" or "why didn't I think of that?" This is certainly an example of "use the language".
- chessturk 8y agoStealing this pattern. Thank you.
- Insanity 8y agoYeah me too! But I wonder how it'll hold up in some actual code scenarios where not everything is chained neatly in a function. And I could rewrite that, but that might be worse. If you want to do something with the intermediate results (e.g, the functions return more than just error) this will not be possible.. resourceName, err := connection.getName() if err != nil { // handle } fst := strings.Split(resourceName, ".")[0] sendTime, err := connection.findSendtime(fst) if err != nil { // handle } // do something with sendtime and call new func.. you get the point EDIT: Fix some mistakes in my example code. Obviously fake example though :)
- ben_jones 8y ago
- natefinch 8y agoI've been writing Go for over 6 years. Error handling at the source of failure is a feature, not a bug. Rob Pike's example code is bad and I would not allow it through a code review. Why? It makes the code lie. It appears as though the writes cannot fail, but in fact they can, and later writes become no-ops. On top of that, it means that you lose the context of which write failed. Did you fail writing the header, the body, or the footer? You can't know anymore. This is like try { doThing() doThing2() doThing3() catch (Exception ex) { // handle } You can't tell what failed. You can't tell what is possible to fail. The handler can't differentiate.
- syrrim 8y agoCode lies all the time. This is called an abstraction, and is very useful. The abstraction that rob's code provides is that it makes you think of writing not as doing something with the OS, but as merely pushing to a buffer, with flush being the call that actually commits the transaction. Therefore, flush is the call that can fail. Is this literally what is happening? No, but it is a much easier way to think of things, and makes the code more readable. >Did you fail writing the header, the body, or the footer? You can't know anymore. You are writing to the same stream in both cases. The error wouldn't be caused by the header or footer data, but by some property of the stream, like it being closed, or the disk running out of space. Knowing whether the failure occurred while you were writing to the header or footer is unnecessary information, and therefore obfuscatory.
- ereyes01 8y agoI disagree with this rationale, but I'll let someone smarter than me make the argument: "The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise." - Edsger Djikstra Or said another way, a good abstraction automates a precise set of tasks instead of adding ambiguity.
- firethief 8y agoI'm not sure that's the argument you want to use it as. Dijkstra is surely not arguing that abstraction should not lose information (that would actually not be abstraction at all); he is saying that the important thing about it is that it "finds" the pertinent information (i.e. by throwing away the irrelevant details). He's not redefining the term as something incompatible with its usual meaning, but proposing a perspective, taking the information loss as a given.
- dilatedmind 8y agoIn this example could we not just use a for loop
- letientai299 8y agoCompare the 3 approaches, I rather go with the first one. Yes, it's verbose, ugly, repetitive. But It's dead simple, easy to understand. The other two approaches just make the code harder to reasoning once it getting bigger.
- tirumaraiselvan 8y agoExcept your program won't grow bigger with the "monadic" error handling approach.
- nprateem 8y agoError handling has to be one of the worst things about go. Even though IDEs can highlight where you've forgotten to bind an error returned from a function to a variable they don't prompt you if you then forgot to return that err back up the call stack - it's easy to just log the error and forget to actually return it. The only way I've found to avoid RSI and make error handling just about tolerable is to use an IDE that supports code autocompletion - e.g. type `err` and hit tab and it should replace it with an `if err != nil {}` block. The other thing is to use pkg/errors[1] and wrap the error so you've actually got a stack trace if you want to log the error further up the call chain. Error handling is a solved problem - exceptions, or like the article says monads if the language is functional. The fact that Go supports neither out-of-the-box is a frustrating waste of time. [1] https://github.com/pkg/errors https://github.com/pkg/errors
- YorkshireSeason 8y agomonads if the language is functional Monads are not a functional concept. For example an imperative language like Scala (which, being in the ML family of languages has a powerful and first-class functional sublanguage) has monads that work perfectly fine with imperative computation. Indeed, I find it a good programming style to let trivial, ubiquitous monads, like printing / IO / global state, be handled by the built-in primitives, while higher-level, unusual, application-dependent effects are implemented explicitly using monads. Monads are essentially a form of overloading / re-defining composition operators, e.g. the semicolon operator in traditional imperative languages. In languages like Haskell and Scala, types (and higher kinds) are used for this purpose, hence it is often though that monadic programming is restricted to statically typed languages with powerful types, and don't make sense for dynamically typed languages. Nothing could be further from the truth. All we need is a 'hook' into the sequencing mechanism(s) the language provides, at least in principle. Admittedly, the outcome is not nearly as pretty as in statically typed languages. See [1, 2] for discussions. [1] T. Garnock-Jones, Monads in Dynamically-Typed Languages. https://eighty-twenty.org/2015/01/25/monads-in-dynamically-typed-languages https://eighty-twenty.org/2015/01/25/monads-in-dynamically-t... [2] O. Kiselyov, Monadic Programming in Scheme. http://okmij.org/ftp/Scheme/monad-in-Scheme.html http://okmij.org/ftp/Scheme/monad-in-Scheme.html
- blondin 8y agolittle off-topic. am i the only one who thinks Go is becoming a verbose language? some of the creators i have followed for years because on their take on succinctness and simplicity in the software realm. and there we are with Go becoming somewhat verbose to write in my opinion. as opposed to C or Python, or even Rust, if you will, for example...
- Groxx 8y agoI find the language mostly concise, i.e. new types / funcs / etc are about as small as they can be (without type inference), but the type system tends to result in an absurd amount of error-prone duplication and/or wrappers. And the wrappers that can clean up the duplication take a fair amount of work each time, and must be repeated each time, so they really only exist when there are dozens of places where it's useful, rather than some smallish number - everywhere else you just `if err != nil { return err }` and that just keeps accumulating.
- networkimprov 8y agoThe design pattern espoused here is dated. The emerging consensus for exception-free Go error handling entails automatic dispatch of errors to local handlers. See the golang wiki[1]. I've drafted a menu of possible requirements for Go 2 error handling[2] because the Go team's Draft Design document omitted a requirements section. [1] https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback [2] https://gist.github.com/networkimprov/961c9caa2631ad3b95413f7d44a2c98a https://gist.github.com/networkimprov/961c9caa2631ad3b95413f...