Go’s Features of Last Resort
arp242.net
arp242.net
interface{} is almost always a code smell. I can judge a book by its cover with a grep and line count for interface{} and reflect. If it's more than 20~30 for just about any small-medium size code base I dig deeper and look at why. Usually it is people being lazy and refusing to accept what is my twist on "idiomatic Go": a little bit of copying is better than a whole lot of abstraction[0]. Also devs telling me interface{} and type switches make it polymorphic are the ones who will always complain yet not leave the Go ecosystem. Hopefully generics in 2 solve this for everyone.
panic is great when you really want to crash. There are times to do this. I have only used it once in a batch job runner.
init() is a sin ESPECIALLY when you combine init() with the `.` (dot) imports. Pulling packages around for their magic side effects suck. Also the lack of some library maintainers to acknowledge init() can be poisonous... [1]
Handling large arbitrary JSON objects where I need to operate on their keys but don't need to work with their values by unmarshaling to map[string]interface{}. The alternative would likely be code generation of struct definitions with JSON bindings, which is likely preferable if you already have code generation in your project, but can be an overhead in smaller ones.
It can also be used to create your own syntactic wrappers around API's which consume interface{} such as encoding/json and database/sql:
Unmarshal(r *http.Response, dst interface{}) ([]byte, error)
and BulkInsert(tableName string, columns []string, values [][]interface{}) (time.Duration, error)If you just need the keys, you should unmarshal to `map[string]json.RawMessage` to avoid the overhead of parsing the values into objects only to throw them away without ever using them.
Yes, it was created by behemoths of computer science (but more accurately in this case, dinosaurs), and they clearly have not been writing and debugging large production systems for decades now. That's why the whole thing is clearly missing the lessons learned from modern language design.
Terraform is also written in Go, and even though I refer to it to see how they manage a larger Go codebase, my question is - "so"? This is my opinion from writing Go, I have no problems pointing out what I don't like, even though at Google, I am sure, it would be unforgivable blasphemy.
The path, modules, and error handlings are features to me, not drawbacks.
The problem is, why did it take almost a decade for a modern language, at Google nonetheless, to get a proper dependency management feature?
To me it says that the Go vetting process at Google is seriously constipated, perhaps because people there just say "this does not look right, but who am I to question the decisions of Rob Pike and Ken Thompson?". And then we end up with these language design decisions from the 1970s.
And also, amazingly, error management in Go may have actually been made worse in 1.13: https://www.reddit.com/r/golang/comments/biexq0/go_113_xerro...
type Person {
Name string `protobuf:"name",json:"given_name"`
}Have you seen Protobuf options [1]? They allow for a tag-like-behaviour, but are strongly typed. It's notably used by gogoproto [2] to allow specifying custom field types or names [3] when generating Go stubs.
[1] - https://developers.google.com/protocol-buffers/docs/proto#cu...
Use it like:
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
...
Type myField = 1 [(gogoproto.jsontag) = "my-field"]; type Person struct {
FirstName string `json:"first_name"`
}
And you get json like `{"name": "Alice"}`, you can decode it something like this: var doc struct {
FirstName string `json:"name"`
}
json.Unmarshal(data, &doc)
person := Person(doc)
I have not looked at the assembly so I can't say whether this incurs extra overhead or not.ETA: Also, you already are needing to add code to every encode/decode point for the `map[string]interface{}` handling. :P
This is my goto speed hack when prototyping. It always works. You don't have to worry about deep nesting. And it frees you to design the data model as you prototype. It's also less punishing than reflecting on every node in a list.
And yes, golang / json (and go / node) interactions need to evolve better developer patterns and runtime performance ;)
If "Dot Imports" are frowned upon, it means that your other code using that type is always going to have to refer to it as the stuttering "foo.Foo" rather than "Foo". That's not great.
If you’re working by yourself, go wild and do what you want. On teams, I’m much more likely to need to search through somebody else’s file to understand it or find something.
It also means that additions to foo might conflict with definitions in your package.
The reason this feature exists is to break circular dependencies in unit tests. So in foo_test, you might
import (
. "foo"
"fooutil"
)(I hope people move beyond ctrl-f/grepping, to tooling that has semantic understanding of definitions/references. Go go gopls & GoLand!)
(You can’t find Foo just by looking at syntax at all, you would need to parse both the package where it is used and the package where it is defined. Using normal imports, you can get by with only parsing the call site.)
This is not a good reason to move a type to its own package. Packages are for grouping related functionality.
If the fear is that other types or functions in the same package can access the unexported fields, that's unavoidable, making a new package per type doesn't reduce the risk.
mypkg/internal/foo/foo.go:
type Foo struct { … }
mypkg/mypkg.go:
import "mypkg/internal/foo"
type Foo = foo.Foo
Then, it can be imported from "mypkg` and used as `mypkg.Foo`.Exceptions are generally considered to not be worth the cost
____
Why is exception handling being added in 2 then? Sure, they're calling it check/handle instead of try/except(catch) because Go ought to be hip and different but if it moves like a duck and quacks like a duck...
It's also just a proposal which received a lot of feedback/criticism, and eventually they decided to go with another try() proposal which ended up being abandoned after criticism. I'm not quite sure what the current status is here.
So nothing is "being added" in Go 2 thus far.
I have no problem with the general rule nor a built-in mechanism for breaking them.
Simple example: if I ever have a row lock I wrap the corresponding block in recover to ensure that I never have a deadlock. But I certainly don’t wrap every function in recover...
I've been writing my first production software in go this year, and I must say I'm quite favorable towards its error handling now, ugly as it may look. Even during testing there haven't been any crashes except in the places where you can expect them (like writing x := y.(string) when y isn't a string).
For me, the only real downside in go: nil pointers in combination with the absence of non-nullable types.
def f():
bar()
try:
foo()
catch KeyError as exc:
print(exc)
Calling recover() in a defer applies to the entire function, so you'll need to wrap foo() in an anonymous function. You also need more work to not use "Pokemon exceptions" (gotta catch 'em all) and recover() only KeyErrorThink things like json encoding (entirely réflection based), or writing a set of functions that can operate on something more generic. It’s not scary and it’s not really that slow, you just need to be very particular and choosy in your application of it.
Go would not even be on my list for Python developers looking for performance.
There are much better AOT compiled languages with a feature set similar to Python.
A lot of the features that do exist seem to have some strange quirks. Package/dependency management comes to mind but maybe its just unfamiliar to me.
Fundamental channels and goroutines is really nice though.