When in Go, do as Gophers do
talks.golang.org
talks.golang.org
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
}When I see code like this I will assume that it's going to check every condition:
if !complete {
// do something
}
if stat, ok := r.Object.(*api.Status); ok && stat.Code != 0 {
// do something
}
if r.Created {
// do something
}
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.
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.
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?
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).
http://fsharpforfunandprofit.com/posts/recipe-part2/#series-...
There are many cases where this is not true.
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!
One is that it quickly becomes hard to find out where errors really came from. Consider this idiomatic code:
func MyFunc(input int) (output int, err error) {
if output, err = someOtherFuncA(input); err != nil {
return
}
if output, err = someOtherFuncB(output); err != nil {
return
}
// ...
}
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.
if err != nil {
continue
}Yet C programmers use shitloads of macros to make up for C lack of features.
You need discussion about what's considered "good" before you can arrive at a relatively common opinion about that.
That said, I've tried channeling Rob and it is an interesting and informative experience ..
http://talks.golang.org/2014/readability.slide#11
In this example, have to check error 3 times to write 4 lines of code.
> 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.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
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.
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.
Simply spoken, the original code will suppress the error from out.Close().
I think this is tricky in any language.