consumer nhé.

còn đây là “kinh thư”.

I was primarily an (Enterprise) Java developer before coming to Go. A couple of observations:
- Each programming ecosystem has its own values. Sometimes the values in one ecosystem are not compatible with the others. We just have to make our peace with it. I wouldn’t write Go-style necessarily in Java, nor would I write Java-style in Go. That said, there are a lot of places where Go and Java style do work together. The Go Proverbs are very handy, especially when coupled with Effective Go. If you’re interested in seeing how Go is used at enterprise scale, this style guide could be useful.
- Use the simplest code to deliver what you need. Java tends to be a bit more prematurely abstraction heavy. Go isn’t, which is not to say that abstractions and Go are incompatible. Classical patterns can be useful in Go (e.g., the strategy pattern), but they are much more minimalistic and emergent in their use and expression. If a classical pattern is getting in the way of writing good Go (e.g., singleton) or adding more noise (e.g., a factory) than value, don’t use it. A very specific remark is that callbacks and observers may not necessarily be 100% idiomatic, for instance, when channels could suffice better for communication. See the Go Proverbs. This is not to say that callbacks or observers shouldn’t be done; it’s that to do them correctly you need to identify and document all invariants up-front around cancellation, error handling, blocking semantics, etc. Really focus on clarity and simplicity first.
- Go values explicitness over implicitness. If data is passed between API, it is done explicitly and in a way you will visually see and not through an opaque mechanism like reflection. One of the most poignant examples of this is the context API, which is used for deadline and cancellation signaling and propagation across distributed it asynchronous code and system boundaries (as well as adapting some asynchronous APIs to become synchronous). Compare how that could be solved in other language ecosystems perhaps by using thread-local storage, which is frankly akin to spooky action at a distance!
- Files and packages are not the same thing. In Java, the atom of design is the class, which corresponds to a file. In Go, it is a package. Packages can consist of multiple files. In Java, the package is a weaker organizing concept. It’ll take a little while to reframe your mindset. A good way to help learn this is to use https://pkg.go.dev/ for documentation consumption. Note how the smallest granual is the package.
- Corollary: the name of a Go package matters infinitely more than its import path. Import paths (from a user’s code consumption and organization perspective) are nearly useless.
- Define interfaces in packages that consume the interface values, not the ones that provide the values.
- Exception design and exception handling discipline in Java can be mapped nearly 1:1 onto errors idiomatically, except for stack traces. Think about the difference between propagating a
new IllegalArgumentException(...)
versus creating you own custom exception type in terms of throwing and handling. The same thing is true for Go with custom error types (e.g.,os.PathError
) and sentinel values (e.g.,io.EOF
) and opaque error values (e.g.,fmt.Errorf
). - Lean into
defer
and resist creating finalizers throughpackage runtime
. Even though Go is garbage collected, APIs that have non-garbage collectable resources (e.g., file handles) should expressly implement aClose
orStop
method for disposal that can be used withdefer
. - You rarely need a mocking framework. In order of increasing complexity: stubs, fakes, spies, and mocks. Climb up that ladder only as situational requirements dictate. With small interfaces, you can quickly write stubs and fakes. You won’t miss mocks with their fragile over-specification problems! 😉
- The language and standard library come with batteries included — meaningfully so. As you learn and aim to do your work, I would encourage you to see if the Go standard library can solve your problems first. You won’t have to worry about it breaking your code, which is a value that parts of the Java ecosystem really care about with never removing APIs, nor becoming unmaintained one day. Controlling for age, the Go standard library has fewer regrets/dubious APIs compred to the Java standard library (at least from what I observe in my day job), where it is common to use a third-party library because something in the Java Platform is fundamentally broken like the time and date abstractions. Keep in mind that some third-party packages were made by enterprising developers who just came to Go from another ecosystem and wanted to replicate something they were used to. Some of their APIs and principles are not super idiomatic. Explore, but use a careful eye. Outside of program composition, which there’s no way the standard library can demonstrate, the standard library is very instructive on good patterns.
One thing that I would recommend continuing from the Java world in Go:
Java has the proverb that constructors should do little work. Go doesn’t have constructors per se(, but it can have lowercase-f factories and capital-f factories (as in the factory pattern)), but natively allowing a caller to pass useful dependencies is a good thing, especially when it comes to testing.