I've never known a language with so much discussion on why what you are doing isn't idiomatic as Go.
I'm enjoying using Go for the few small services I'm using it for but it seems that a language which has to constantly fight it's users to reinforce what it considers idiomatic has some core issues.
this is especially true for everything related to error handling. I've never had to deal with a java codebase where exception were causing problems, but golang made me feel worrying about it from day 1. I really think they made the language a little bit too small.
I think small is a good thing in languages for example the C programming language is very simple in the language but very powerful. Simplify Simplify Simplify.
If you have only worked in languages with exceptions, it's not surprising that you would struggle with Go's C-style error handling.
I've grown to prefer it, as exception handling typically boils down to "not my problem" in most code bases. Go forces you to think through each error condition, which is an unusual amount of effort for people who may not be accustomed to it.
Go's error handling remains verbose, unsafe and error-prone compared to more modern languages which use return values for error signaling (Erlang, MLs, Haskell, Rust).
The problem is not that Go's error handling requires more thinking or efforts, it's that it is bad, and while that's an improvement from C's terrible error handling it's still nowhere near good enough, let alone good (unless you consider C's error handling to be good enough in the first place).
> I must be missing something.. Returning a value in Go for error signaling is the way to signal errors in Go.
Yes? That's the point, I'm comparing it to more modern languages which signal error "the same way", by opposition to languages implementing different high-level methods of error signaling. The clause is there to explicitly point out that I'm not considering or talking about exceptions and conditions-based error signaling.
Maybe it'd have been clearer if I'd written "languages which also use return values"?
Yeah, I parsed this incorrectly as well. It was an ambiguous statement.
You posted a while back that "scope inference (in Python) sucks". Could you expand on that. I know it's totally unrelated to this thread but I was really hoping to understand what you meant.
In Go, you're not returning a sum type, so it's down to the programmer to remember that the "real" return value is likely toxic waste when its copassenger the error return value is non-nil.
What's the practical problem of this, though? The (varname, err := thing) pattern is sprinkled all throughout the language, enough to where it should be instinctive for a coder to check the err value when doing anything that could fail.
Correct me if I'm wrong, but I believe with sum types you can chain together multiple calls then pattern match once at the end. In Go you have to check at each call.
If you have an unusual amount of error handling cluttering up your code in Go, then you can use `panic` (with `defer` and `recover` to convert panics to errors in the public interface).
You can embed metadata with sum types on a return object. You can then use the metadata programmatically to increase correctness, enhance readability, and reduce boilerplate. This blog post goes into more details:
> You're not returning a sum type so it's down to convention to remember that the "real" return value is effectively garbage when the error return value is non-nil.
Saying that a particular language feature is bad in a vacuum is a ridiculous endeavor because you aren't accounting for trade offs in the language as a whole. The most obvious way to "fix" Go error handling is to add some form of sum types to the language. But this comes with its own set of trade offs that have been discussed at great length on golang-nuts.
If MyFunc returns an error it's not possible to know where it came from unless you know all about someOtherFuncA and someOtherFuncB and they return different kind of errors.
What's missing from the stdlib is a utility that wraps errors to reproduce what's essentially a backtrace. `return wrapError("someOtherFuncA", err)`
The other issue is that (error) doesn't tell you what kind of errors are going to be returned. It's often useful to be able to classify errors as to respond to them accordingly. IO errors might be retryable but data-structure errors might be not. In the stdlib they use value comparison as a trick to classify errors combined with documentation of what exactly will be returned.
Think the point i to handle errors wherever they're best handled. Sometimes that means returning it and sometimes it means handling it right away.
I've written code with this style of error-handling in Perl 5, and I think it works quite well. I like having "custom" error-handling that fits the style and the purpose of that particular program, instead of trying to to fit a general pattern onto all programs. It also means you can start out with something very crude, and improve it as you go along and learn more about the failure modes etc.
Given its prevalence all across Go code, are you _sure_ it's not idiomatic? Hell, look at the presentation in the link: http://talks.golang.org/2014/readability.slide#11. If the oracle of idioms does not write idiomatic code, then wtf is idiomatic code?
> Go's error handling works well in small programs but there's a couple of issues.
It works fine in big programs, too. If you need more context, you add custom errors that have a context field. If you need to match different types of errors, you can have an error code that you can check against, or you can simply match strings (a lot of Go code does this). What you see as a problem is not really much of a problem in practice for well-structured code.
That's a question i've often asked myself, whether having me think about error handling at almost every line of code was actually a good thing, or just plain overkill since most of time killing the top-most function ( and maybe retry it) is the only thing to do.
I don't really have an answer to that. But what i know is that i find the practice of having typed exception in java plus catching the one you want to deal with per block of code much more readable than the serie of if / else that go forces you to do.
Ps : i do write in many different languages, including C and python. But with C you know that you're writing in an prehistoric language so nothing cumbersome really surprises you, and python also has try / catch logic.
I agree with that as well. I was just now writing a program to receive a command and perform an action, and sometimes it will receive invalid commands, or there will be some error performing the action. In these cases, I just want it to ignore the errors and resume its loop.
With Python, you just wrap it in a try/catch and forget about it. With Go, I already wrote five error checks and I'm sure I haven't caught some condition that will make it crash. I have to say I prefer the exception style, at least in this case.
An exceptionally well written book, I'd like to add.
Many authors try to unite casual style with technical writing. Russ Olsen does it beautifully.
He never treats you as a child or an idiot. He never lets you feel his master status. He's like the good-natured old-timer who sits next to you with a cup of coffee, teaching you more than you realize.
Gonic - causing a desired reaction - might work, though I suppose any correct code would fit that definition. I sort of like gothic - always recognizable, consistent, big.
Heh... I had a similar thought back when I was using Python. In both cases, I don't think it's a matter of the language "fighting" you. You can write Go however you like, so long as it compiles and runs. However, it IS a matter of people on the Internet mocking you for not being "pythonic".
You must, truly, channel the minds from planet 9 to enjoy the experience of Go. (This statement is not considered 'polite' in certain circles, but darn it, I can't help myself ;)
That said, I've tried channeling Rob and it is an interesting and informative experience ..
I think that's a good thing. If Scala had a better commonly accepted idea of what's "idiomatic Scala", then maybe the "there's more than 6497492 ways to do it" stigma that Scala's recently gotten might've been avoided.
You need discussion about what's considered "good" before you can arrive at a relatively common opinion about that.
Don't forget the constant reminder that leaving <language feature X> out is just simplicity, even if it means you're writing a lot of if statements and extra code.
It is a perfectly reasonable way to test for interfaces. A single struct may satisfy a hundred interfaces which it doesn't even know about, this is actually one of the best traits of go.
I looked at this slide for a while and couldn't understand it. Could you explain the detail a bit ?
It seems like relying on dynamic type casting over a nil variable, is that correct ?
But then what would the following "if" look like ?
This is how you can make sure that a struct implements an interface. The line 'var _ scan.Writer = (*ColumnWriter)(nil)' assigns a nil pointer of the struct to a scan.Writer interface reference which would fail if ColumnWriter doesn't implement the interface. And the casting always works.
This check is not really needed as the code would fail when a function which accepts scan.Writer would fail (compilation) if ColumnWriter is sent and if it doesn't satisfy the interface. However, this is easier to debug.
Yeah, this type of check is nice if you intend to implement an interface, but don't actually use the type as that interface in your package. This will give a compile time error if your type doesn't satisfy the interface. You could write a test for it too, but this is a little simpler and harder to miss.
Go does not require it. Interface compliance is statically checked.
This is a debugging trick because Go interfaces use structural subtyping instead of explicitly declaring which types implement an interface. This is a calculated design decision with several trade offs. There is nothing "sad" about it.
How do you list all interfaces a give type supports just by looking at it?
Both by design, as well as, by accident (usually it different method semantics).
You don't need to teach me Go, I was into it before 1.0 given its Oberon influences, and like everyone on gonuts that disagrees with the design gets told, I went elsewhere.
> How do you list all interfaces a give type supports just by looking at it?
By design, you can't.
> You don't need to teach me Go
Then don't state patently false claims. "Other languages provide constructs to check for interface compliance, Go requires writing dummy code." This is false because interface compliance is checked statically. This is orthogonal to whether you can list all interfaces satisfied by a given type.
> I was into it before 1.0 given its Oberon influences, and like everyone on gonuts that disagrees with the design gets told, I went elsewhere.
That's not a feature unique to gonuts. If you disagree with a project's fundamental design and stated goals, then I'm not sure what else you might expect.
For example, I'm not particularly interested in the JVM/Java world due to a myriad of design decisions that they've made. But I don't go around trolling the Internet with near content-free comments and inaccurate statements about their ecosystem.
> How do you list all interfaces a give type supports just by looking at it?
That question doesn't make any sense. I can write a new interface that an existing type supports and then use that type as that interface. That's the awesome power of go interfaces. Using existing types for things their original creators couldn't have thought of.
I found that I already do all of these as a result of reading through a lot of the Go standard library when I was learning it. One of the best ways to really learn a language is to read the standard library (the parts implemented in that language, anyway). That way you get a sense of the idioms used, but also understand the sometimes subtle trade-offs of common functions.
This can be both good and bad. Two examples: The Rust standard library has/had lots of awkward bits, from when certain language features didn't yet exist, or before there was a convergence on how to accomplish something idiomatically. The Ruby standard library has tons of Ruby code in it that's 20 years old, that nobody has touched for various reasons. I certainly wouldn't write Ruby in the same way.
Yeah. When I learned Ruby I read the Rails code base, which comes with its own set of issues, but which was more modern and was a better representation of the current state of Ruby.
In Java, I'd have to write 3 catch blocks. It's possible to write a blanket catch, toss it up to higher levels and make it someone else's problem. I vaguely recall this being against Pike's vision of what "exceptional" means and he wanted a language that forces developers to think of disk/network failure as a very common case.
Given that every single error check there is just passing up the error, is it really that different from having exceptions that propagate along with RAII style resource cleanup? The vast vast vast majority of Go code simply propagates errors.
> Given that every single error check there is just
> passing up the error, is it really that different from
> having exceptions that propagate along with RAII style
> resource cleanup?
Yes, it's fundamentally different. The whole point is to make those error blocks visible, so future maintainers are forced to deal with the reality that those invocations are fallible. Maybe there's a way to make the error handling less verbose, but hiding it altogether would subvert that explicit language design goal.
Except, you know, just mindlessly typing out "if err != nil { return }" does not somehow force enlightenment upon the user. The error blocks are so uniform in their banality that future maintainers treat them the same way that error handling is treated in every other language.
The fundamental difference is that each separate error is accounted for rather than 4 lines all being treated as error points so you never know which could throw.
If you use try-with-resources in Java 7, you don't need to write any exception handling.
The function would be '... throws IOException'. That seems to satisfy the "think of disk/network failure as a very common case" design goal with minimal boilerplate. That brings up a separate discussion on checked vs unchecked exceptions, but I guess we can save that one for comparisons against a language with unchecked exceptions!
I started out thinking this was a little tedious and even came up with a little library [1] to use the panic-recover style handling within a package [2].
However, after a few months of writing code using that model, I find that I don't really like it and prefer the if err != nil model much more.
While this can appear to be a little tedious, I've found it extremely useful once I started checking code coverage in my tests. It makes it very clear which exceptional cases you aren't testing. This in turn makes it obvious how well tested the code is. I know from looking at my code coverage what kind
failures I'm handling properly and which ones I'm delegating to the caller. This allows for better documentation where API users know what failure modes to expect.
I'm on Chrome on Mac and it's not entirely obvious how to navigate these slides. Going to the link only shows me the first 3 slides by scrolling right.
Playing around a bit, I discovered that the arrow keys bring you to the next slides. Perhaps there could be a way to make that more obvious, or provide buttons onscreen.
I've only been using Go for less than a week and my impressions are less than good. I'm still waiting and hoping to understand what it is that people like about Go, but I'm starting to suspect that won't happen.
The biggest problem I have had most so far is when reading other code, finding the meaning amongst all of the error nil checks and the sprawling if conditions that they bring.
Another thing that makes it difficult is the preference for scattering returns everywhere and avoiding `else if`s, I read code structurally so code like this from slide 29 (http://talks.golang.org/2014/readability.slide#29) is really difficult to parse.
func finishStatus(r Result, complete bool) int {
if !complete {
return http.StatusAccepted
}
if stat, ok := r.Object.(*api.Status); ok && stat.Code != 0 {
return stat.Code
}
if r.Created {
return http.StatusCreated
}
return http.StatusOK
}
If you are religious about single-return style --- and I was an observant practitioner of it until recently --- you simply aren't going to like Golang.
For that matter, if you hate C programming --- not in the sense of "C is dangerous and error prone" and more in the sense of "I hate the way I feel when I write C code", you also aren't going to like Golang.
The former issue grinds on me a bit, though I'm coming to appreciate what it does for the reliability of my code versus languages with exceptions. On the other hand, I love writing C code, and that probably makes up for it.
I suspect you're right. I'm going to continue with Go for a little while longer to give it a real chance, there are a few things I like about it such as the dependency management and the lightning fast compilation so there's still a chance that I will grow to like it.
I really hate Golang error handling, but I have to grudgingly acknowledge that it results in code that is more reliable than Ruby or Python (and much more reliable than C). That's a dividend paid by explicitness.
I would not say I've grown to like the error handling, but when I trudge through it now, I do so realizing that there's probably an actual benefit to my program of the annoyance.
The Go standard library is also really, really well designed.
Yes, this is a really bad abstraction that was yanked from its context and made code really hard to follow and understand which will lead to a bug someday.
But we don't have to follow his advise on this and are better off keeping our code sane.
In the original code if out.Close() returns an error, run() will return nil. The revised code sets `err` to the returned error from out.Close(), in case the error from os.Create(*output) is nil.
Simply spoken, the original code will suppress the error from out.Close().
92 comments
[ 3.3 ms ] story [ 164 ms ] threadI'm enjoying using Go for the few small services I'm using it for but it seems that a language which has to constantly fight it's users to reinforce what it considers idiomatic has some core issues.
Yet C programmers use shitloads of macros to make up for C lack of features.
I've grown to prefer it, as exception handling typically boils down to "not my problem" in most code bases. Go forces you to think through each error condition, which is an unusual amount of effort for people who may not be accustomed to it.
The problem is not that Go's error handling requires more thinking or efforts, it's that it is bad, and while that's an improvement from C's terrible error handling it's still nowhere near good enough, let alone good (unless you consider C's error handling to be good enough in the first place).
I must be missing something.. Returning a value in Go for error signaling is the way to signal errors in Go.
Yes? That's the point, I'm comparing it to more modern languages which signal error "the same way", by opposition to languages implementing different high-level methods of error signaling. The clause is there to explicitly point out that I'm not considering or talking about exceptions and conditions-based error signaling.
Maybe it'd have been clearer if I'd written "languages which also use return values"?
You posted a while back that "scope inference (in Python) sucks". Could you expand on that. I know it's totally unrelated to this thread but I was really hoping to understand what you meant.
Thanks!
http://fsharpforfunandprofit.com/posts/recipe-part2/#series-...
There are many cases where this is not true.
One is that it quickly becomes hard to find out where errors really came from. Consider this idiomatic code:
If MyFunc returns an error it's not possible to know where it came from unless you know all about someOtherFuncA and someOtherFuncB and they return different kind of errors. What's missing from the stdlib is a utility that wraps errors to reproduce what's essentially a backtrace. `return wrapError("someOtherFuncA", err)`The other issue is that (error) doesn't tell you what kind of errors are going to be returned. It's often useful to be able to classify errors as to respond to them accordingly. IO errors might be retryable but data-structure errors might be not. In the stdlib they use value comparison as a trick to classify errors combined with documentation of what exactly will be returned.
I've written code with this style of error-handling in Perl 5, and I think it works quite well. I like having "custom" error-handling that fits the style and the purpose of that particular program, instead of trying to to fit a general pattern onto all programs. It also means you can start out with something very crude, and improve it as you go along and learn more about the failure modes etc.
It works fine in big programs, too. If you need more context, you add custom errors that have a context field. If you need to match different types of errors, you can have an error code that you can check against, or you can simply match strings (a lot of Go code does this). What you see as a problem is not really much of a problem in practice for well-structured code.
I don't really have an answer to that. But what i know is that i find the practice of having typed exception in java plus catching the one you want to deal with per block of code much more readable than the serie of if / else that go forces you to do.
Ps : i do write in many different languages, including C and python. But with C you know that you're writing in an prehistoric language so nothing cumbersome really surprises you, and python also has try / catch logic.
With Python, you just wrap it in a try/catch and forget about it. With Go, I already wrote five error checks and I'm sure I haven't caught some condition that will make it crash. I have to say I prefer the exception style, at least in this case.
Python is another language where there is a lot of talk about idiomatic code ('Pythonic').
C++11 and C++14 also have a lot of idiomatic discussion since the community's trying to move to a different style with the latest enhancements.
Not sure if this is a signal of core issues.
Many authors try to unite casual style with technical writing. Russ Olsen does it beautifully.
He never treats you as a child or an idiot. He never lets you feel his master status. He's like the good-natured old-timer who sits next to you with a cup of coffee, teaching you more than you realize.
I wonder what an appropriate adjective could be for idiomatic Go code. Gonic? Gonian?
That said, I've tried channeling Rob and it is an interesting and informative experience ..
You need discussion about what's considered "good" before you can arrive at a relatively common opinion about that.
http://talks.golang.org/2014/readability.slide#17
But then what would the following "if" look like ?
This check is not really needed as the code would fail when a function which accepts scan.Writer would fail (compilation) if ColumnWriter is sent and if it doesn't satisfy the interface. However, this is easier to debug.
Other languages provide constructs to check for interface compliance, Go requires writing dummy code.
This is a debugging trick because Go interfaces use structural subtyping instead of explicitly declaring which types implement an interface. This is a calculated design decision with several trade offs. There is nothing "sad" about it.
Both by design, as well as, by accident (usually it different method semantics).
You don't need to teach me Go, I was into it before 1.0 given its Oberon influences, and like everyone on gonuts that disagrees with the design gets told, I went elsewhere.
By design, you can't.
> You don't need to teach me Go
Then don't state patently false claims. "Other languages provide constructs to check for interface compliance, Go requires writing dummy code." This is false because interface compliance is checked statically. This is orthogonal to whether you can list all interfaces satisfied by a given type.
> I was into it before 1.0 given its Oberon influences, and like everyone on gonuts that disagrees with the design gets told, I went elsewhere.
That's not a feature unique to gonuts. If you disagree with a project's fundamental design and stated goals, then I'm not sure what else you might expect.
For example, I'm not particularly interested in the JVM/Java world due to a myriad of design decisions that they've made. But I don't go around trolling the Internet with near content-free comments and inaccurate statements about their ecosystem.
That question doesn't make any sense. I can write a new interface that an existing type supports and then use that type as that interface. That's the awesome power of go interfaces. Using existing types for things their original creators couldn't have thought of.
You are a consultant playing fireman in a Fortune 500 corporation.
Your task, in case you accept it, is to fix a performance problem no one from in-house teams has been able to track down.
The code is developed in three sites, all in separate time zones, with an overall size of 40 developers ideally churning code 8 hours a day.
Now dive in into this code and fix the issue.
It is a fixed price project of one week.
This is an example how code navigation is valuable.
> That's the awesome power of go interfaces.
Like any language that supports structural typing, nothing awesome about it.
https://github.com/dominikh/implements
http://talks.golang.org/2014/readability.slide#11
In this example, have to check error 3 times to write 4 lines of code.
The function would be '... throws IOException'. That seems to satisfy the "think of disk/network failure as a very common case" design goal with minimal boilerplate. That brings up a separate discussion on checked vs unchecked exceptions, but I guess we can save that one for comparisons against a language with unchecked exceptions!
However, after a few months of writing code using that model, I find that I don't really like it and prefer the if err != nil model much more.
While this can appear to be a little tedious, I've found it extremely useful once I started checking code coverage in my tests. It makes it very clear which exceptional cases you aren't testing. This in turn makes it obvious how well tested the code is. I know from looking at my code coverage what kind failures I'm handling properly and which ones I'm delegating to the caller. This allows for better documentation where API users know what failure modes to expect.
[1] https://godoc.org/github.com/surullabs/fault [2] https://golang.org/doc/effective_go.html#recover
Playing around a bit, I discovered that the arrow keys bring you to the next slides. Perhaps there could be a way to make that more obvious, or provide buttons onscreen.
The biggest problem I have had most so far is when reading other code, finding the meaning amongst all of the error nil checks and the sprawling if conditions that they bring.
Another thing that makes it difficult is the preference for scattering returns everywhere and avoiding `else if`s, I read code structurally so code like this from slide 29 (http://talks.golang.org/2014/readability.slide#29) is really difficult to parse.
When I see code like this I will assume that it's going to check every condition:
If the first condition matching causes the second, third, nth condition to not be checked then why not make it clear in the code by adding an `else`?The recommended way means that I can't trust my assumption and that I'll have to read the entire function and examine the `//do something`s instead
For that matter, if you hate C programming --- not in the sense of "C is dangerous and error prone" and more in the sense of "I hate the way I feel when I write C code", you also aren't going to like Golang.
The former issue grinds on me a bit, though I'm coming to appreciate what it does for the reliability of my code versus languages with exceptions. On the other hand, I love writing C code, and that probably makes up for it.
I would not say I've grown to like the error handling, but when I trudge through it now, I do so realizing that there's probably an actual benefit to my program of the annoyance.
The Go standard library is also really, really well designed.
http://talks.golang.org/2014/readability.slide#10
Simply spoken, the original code will suppress the error from out.Close().
I think this is tricky in any language.