Keeping your Go modules compatible
blog.golang.org
blog.golang.org
I like this pattern, and I have used it. The issue I have is when I look at the generated godoc, the original interface is in the signature. I don't know the function accepts the new interface as well without either digging into the code or a comment for the function.
> There is one subtle way a new field can break user code unexpectedly. If all the field types in a struct are comparable—meaning values of those types can be compared with == and != and used as a map key—then the overall struct type is comparable too. In this case, adding a new field of uncomparable type will make the overall struct type non-comparable, breaking any code that compares values of that struct type.
From experience, this is non-trivial to avoid in a long lived codebase.
It’s telling that 99% of the codegen tools used in Go, e.g. protobuf, thrift, Gorm, etc. all output an isEqual method of some kind.
It's ironic that golang was supposedly designed for "programming in the large", but it's just a very bad experience for non-trivial code bases.
- Generics (lets see if flyweight design actually makes it)
- package names that don't depend on source locations
- binary packages for proper encapsulation and even faster builds
- both points together allow for components eco-system to thrive
- more knobs to turn on when performance matters
- a proper graphical debugging experience
- Type safe enumerations - Generics (lets see if flyweight design actually makes it)
I'll give you both those. Generics would be the killer feature for me.
- package names that don't depend on source locations
Java package structure mirrors the file system, no?
- binary packages for proper encapsulation and even faster builds
Go package binaries are cached after build. In any case, build speed is rarely an issue for Go.
- both points together allow for components eco-system to thrive
One of the most impressive thing about Go IMO is the speed at which the ecosystem has grown and is growing.
- more knobs to turn on when performance matters
I have found it takes a lot longer to reach the need for performance knobs in Go than in Java.
- a proper graphical debugging experience
Not sure what you mean by "proper" and why it has to be graphical, but VSCode + Golang extension gives me enough insight into what's going on under the covers at debug time, equivalent to eg Eclipse + Java debugger.
Just for the record, I love Java and have been programming in it for over 20 years. It has many great features and the ecosystem is unsurpassed.
Go is a much more practical language though IMHO. The time to value is much shorter. Setting up a module project is one command. Writing & running tests, complete with coverage and performance, is supported out of the box by the basic "go" command. The standard lib is broad and deep. And as mentioned the ecosystem is growing fast.
No, it mirrors the directory structure, from import location, without any reference to DNS servers or source code repositories.
All in all Go is a Java 1.0, with all plus and minus that it entails.
The big difference is you have to place the code in the $GOPATH with the right directory structure and disable mod (GO111MODULE=off)
it might well be something tacit that golangers learn from each other directly? or otherwise it's so obvious and simple and I just haven't realized?
it feels like it's the kind of thing that just clicks one day possibly after a few years
On a day to day operation, you need to know 2/3 commands that's it.
- go mod init
- go mod tidy
- go get xxx
The current first Google result when looking for "go modules" is https://blog.golang.org/using-go-modules which is a very verbose post starting with "This post is part 1 in a series of 5 parts". Not very engaging.
In one year I've accumulated a few snippets that I use regularly and get used to `go mod` but it was painful at the beginning. I wish I could have contributed to this topic but Go is most of the time a language that only requires to read pkg.go.dev and the main blog posts to know what's next and this Go modules workflow is, like gopls, something that doesn't currently have a good place to be documented AFAIK.
Edit: Thaxll's comment gives a very nice URL to learn about Go modules, still too much for a beginner IMHO but it has a Q&A part that goes very deep.
A little while ago we were hitting yet another issue with dependencies related to version pinning at work. I tried to divine what the disparate versions mean, and came up with this flow chart from a legalistic reading of stack overflow issues [1] and the go modules doc [2]:
has the package opted into go modules (i.e. is there a go.mod?)
yes
does the repository use semver?
yes
is the major version <= 1?
yes
=> normal versioning, e.g. github.com/stretchr/testify v1.3.0
no
=> when importing, we need add /vN at the end, e.g., import "github.com/my/mod/v4", and in go.mod it will behave like github.com/my/mod/v4 v4.1.0
no
=> pseudo-versioning will be used, e.g. github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181
no
does the repository use semver?
yes
is the major version <= 1?
yes
=> pseudo-versioning will be used, e.g. github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181
no
=> version will be marked as incompatible, e.g. github.com/zeromq/goczmq v4.1.0+incompatible
no
=> ???
I still don't know if this is right. But that's not really the point. I think the go authors painted themselves into a corner with such an anemic initial release. Now that they're trying to address shortcomings, the complexity is blowing up to handle the very large/diverse ecosystem. It reminds me a lot of java around 1.4 when they added in generics. In the long-run it was the right move, but man was it painful.1: https://stackoverflow.com/a/57372286 2: https://github.com/golang/go/wiki/Modules
I'll also add that fetching a golang package was complicated. As I remember, GitHub seemed to be special cased. Other URLs involved an HTTP request with a ?go-get=1 parameter, and then parsing the returned HTML for certain meta tags¹. (I thought only Python was guilty of that sin!) Some URLs I ended up just re-writing, because gopkg.in packages effectively redirect to GitHub in ways that, if abandoned, allow someone else to intercept[1].
And none of this is documented anywhere particularly coherent. Bits here and there, maybe.
(Why not use go itself? Because this platform's package manager wanted to verify that the software was being built from the correct source, by hashing the inputs. It also, then, fetched those inputs, so that there wasn't some form of bait & switch, and there is no network at build time to further prevent silliness. I considered vendoring, but it isn't clear to me how vendoring works with go modules, since it doesn't seem to take into account the module version? / the path layout only allows for a single version.)
[1]: https://github.com/go-fsnotify/fsnotify
¹some servers didn't even return the right Content-Type, either, but perhaps that's an implementation bug. But I didn't have a spec to know.
go team dropped the ball on this. i feel a sense of non-consensus among themselves on what modules should have been. and it's frustrating that there is no definite documentation either.
maybe one day...
We now have:
- magic file names
- magic directory names
- magic comments
- magic env vars
- API design requires gymnastics and juggling since recursive imports are disallowed
- A ton of useful code in the standard library remains unlayered and unexposed for no good reason (pprof, for example)
- Requiring full paths rather than relative imports complicates everything when you're fetching it from a different source location (replace directives and magic ENV vars).
- Slice implementation means that you need to do double pointer storage to keep track of the same slice from multiple places
- Creating arrays with a dynamically defined length is horribly overcomplicated
- Their policy on warnings and instead calling every little thing an error is terrible and wastes a ton of developer time.
- Lack of runtime const value support is just sloppy.
- Why do go source files even need a package declaration? It serves no useful purpose since the compiler already knows enough to spit out an error if you get it wrong.
- Refactoring across packages is nearly impossible to automate because there's no way to declare adherence to an interface.
- No importing of test code from other test code (which means that the import code in the compiler has increased complexity to enforce this silly rule).
- Stack overflows are STILL impossible to track down (after a DECADE!) because it only prints the top of the stack, which is useless in such a case.
- Linting is too opinionated and is all-or-nothing.
------------------
I could go on. I really wanted to like go, and many parts of it do actually feel nice. But even after a decade it's a huge mess. I've spent more time writing workarounds for go than for any other language in my 25 year career. Hell, I even modified the damn compiler out of frustration! [1]
[1] https://github.com/kstenerud/go#the-go-programming-language
I know about one use for it: package mypackage_test allows you to exclude test code from builds. I agree with the rest of your points though :)
For the record, I'm indifferent on the chosen module solution being right or wrong. My ideal module tool is something like npm, but it shows me the impact: diff the module changes, look at my code, and tell me what's impacted by the upgrade. Giving developers more information when upgrading libraries will go a long way towards making "update your libraries" a regular housekeeping task.
Nothing of this has to do with or would be better with generics.
There are millions of ways to break users with an update that machines can't detect. For example:
Old version:
func Lt(a, b YourType) bool { return a < b }
New version:
func Lt(a, b YourType) bool { return a >= b }
This is not specific to Go.