Coding Style
Coding style at go-openapi
TL;DR
Let’s be honest: at
go-openapiandgo-swaggerwe’ve never been super-strict on code style and linting.But perhaps now (2025) is the time to adopt a different stance.
Even though our repos have been early adopters of golangci-lint years ago
(we used some other metalinter before), our decade-old codebase is only realigned to new rules from time to time.
Now go-openapi and go-swagger together make up a really large codebase, which is taxing to maintain and keep afloat.
Code quality and the harmonization of rules have thus become things that we need now.
Meta-linter
Universally formatted go code promotes ease of writing, reading, and maintenance.
You should run golangci-lint run before committing your changes.
Many editors have plugins that do that automatically.
We use the
golangci-lintmeta-linter. The configuration lies in.golangci.yml. You may read the linter’s configuration reference for additional reference.
This configuration is essentially the same across all go-openapi projects.
Some projects may require slightly different settings.
Linting rules posture
Thanks to go’s original design, we developers don’t have to waste much time arguing about code figures of style.
However, the number of available linters has been growing to the point that we need to pick a choice.
Our approach: evaluate, don’t consume blindly
As early adopters of golangci-lint (and its predecessors), we’ve watched linting orthodoxy
shift back and forth over the years. Patterns that were idiomatic one year get flagged the next;
rules that seemed reasonable in isolation produce noise at scale. Conversations with maintainers
of other large Go projects confirmed what our own experience taught us:
the default linter set is a starting point, not a prescription.
Our stance is deliberate:
- Start from
default: all, then consciously disable what doesn’t earn its keep. This forces us to evaluate every linter and articulate why we reject it — the disabled list is a design rationale, not technical debt. - Tune thresholds rather than disable when a linter’s principle is sound but its defaults are too aggressive for a mature codebase.
- Require justification for every
//nolintdirective. Each one must carry an inline comment explaining why it’s there. We maintain a full audit indocs/LINTING.md. - Prefer disabling a linter over scattering
//nolintacross the codebase. If a linter produces systematic false positives on patterns we use intentionally, the linter goes — not our code. - Keep the configuration consistent across all
go-openapirepositories. Per-repo divergence is a maintenance tax we don’t want to pay.
The result is a three-layer defense: the .golangci.yml config as a baseline, //nolint with
mandatory justification for the rare exceptions, and docs/LINTING.md as an audit trail.
Contributors should read the disabled list as a set of conscious choices, not gaps to fill.
We enable all linters published by golangci-lint by default, then disable a few ones.
Here are the reasons why they are disabled (update: Feb. 2026, golangci-lint v2.8.0).
As you may see, we agree with the objective of most linters, at least the principle they are supposed to enforce. But all linters do not support fine-grained tuning to tolerate some cases and not some others.
Note on
thelper: the only value we needed from this linter was checking fort.Helper()calls inside genuine test helpers. Unfortunately, it produces persistent false positives on test case factories (functions returningfunc(*testing.T)), which is a pattern we use extensively with ouriter.Seq-based table-driven tests. It also enforces naming conventions we don’t subscribe to. The issue has been reported upstream. We prefer disabling it entirely over maintaining//nolint:thelperdirectives across every test file.
When this is possible, we enable linters with relaxed constraints.
Final note: since we have switched to a forked version of stretchr/testify,
we no longer benefit from the great testifylint linter for tests.