Useful Techniques in Go
arslan.io
arslan.io
In a very large code base, not using tagged literals will just break anything. Not sure you want it (for the case of extending). Removing will already break it so no problems there.
(Disclaimer: I wrote the blog post)
Say I have 3 members that must be set, and 5 optional members. Go's solution is to make the 3 critical members private so that someone using this struct will probably figure out they should use a construction function. And doing this means replacing all code that creates my struct with calls to a 'New' function. With all this friction, people aren't likely to do the right thing.
At least in C++ with public members I can initialize appropriately in the constructor. Of course littering my code-base with public members would be bad practice in the first place.
As an example, say I have []Card. If I want to sum all cards with a certain color then I could
Sum(cards []Card, color string)
but that is not great. Rather, I could use type Cards []Card
and then name a method (cards *Cards) Sum(color string) int
The benefit is that any []Card can now have Sum called on it without any explicit casting.I've been using this and find it to be clean.
https://github.com/golang/tools/commit/2c5c896732f6bfdcc0360...
Also regarding #6, the author didn't say it, but with iota is really trivial to create power of two constants, so you can combine them: http://play.golang.org/p/rzD3Vl0C4q
For #5, he should have mentioned the stringer tool to maintain the list of strings for you: https://godoc.org/golang.org/x/tools/cmd/stringer
#10 really depends. There are many cases where you want to wrap your map, but there are many other cases (more cases dare I say), where it doesn't really matter because you only use the map (which is embedded in s struct) in very few places (basically some methods of that struct).
The example also fails to synchronise reads.
A more important case that is not mentioned is that if you often initialise once and read many times, without ever writing again, you don't need that kind of wrapper in those scenarios.
And you also don't want that kind of wrapper even if you read and write often, but read more than one value at a time. That hurts performance, but can even limit your available semantics. With an explicit lock you can arrange so that your map presents a consistent snapshot at all times.
Honest question: Why?
But if you use negative values, and you write a positive value, when you see it, you'll know.
Of course this is a very trivial thing, but it helped me many times.
Sometimes it's better to use typed constants with a custom type, then the Go type system will help you and not let you assign things of a different type. Note that I only said sometimes, in many cases it's better to use untyped constants (or use primitive types rather than custom integers), and positive integers because you can calculate the constant corresponds to some value from a delta, or from some other criteria, like here:
https://github.com/golang/go/blob/54789eff385780c54254f822e0...
with the definitions of constants here:
https://github.com/golang/go/blob/54789eff385780c54254f822e0...
Another thing useful when debugging is using constants with non-overlapping peculiar ranges, rather than small integers, then simply by seeing a value in a debugger (or in a print of a value without a String() method), you'll know where that comes from.
for loop := true; loop; {
select {
case <-time.After(time.Second):
fmt.Println("hello")
default:
loop = false
}
}
fmt.Println("ending") type T struct {
Foo string
Bar int
Qux string
}
t := T{"example", 123} // doesn't compile
t := T{Foo: "example", Bar: 123} // OK
I find this behavior somewhat surprising. Contrary to the author's claim, I would expect both to fail to compile.Reading the Go specification, it seems to be a valid behavior.
> An element list that contains keys does not need to have an element for each struct field. Omitted fields get the zero value for that field.[1]
I understand zeroing out the "unused" fields is common in low level programming, but Go's behavior in this specific case feels a bit too implicit for my taste.
I wouldn't - the first example is undefined (is 123 Bar or Qux? I guess technically it should be Bar as without quotes it's an int... but it's not unusual for int's to be cast into strings on assignment) and I would expect it to fail. The second example explicitly states which variable is being assigned.
edit: Thinking more about it, I guess it could go either way, but it's just one of those things that once you do it once you know it.
What I disagree is the "zeroing out" part. I don't want 0 or empty string to be more "special" than ordinary values. Letting the compiler decide what values are suitable if I omit them can be a cause of potential bug.
https://golang.org/ref/spec#The_zero_value
I personally like it this way, and it's better, imho, to have a "set everything not specified to 0" step rather then "leave whatever garbage was there"
However I would say that while default zero is better than garbage value, failing to compile is even better than default zero. (e.g. the compiler refuses to compile `var i int` in the first place) But I guess this is a matter of preference.
The nice thing about this approach, of course, is that they can omit all the complexity of "zero values" from the language.
> What I disagree is the "zeroing out" part. I don't want
> 0 or empty string to be more "special" than ordinary
> values.
Sentinel zero values have been part of Go since day 1, and leveraged to provide some very nice semantics. See e.g. bytes.Buffer. It's a fundamental and useful part of the language.The alternative is not to let you omit them, or to nil them. Both worse.
As I'm more familiar with JS than golang, it seems to me, with the current method, I can do something like...
var inst = new CustomObject(param1, param2, {
option15: 'bar'
,...
});
Where the first two parameters are required, but the last one can be a structure that represents available/optional options as a singular type... This would be much cleaner than supporting every variation of overloads as part of object construction. Let alone where a type can simply be used directly.Is this...
var obj = new CustomObject(...);
...
obj.option15('bar');
The same as var obj = new CustomObject(...).option15('bar');
IE: does calling option15 create a new closure/space that is separate from the original object (more functional separation) or does it mutate the original object?I tend to prefer to limit mutations as much as possible in my code... it really just depends on your needs.
Valid source:
t := T{"example", 123, ""}
or
t := T{"", 123, "example"}
Using some kind of unification of set(String, Int, String) with set("example"::String, 123::Int)If I change a data structure I want the compiler to tell me exactly which parts of the code I should rethink. I do not want it to silently initialize new fields with automatic values, as that may invalidate some of my invariants.
Confusingly, it seems that the author is aware us this issue, as he acknowledges exactly this issue #6, and provided a recommendation for easier debugging of such situations.
If you change the code such that the zero value of that field makes the code behave differently, then yes, it's your responsibility to go find all the places that use the struct and update them. However, thanks to static typing and nice namespaces, that's trivial.
Not sure I agree about #7 though, as I prefer the more explicit and log friendly approach of not returning function call results directly.
#3 - ("T{A:1, B:2} is better than T{1, 2}") - usually yes, and you'd want to write it multi-line for readability etc (see #4); but there are times, when you exactly want to benefit from the fact, that T{1, 2} is verified for completeness. And sometimes it can read better in table tests. (Sometimes. Only sometimes.)
#5 - as others said already, you can be smart and spare yourself some dumb work here and use golang.org/x/tools/cmd/stringer + go generate.
#6 - even better: start from 0, and make sure the 0 value is correct as the default value for unitialized variable/field! E.g.:
const (
Stopped State = iota
Running
Rebooting
Terminated
)
// That said, this is assuming the "Stopped" can also mean "pre-running".
// Otherwise, add a named "Uninitialized" state, or something.
#7 - I believe sometimes yes, sometimes not. Especially in longer functions, for the sake of "no surprises", it might be easier for readers to just keep multiple boring (read: regular) "return 0, err" blocks, and final "return x, nil". Also, it's then slightly easier to add more code to such a function (in growing codebase), and/or refactor it. Still, for cases when this results in a concise one-liner, totally yes!#9 - as Author notes at the end, "This approach has the disadvantage that it pushes out the indentation and makes it harder to read. Again seek always the simplest solution." In my opinion, nice trick to know, but usually a "x := NewContext(...); defer x.Close()" or similar is the standard idiom.
NOTE: Especially for locks, I'd say you won't be adding anything to the block in future (see also: YAGNI). On the contrary, you might actually want to change it to a RWLock at some point, and then modify only some of the uses, and then the func would actually make it more annoying.
#10 - I'd say, only when you need it. If you don't need the lock, just use the map. If you need the lock... usually, I'd think you probably already have some higher level meaning for the "map", so I'd suggest to already wrap it in a proper type name & higher-level interface. "type FooRegistry { ... }; func (r * FooRegistry) Register(...)" etc. (And, actually, probably don't add the Delete() yet, until you really need it.) And probably you already have more complexity at this point that you'll want to nicely encapsulate in those funcs.
That said, all of the above is just my subjective opinion, too.
"#11" - By the way: if you're able to force yourself to use vim, absolutely have a look at the Author's https://github.com/fatih/vim-go plugin. Especially with full oracle support, it's a killer.
6) Makes sense, but starting it with +1 makes it really easy to see if it's initialized or not (if you care about or need the knowledge of external explicit initialization).
11) Thanks akavel :)