skip to content

When should a home-grown go vet analyzer become a mandatory check for every team, and what may it forbid?

level: principalimportance: nice to knowfreq 18%

answer

  1. mandatory is spending organisational credit
  2. decidable rule, not a preference
  3. measure the hits before you block
  4. you clean the corpus, not forty teams
  5. an escape hatch and someone who can overrule

basics

~20 s

Only when the rule is decidable from one package's syntax and types, has a real defect behind it, has run non-blocking across every repository with its hits sampled, and ships with a fix and a self-serve suppression. It may forbid mistakes, never taste.

solid answer

~50 s

I make a check mandatory when four things hold. The rule is mechanically decidable — a type predicate, not an approximation of an architectural preference. There is a real defect behind it, ideally an incident, so the check pays for the friction it creates. It has run non-blocking across every repository first, with a sample of its hits read by a human and the existing violations fixed by my team rather than handed out as homework. And it ships with a suppression a team can apply themselves at 5pm, plus a `Doc` string that says what to write instead. It may forbid mistakes: a dropped error from the audit-log package, a client built without the deadline it needs. It may not forbid anything a reviewer could reasonably argue about. If we cannot turn a reported false positive around quickly, it goes back to warning.

go deeper

for a junior

Understand that a check which fails builds affects every engineer, so the rule behind it has to be one nobody would reasonably dispute.

for a middle

Be able to argue for or against a specific rule on evidence: how often it fires, how many hits are real, and how mechanical the fix is.

for a senior

Show the rollout you would run — non-blocking first, sample the hits, clean the existing corpus yourself, then block — and what would make you pull it back.

for a principal

Own the governance: who can overrule the check, what response time you commit to for false positives, what the check costs every build, and what would retire it.

## The asymmetry that decides everything A mandatory analyzer is a change every engineer in the organisation is forced to accept, made by a team none of them report to. The cost of a bad one is not just the false positives: it is that the first team blocked by an unfixable check at the wrong moment learns to route around the platform team, and the next check — perhaps a genuinely valuable one — arrives with that history attached. So the bar is not "is this rule good", it is "is this rule good enough to spend organisational credit on". ## Four gates before a check blocks anything **1. Mechanically decidable.** The rule has to be expressible as a predicate over one package's syntax and types, with a crisp statement of exactly what fires. "This call resolves to this function from this import path and its second argument's type does not implement this interface" is a rule. "Constructors should depend on interfaces" is a preference wearing a rule's clothes, and any check approximating it will be wrong often enough that people stop reading its output. **2. A defect behind it.** The strongest case is an incident: this exact mistake caused an outage, twice. The next strongest is a class of bug that is invisible in review and expensive in production. "It would be tidier" does not clear the bar, because the check is charged to everyone's build time and everyone's attention forever. **3. Measured before it is mandatory.** Run it non-blocking over every repository and read a sample of the hits, and — the step people skip — a sample of the places you expected hits and did not get them. A check that reports nothing across a large corpus is usually broken rather than vindicated. The output of this stage is a false-positive rate you can quote when someone challenges the rollout. **4. A path forward from every hit.** Three things: a `Doc` string that names the correct alternative, ideally a suggested fix so the change is mechanical, and a suppression a team can apply themselves without a review from your team. A check with no escape hatch does not survive contact with a release deadline; it gets disabled wholesale, and then the rule is gone entirely rather than gone in one place. ## Who fixes the existing violations The platform team. Introducing a check that flags four hundred existing call sites and telling forty teams to clean up their own is how a technically correct change becomes a political failure. Either fix the corpus first — which suggested fixes make tractable — or scope the mandatory version to new and changed code and carry the backlog yourself. ## What it may and may not forbid Useful mandatory checks share a shape: they encode a fact about a specific API being used incorrectly. Our audit-log helper's error must not be discarded. This internal client must be constructed with the option that sets its deadline. This struct tag has to name a field our serialiser can encode. Each is falsifiable and each has a mechanical fix. Rules that should stay out: layering and dependency direction, naming, function length, which of two equally valid constructions to prefer, anything that depends on intent the analyzer cannot see. If two competent engineers would argue about a hit, a build failure is the wrong medium for that argument. ## The governance around the check Being the person who can block every build is a role with obligations, and it is worth writing them down before the first incident. - **A response commitment.** If a team reports a false positive and the platform team cannot fix it within an agreed window, the check drops back to non-blocking. That single commitment is what makes teams willing to accept the check at all. - **Someone who can overrule.** An engineering lead whose team the check is materially slowing should be able to escalate, and the outcome should sometimes be that the check loses. A check nobody can overrule stops being reviewed. - **A cost budget.** Every check runs on every package of every build. A suite that adds meaningful time to every developer's cycle is spending far more than it looks like on paper, and that cost belongs in the decision. - **Deletion criteria.** Say up front what would retire the check: the API it guards is gone, the pattern it prevents is now impossible, or its hit rate has been zero for long enough that it is either obsolete or broken — and both of those are reasons to act. ## The recommendation to make in the room Most proposed checks should ship non-blocking and stay there. The ones that graduate are few, backed by an incident, cheap to satisfy, and shipped with the corpus already clean. That is a less exciting answer than a suite of forty mandatory rules, and it is the one that leaves the platform team able to make the next rule mandatory when it really matters.

  • A team says your mandatory check is wrong about their code, on a release day. What do you do?
    Give them a documented suppression they can apply themselves immediately, and take the false positive as your bug. Then decide whether it is a one-off or a class: if the same shape shows up elsewhere, the check goes back to non-blocking until it is fixed. Making a team argue with the platform team before they can merge is how the whole programme loses consent.
  • How do you decide between a mandatory check and a lint that only warns?
    By what the failure costs and how certain the rule is. Mandatory suits a rule with a real defect behind it, a near-zero false-positive rate and a mechanical fix. Everything else warns, and earns promotion by evidence: months of hits that people acted on voluntarily is a much stronger case than an argument in a design document.
  • Your check has reported zero hits organisation-wide for six months. Is that success?
    It is ambiguous, and both readings demand action. Either the pattern is genuinely extinct — in which case retire the check and stop charging every build for it — or the check silently stopped matching, which is the more common explanation. A canary fixture that is expected to report distinguishes the two, and should have been there from the start.
  • How do you keep the marginal cost of the next check low?
    Ship one suite binary with a shared traversal dependency so the tenth check costs a fraction of the first, give every check its own flag so rollout can be staged per check, and keep the fixture discipline uniform. The expensive part is never the code — it is the rollout conversation, so make each new check a small increment of an accepted process rather than a new negotiation.

Making a check mandatory is like adding a lane closure on a road you do not drive: the delay is paid by everyone else, so the pothole had better be real and the detour signed.

saying these in an interview costs you the question

  • Making a check mandatory the day it is written
  • Encoding architectural taste as a build failure
  • Handing existing violations to the teams that inherited them
  • Offering no suppression a team can apply themselves
  • No agreed response time for reported false positives
  • Treating zero hits as proof the check is working