Quality-controlling code
In this chapter we’re going to add new audit and tidy rules to our Makefile to check, test and tidy up our codebase automatically. It’s useful to have rules like these that you can routinely run before you commit changes into your version control system or build any binaries.
The audit rule won’t make any changes to our codebase, but will simply report any problems. In particular it will:
Use the
go mod tidy -diffcommand to check if thego.modandgo.sumfiles are out of date and need to be fixed (which you can do by runninggo mod tidy).Use the
go mod verifycommand to check that the dependencies on your computer (located in your module cache located at$GOPATH/pkg/mod) haven’t been changed since they were downloaded and that they match the cryptographic hashes in yourgo.sumfile. Running this helps ensure that the dependencies being used are the exact ones that you expect.Use the
go vet ./...command to check all.gofiles in the project directory. Thego vettool runs a variety of analyzers which carry out static analysis of your code and warn you about things which might be wrong but won’t be picked up by the compiler — such as unreachable code, unnecessary assignments, and badly-formed build tags.Use the third-party
staticchecktool to carry out some additional static analysis checks.Use the
go test -race -vet=off ./...command to run all tests in the project directory. By default,go testautomatically executes a small subset of thego vetchecks before running any tests, so to avoid duplication we’ll use the-vet=offflag to turn this off. The-raceflag enables Go’s race detector, which can help pick up certain classes of race conditions while tests are running.
In contrast, the tidy rule will actually make changes to the codebase. It will:
Use the
go mod tidycommand to prune any unused dependencies from thego.modandgo.sumfiles, and add any missing dependencies.Use the
go fmt ./...command to format all.gofiles in the project directory, according to the Go standard. This will reformat files ‘in place’ and output the names of any changed files.
If you’re following along, use the go get -tool command to add the latest version of staticcheck as a tool dependency to your go.mod file. Like so:
$ go get -tool honnef.co/go/tools/cmd/staticcheck@latest go: downloading honnef.co/go/tools v0.6.0 go: downloading golang.org/x/tools v0.30.0 go: downloading github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c go: downloading golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 go: downloading golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa go: downloading golang.org/x/mod v0.23.0 go: downloading golang.org/x/sync v0.11.0 go: added github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c go: added golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 go: upgraded golang.org/x/mod v0.17.0 => v0.23.0 go: upgraded golang.org/x/sync v0.10.0 => v0.11.0 go: upgraded golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d => v0.30.0 go: added honnef.co/go/tools v0.6.0
Once that’s finished, you should now be able to execute staticcheck using the go tool command, like so:
$ go tool staticcheck --version staticcheck 2025.1 (0.6.0) $ go tool staticcheck ./...
Now let’s go ahead and create the new tidy and audit rules in our makefile. While we’re at it, let’s also add some comment blocks to help organize and clarify the purpose of our different makefile rules, like so:
include .envrc # ==================================================================================== # # HELPERS # ==================================================================================== # ## help: print this help message .PHONY: help help: @echo 'Usage:' @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /' .PHONY: confirm confirm: @echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ] # ==================================================================================== # # DEVELOPMENT # ==================================================================================== # ## run/api: run the cmd/api application .PHONY: run/api run/api: go run ./cmd/api -db-dsn=${GREENLIGHT_DB_DSN} ## db/psql: connect to the database using psql .PHONY: db/psql db/psql: psql ${GREENLIGHT_DB_DSN} ## db/migrations/new name=$1: create a new database migration .PHONY: db/migrations/new db/migrations/new: @echo 'Creating migration files for ${name}...' migrate create -seq -ext=.sql -dir=./migrations ${name} ## db/migrations/up: apply all up database migrations .PHONY: db/migrations/up db/migrations/up: confirm @echo 'Running up migrations...' migrate -path ./migrations -database ${GREENLIGHT_DB_DSN} up # ==================================================================================== # # QUALITY CONTROL # ==================================================================================== # ## tidy: tidy module dependencies and format all .go files .PHONY: tidy tidy: @echo 'Tidying module dependencies...' go mod tidy @echo 'Formatting .go files...' go fmt ./... ## audit: run quality control checks .PHONY: audit audit: @echo 'Checking module dependencies...' go mod tidy -diff go mod verify @echo 'Vetting code...' go vet ./... go tool staticcheck ./... @echo 'Running tests...' go test -race -vet=off ./...
Now that’s done, all you need to do is type make tidy and make audit to execute these checks. Let’s give it a try.
If you’ve been following along closely, the output should look very similar to this:
$ make tidy Tidying module dependencies... go mod tidy go: downloading github.com/google/go-cmp v0.6.0 Formatting .go files... go fmt ./... $ make audit Checking module dependencies... go mod tidy -diff go mod verify all modules verified Vetting code... go vet ./... go tool staticcheck ./... Running tests... go test -race -vet=off ./... ? greenlight.alexedwards.net/cmd/api [no test files] ? greenlight.alexedwards.net/cmd/examples/cors/preflight [no test files] ? greenlight.alexedwards.net/cmd/examples/cors/simple [no test files] ? greenlight.alexedwards.net/internal/data [no test files] ? greenlight.alexedwards.net/internal/jsonlog [no test files] ? greenlight.alexedwards.net/internal/mailer [no test files] ? greenlight.alexedwards.net/internal/validator [no test files]
That’s looking good. The make tidy command resulted in some additional packages needing to be downloaded, but apart from that, all the checks completed successfully without any problems.
Additional information
Testing
We covered the topic of testing in a lot of detail in the first Let’s Go book, and the same principles apply again here. If you like, feel free to revisit the testing section of Let’s Go and reimplement some of those same patterns in your API. For example, you might like to try:
Creating an end-to-end test for the
GET /v1/healthcheckendpoint to verify that the headers and response body are what you expect.Creating a unit test for the
rateLimit()middleware to confirm that it sends a429 Too Many Requestsresponse after a certain number of requests.Creating an end-to-end integration test, using a test database instance, which confirms that the
authenticate()andrequirePermission()middleware work together correctly to allow or disallow access to specific endpoints.