Show HN: My notes on Working with Go
github.com
github.com
I clicked one part (enums) and noticed a pretty glaring issue. The way you are doing enums is really _not_ conventional Go code and probably not something I would allow past a PR review. I’m saying this as someone who has built many production systems with Go and taught it to many devs coming from Java.
This guide is more in-line with how Go enums should be designed: https://blog.learngoprogramming.com/golang-const-type-enums-...
So, while I dig a little further, I think it’s fair to warn people to take this with a grain of salt. Use the language yourself and come to your own conclusions as OP did.
I'm sure with the experience you have with Go, you can open a pull request to their notes to fix this issue.
Do you have any thoughts on iota vs strings?
One potential annoyance to me in your design is that there isn't an easily accessible map of enum values and their string representation. That is really useful for testing; most of my enums end up with a .Validate() method that checks whether the underlying value is actually a valid member of the enum. If I have that map, it's trivial to right a test that iterates over enum keys and ensure that a) valid keys pass Validate(), and b) enum.String() returns the corret string. Enums (theoretically) shouldn't be modified at runtime, so it should be perfectly safe and valid to have a single, global instance of that data.
I thought the use of maps to store the enum keys/values was odd, but when I thought about it, maps allow the enum keys to be sparse, which can be valuable.
My biggest issues would be maintainability and, as you mentioned, using global maps. If your enum itself is not a string value, but you NEED a string representation, then it should just implement `Stringer`.
As far as maintainability:
- The naming convention used here is not consistent with how enums are named in Go. For a good example, see the HTTP package, specifically request methods and response status codes [1]. Really, it isn't even an appropriate naming convention for constant values in any language, with `UPPERCASE_UPPERCASE_minor_note`, it looks a bit silly and took longer for me to grok.
- What if I want to add a new enum variant? I seriously have to add it to three different places? If I start touching this map in places outside the original source file, things will get really ugly really fast.
- A bit nit-picky, but: why use an `int32` here?
This file, in my opinion, is over-engineered. Go code should be simple; many people learning Go for the first time hesitate to work that way, unfortunately.
Other common issues I see with people learning Go for the first time:
- Overuse of concurrency. People like to use the `go` statement wherever possible, and create overly-complex APIs with channels. Unless you have multiple events that need to be done in parallel, yet simultaneously needing to communicate between them, you should not use them in your package's public API. Let your package consumers choose when to make that call.
- Package structure, particularly in modules. People like `src` directories, but Go doesn't work that way. Your top priority as someone learning Go should be to dig through Go's standard library itself and see how it is organized.
Agree. As much as possible, always leave it up to your caller on whether or not you want the blocking calls to run concurrently. Over-use of `go` as well as unnecessarily buffered channels are two really common anti-patterns with new Go developers.
And don't get me wrong, they're not breaking or absolutely terrible design choices, it's just that they're choices you typically wouldn't make after spending quality time writing apps with the language.
1st way: the way our Java devs thought Go was “supposed” to be written
2nd way: how Go should probably actually be written
By embracing the simplicity and Go’s opinionatedness the first time around, you’ll save yourself a lot of refactoring headache later.
I don't understand. Isn't this exactly what they did?
The only actual problem I see with this enum implementation is that the module-level value->string map is module public, which should definitely be fixed. Other than that, this looks like a bog-standard enum implementation with slightly odd internal naming conventions.
> I seriously have to add it to three different places?
Well, come on, now. The idiomatic example has two places to edit as well, and the third place here is for functionality the idiomatic example doesn't have (mapping back from string to value). You could always generate this map at runtime in an init() function but I suspect some would complain about that as well.
The code in the section labeled Enums looks like it is straight out of the Protobuf compiler. grpc is referenced later on, so that structure pattern may be related.
TLDR: Before going down that route, you should make sure you really REALLY need them. Someone learning Go for the first time probably doesn't.
I wish I could convince my team to try Rust for our next service
Go indeed has a lousy GC (compared to the JVM, at least.) This is why, in most production Go codebases I've seen, there's heavy reliance on https://golang.org/src/sync/pool.go (or a NIH knocked-together version of it, if the author doesn't realize sync/pool exists.)
Try just switching a few of your most-oft-called constructors to allocate from a pool. It's pretty much the lowest-hanging fruit for Go performance gains.
- the cheat sheet differentiates between 'declaring' and 'declaring and initializing', but in Go there are no uninitialized arrays (or slices) - a lot of times in this file, a slice is created instead of an array (lines 18, 21, 25, 31) - arrays in Go don't really have a capacity (it's always the same as the array's length) - the built-ins append and copy as well as the sort functions don't accept arrays (I assume this is why slices are created?)
This is a little murky and misleading. In Go, you can actually save memory by 'declaring' only. For example, if you do var x []string, and never use x, it never actually uses memory. Whereas x := []string{} does. The JSON encoder treats the two differently, as well.
The data pointer in the slice points to an array-type of size zero. All allocations for objects of size zero return a fixed address in the data section (so there is a distinction between a nil and non-nil object, but a pointer to a zero sized object does not actually take any space).
See line 909: https://golang.org/src/runtime/malloc.go
This still has nothing to do with "declaring" vs "initializing" (Go makes no such distinction; all values are always "initialized"), or direct use of arrays.
This is no distinction for arrays for non-array. E.g. [0]int is a valid type, and it's size is zero. It is treated exactly the same as all other zero-sized types. This is not a special optimization: there are may cases of zero-sized types.
The slice itself is a value type. So foo := []int{} would occupy 24-bytes (data pointer, len, cap) and not necessarily escape to the heap, exactly the same as var foo []string.
I don't know why the numbers are consistently different for the different programs, but the compiler is completely optimizing out the function call in both cases.
Consider the example where you do two prints: https://play.golang.org/p/Y1GUutEQkzx
Here is the compiled object code, which is identical for true and false: main.go:15 0x49b640 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX main.go:15 0x49b649 483b6110 CMPQ 0x10(CX), SP main.go:15 0x49b64d 7624 JBE 0x49b673 main.go:15 0x49b64f 4883ec08 SUBQ $0x8, SP main.go:15 0x49b653 48892c24 MOVQ BP, 0(SP) main.go:15 0x49b657 488d2c24 LEAQ 0(SP), BP main.go:16 0x49b65b 0f1f440000 NOPL 0(AX)(AX*1) main.go:16 0x49b660 e89bfeffff CALL main.printStats(SB) main.go:21 0x49b665 e896feffff CALL main.printStats(SB) main.go:22 0x49b66a 488b2c24 MOVQ 0(SP), BP main.go:22 0x49b66e 4883c408 ADDQ $0x8, SP main.go:22 0x49b672 c3 RET
Notice how there's nothing between the printStats calls?
If anyone knows similar notes for Python & C#, please reply.
Don't get me wrong, I think C# and .Net in general are pretty cool pieces of technology, but I never find myself reaching for them because I'm very often confident that I can get the job done more quickly with Go.
(I don't mean ill to the original author, they present it as such. Just saying that here.)
I think it's more geared towards people who have experience in one language and want to get a quick crash course in another. Most useful for people who learn by example.
would be useful to somebody, seems like a good idea to me.
So I went to the next link down, "Unexported fields from an exported struct". Figured this would be the clever way to access the "message" field. But again I don't get it. It seems it's the same code as the last example.
This either says something about my ability to understand Go code or that maybe a bit more exposition would be useful for at least some of the examples.
So in the end there's really no trick, you still can't access them directly.