Live data from Hacker News

Uber Go Style Guide

github.com

71–80 of 140 posts

Re: Uber Go Style Guide

#71

Something that I don't see style guides addressing, but think they should, is how to cancel work when the caller no longer cares about the answer. (Consider an aborted RPC, or someone pressing the "stop" button in their browser.) A lot of people will do things like: func (s *Server) HandleFoo(ctx context.Context, in *input) status { ch This will block on the channel write even if the context becomes cancelled. Instea…

the way to do is to pass context all the way through, until the other thing you are waiting on uses up no resources.

so in your example it should be done like this: ch and the 'workFor' function should be the one that gets canceled with the context.

Deep down at the very end, you would have select statement with 'Of course the world isn't perfect and not everything takes context right now (even in standard library), so you do have to do some workarounds every once in a while.

Re: Uber Go Style Guide

#72

Something that I don't see style guides addressing, but think they should, is how to cancel work when the caller no longer cares about the answer. (Consider an aborted RPC, or someone pressing the "stop" button in their browser.) A lot of people will do things like: func (s *Server) HandleFoo(ctx context.Context, in *input) status { ch This will block on the channel write even if the context becomes cancelled. Instea…

Having gone from a lot of C# to some Go, I’m use to passing in a CancellationToken to all asynchronous methods. I’ve noticed that there’s a lot of Go libraries missing Context support too .

Re: Uber Go Style Guide

#73

Something that I don't see style guides addressing, but think they should, is how to cancel work when the caller no longer cares about the answer. (Consider an aborted RPC, or someone pressing the "stop" button in their browser.) A lot of people will do things like: func (s *Server) HandleFoo(ctx context.Context, in *input) status { ch This will block on the channel write even if the context becomes cancelled. Instea…

the way to do is to pass context all the way through, until the other thing you are waiting on uses up no resources. so in your example it should be done like this: ch and the 'workFor' function should be the one that gets canceled with the context. Deep down at the very end, you would have select statement with ' Of course the world isn't perfect and not everything takes context right now (even in standard library),…

This is a very trivial example so doesn't dive into all the complexity. There is a lot of nuance here, like whether or not you really want to do an operation in the background in the first place. Background work does means that you lose the ability to apply backpressure to the calling system, and that will cause a lot of problems under load. Even if you do want to do the operation in the background, you still want to provide a (derived) context so that you can link the background work to the initial request (at the very least, for tracing purposes). That is omitted here for simplicity.

Where I was going with all of this was that the linked style guide mentions cases where people create buffered channels presumably because their channel writes start blocking. No buffer will make up for the case where the rate of work requested is higher than the rate at which work can be completed. What people really want in that case is the ability to get out of the channel write and return an error to the downstream system; you don't want to buffer, you want to cancel. There are many ways to accomplish the act of cancelling work, but you do have to watch your channel writes explicitly.

Re: Uber Go Style Guide

#74

I really like the horizontal 'Good/Bad' code comparisons in this guide. I didn't realize how horizontal vs. vertical code comparison affects readability; IMO horizontal is MUCH more readable. Example: https://github.com/uber-go/guide/blob/master/style.md#defer-...

Agreed. This makes it much better parsable!

Although on the phone I initially didn't realize there was horizontal content/scrolling! Thanks for pointing that out! Very neat.

Re: Uber Go Style Guide

#75
post #4

"Copy Slices and Maps at Boundaries. Slices and maps contain pointers to the underlying data so be wary of scenarios when they need to be copied. Keep in mind that users can modify a map or slice you received as an argument if you store a reference to it. Similarly, be wary of user modifications to maps or slices exposing internal state." This could be used as an ad for Rust borrow checker, verbatim. You can't modify…

Yes, that's not good, but it seems like in practice this doesn't come up too often in Go? More typically you're building a new slice containing the results of a query or other computation, and returning it without keeping a reference. Or instead of exposing an internal map, you have a Get method.

This issue comes up with a reused bytes.Buffer.

    var buf bytes.Buffer
    buf.WriteString("foo")
    b := buf.Bytes()
    fmt.Printf("b == %v\n", string(b)) // b == foo
    buf.Reset()
    buf.WriteString("bar")
    fmt.Printf("b == %v\n", string(b)) // b == bar
Yes the documentation for .Bytes() says that "The slice is valid for use only until the next buffer modification" but people don't always read the docs for every method they use, especially if it's a method they've used a bunch before. Having a reusable buffer that you return the .Bytes() value from is very tempting. Bug-free code would either copy the result or not reuse the buffer.

Re: Uber Go Style Guide

#77
post #4

"Copy Slices and Maps at Boundaries. Slices and maps contain pointers to the underlying data so be wary of scenarios when they need to be copied. Keep in mind that users can modify a map or slice you received as an argument if you store a reference to it. Similarly, be wary of user modifications to maps or slices exposing internal state." This could be used as an ad for Rust borrow checker, verbatim. You can't modify…

It is a real need sometimes (many times in fact) in programming. This is one of reasons why Go is so flexible.

You can't have the best of both worlds. You win some and lose some. Rust avoids this but loses flexibility.

Re: Uber Go Style Guide

#78
post #15

No buffered channels, or if you do, you must provide a very strong rationale ;) I still use them quite a bit. Particularly synchronizing large rule sets as bit arrays. Of course you can use sync.Wait instead. But make(chan bool, N) semantics are just more convenient. It's stealth synchronization as a by-product. And hence the warnings about determinism!

There are some situations buffered channels are required: https://go101.org/article/channel-use-cases.html

Re: Uber Go Style Guide

#79

Not an expert, but could someone explain why it says: "Panic/recover is not an error handling strategy. A program must panic only when something irrecoverable happens such as a nil dereference." Why is that any more irrecoverable than anything else? (You can check if it's nil before referencing it, right?)

nil when you’re not expecting it.

panic when there is a bug in your program; ie program not behaving as expected, all bets off.

return err when expected conditions fail: bad data, service down, whatever — handle gracefully.

Re: Uber Go Style Guide

#80
post #76

I am not a Go programmer, but this seems like a really nice guide. Is there anything similar for Python or C++?

Yes, it's great. I know a few folks working on Go backends - I'm going to point this out to them. And I think like you, it raises the question of 'Is there a guide like this for what I'm working in?'

In my early React years, I had a lot of conversations about good vs bad as we got used to working in a declarative component hierarchy rather than the old imperative (tweak that DOM!) way. I still come across projects where people write html + bootstrap translated into a render method rather than using the power that is components.

Post reply on HN