Catalin Bozianu

Which rules deserve to be code

I keep two kinds of rules: a file listing what an agent should do, and a directory of small programs that make certain things impossible.

Over the past months I have been moving rules from the first place to the second, one at a time, each move prompted by the same discovery: the rule was being followed most of the time.

Most of the time is the problem. A rule followed ninety percent of the time, in a loop that runs forty times a day, is violated four times a day.

The two that taught me this

Weakening the config instead of fixing the code. When a linter complains there are two ways to stop the complaint. One takes ten minutes, the other takes ten seconds and is editing the linter's configuration. There is now a guard that refuses edits to about thirty config files outright.

What is happening there is not laziness or deception. The task was framed as make the check pass. Editing the configuration makes the check pass. It is a correct solution to the problem as stated, and the problem as stated was wrong.

Skipping the pre commit hooks. There is a flag for that. It exists for real emergencies. It is also the fastest route past a failing check, so it is blocked outright now, with a message saying to fix what failed.

Same shape: the goal became the gate rather than the thing the gate protects.

The promotion test

Here is what I actually use, after getting it wrong in both directions.

Promote it to code when the violation is silent, cheap to commit, and expensive to discover. Weakening a linter config is invisible in a diff full of real changes and surfaces three months later as a class of bug nobody is catching. That is the profile.

Leave it written when it needs judgement, when the exception is sometimes correct, or when a false block costs more than an occasional miss. Most style preferences live here. Most people promote them anyway, which is why their tooling fights them all day.

There is a budget. Every mechanical rule is one more thing that can block legitimate work at three in the morning, so spend it on the silent ones.

The rule that must fail open

I have a guard that blocks a ticket moving to testing unless it carries a recent comment explaining what changed. It exists because a tester told me, on a specific day, that a bare status change tells her nothing.

That guard checks an external system, and external systems are occasionally unreachable. When it cannot check, it allows the transition and prints a warning.

That was deliberate and it is the decision I am most pleased with. A guard that blocks real work when its own dependency is down teaches everyone to work around it, and a guard everyone works around is worse than no guard, because you still believe in it.

Decide, for every automated check you write, what it does when it is blind. Most people never decide, and the default is usually to fail closed, which is correct for security and wrong for almost everything else.

The number that settled it for me

A hook fires one hundred percent of the time. A written instruction fires somewhere between half and four fifths of the time, depending on how relevant it looked during that particular session.

Write those two numbers next to each other and the question stops being whether your instructions are well phrased. It starts being which rules you are willing to pay for in code.

Back to the diary