Go Tooling I Reach For Every Day
A practical tour of the CLI tools and patterns that make up my daily Go development workflow.
🔨 Good tooling is invisible.
This, of course, is a twist on the design axiom that “Good design is invisible”. The core principal is that when “Good design” happens, users don’t distinguish it from their core experience. Yet if “Bad design” happens users notice it and it disrupts their experience.
Similarly, when “Bad tooling” happens contributors won’t use it. It’s hard to expect them to really. Tooling that takes extra work will feel like a chore and a burden. Whereas when “Good tooling” happens, contributors will use it and it will feel natural and effortless.
# The Most Essential CLI Tools
Skipping #golang itself since that’s too obvious — the most important tool is golangci-lint. So we will start there!
# Linters via golangci-lint
It’s a linter tool for #Go code and consolidates many, many linters into one tooling set. All the various linters and formatters are bundled into a single config for your project. It provides a great way to catch common mistakes and improve code quality.
Generally speaking, I don’t have a “default config” for golangci-lint yet. For each project, I tend to scale the intensity of the config with the project needs. In other words, the more serious a project directly impacts how strictly I configure the linters.
# Releasing via goreleaser
The essential tool for releasing Go binaries and packages. Especially useful when publishing to GH releases. The configuration is straightforward and simple to get started with — yet, also allows for a lot of customization.
There is no universal “best” way to configure goreleaser. Each project and I find it’s best to create a config organically as you build a project. This allows you to customize the config to your specific needs.
Some “Pro-tips” for goreleaser:
- Take advantage of
before.hooksto run your own scripts before the release process.- Do you want to
go generateorgo mod tidy? - Maybe you need to call
npmoryarn? - Should the repo be clean of changes after these steps?
- Do you want to
- Use
release.draft=trueto create a draft release.- This does require adding a step to un-draft successful releases.
- This can be useful if you need immutable release artifacts, allowing you to add more artifacts before publishing.
- Helpful to implement “attestation” of release artifacts.
- Always use
release.prerelease=autowhen possible.- This will automatically set the release to a prerelease if the commit is tagged with a prerelease tag.
- This is useful for testing pre-release artifacts.
- This is also useful for testing pre-release artifacts.
- Tag the releases via
gitCLI instead of GitHub UI.- This is required for draft releases to work properly.
- Tag releases with a
vprefix.
# Repository Glue via Makefile1 or Taskfile.yml
At this point I tend to alternate between two tools. They are both great and the choice tends to come down to personal preference.
Or more often established conventions in existing projects.
# Makefile
A Makefile is a great way to automate repetitive tasks. It’s a simple way, given the context, to create a uniform set of targets for all contributors to rely on. They are also very ubiquitous and a well-established tool for software development. So it’s super common for developers to be familiar with it to make onboarding easier.
# Taskfile.yml
The Taskfile.yml is a similar tool based on YAML built on top of #Go. You can technically use it anywhere, but it pairs particularly well with Go projects. You can think of it as a modern kind of Makefile with a declarative YAML syntax.
The main reason I prefer Taskfile.yml over Makefile is that in a Go project you don’t even need to install it.
Thanks to go tool a single version of task can be used for all project Contributors.
This simply requires users calling it via go tool task {target} instead of task {target}.
This is a very small change that makes it easy to adopt.
# Generic Pro Tips
Some “Pro-tips” for either tool:
- Define targets that can be helpful to all contributors.
- Consider using a uniform docker image as a “proxy layer” so all dependency commands/tools are uniform.
- This layer should only be used outside automated CI.
- This layer should be versioned and pinned for reproducibility — if needed.
- If you expect a lot of contributors pick
taskand document calling viago tool
# Non-CLI Tooling is Essential Too
I also want to mention that I use a lot of non-CLI tools too — for instance an IDE is crucial to my workflow. These non-CLI tools are just as important as the CLI tools we rely on but do not replace them.
For instance, tools like:
- GoLand
- Or really any of the JetBrains IDEs
- SourceTree - For a simple Git GUI.
- Obsidian - For notes and keeping track of ideas.
- Mermaid.js Charts - For creating flowcharts and other diagrams in markdown.
- GitHub Actions - For automating my CI/CD pipelines.
- GitLab - For hosting my private Git repositories and CI/CD pipelines.
The most important one is GoLand as it’s a very powerful IDE for #Go — however, VSCode is also a great option too. The core features I rely on in an IDE are: syntax highlighting, auto-completion, and refactoring support.
While I mostly rely on the Git CLI, I also use the SourceTree GUI for managing my Git repositories at times. This can be helpful for visually seeing the Git-tree and starting complicated rebases or other similar tasks.
-
Not the official Makefile website, but a very helpful guide. ↩︎