zet

Keep Golang commands in cmd, not repo root

After more than 10 years of Go programming there’s an unfortunate practice that I have followed that many others have as well to decide whether to make a git repo into a command with a (think main.go) or a package (think lib.go). Many projects are so big that they will put all the library code at the top level and all the commands in cmd directory. Nothing about Go requires this, it’s just a common practiced further codified by the Cobra CLI tool.

Initially this seems useful because installation if you have Go becomes go install github.com/whatever@latest. In reality, no one installs using this method and it is regularly used incorrectly in favor of the very broken go get ... (which is specifically disabled in modern Go versions because of how horribly it was being abused to “install” commands.

Makefiles are also very popular these days with Go developers (as if they just discovered some shiny new tool). They almost always use them completely horribly wrong, but so many people are doing it that they just accept it, like Christianity. Same is true of Cobra.

And goreleaser—one of the best projects I have ever seen—is the new standard for releasing Go code. Those not using at this point are subjects of very loud laughter from their Go developer peers. Once you use goreleaser you start to realize that the utility of go install github/whatever@latest is pretty much a deprecated, insignificant practice that people should avoid. For starters, you have to have go installed to even do it. And in an era where gh release download exists—that works for anything not just Go—the utility of the go install command drops below zero.

Why all the fuss? Because all that go install stuff has been the primary motivator for me to use main.go in my root git repos. I mean, who wants to type go install github.com/rwxrob/cmd/mycmd@latest. Those four extra keystrokes are unbearable! But that is exactly what my team has chosen to force anyone using the go install approach to use. Even though this isn’t official directory structure, it makes a lot of sense because the idiots doing go get github.com/rwxrob/cmd/mycmd (who will be denied in modern Go versions) will have to type cmd and realize, “oh shit, I’m an idiot.” Because go get has always been for package fetching package libraries and not installing commands despite it’s moronic abuse by noob coders.

The bottom line (okay, several lines): never create a main.go that isn’t in a subdirectory of cmd directory and life is good. Any Go code at the top should be package libraries. And frankly, should probably not combine your library packages with the commands because you are forcing everyone to copy all that command code even if all they want is your top-level entry functions and business logic. This might not see like much of a big deal, until an “innersource” enterprise architecture forces you to vendor all that code and it’s dependencies. At that point you are cursing that repo maintainer for stuff those unused commands into that repo just cuz it was easier for them.