Thoughts

The guard was on the wrong noun

A permission check that named one action instead of a capability, and survived four rounds of review because each round fixed the example rather than the category. What SOLID is actually for.

SOLID is five principles and about nine hundred blog posts explaining them with Animal, Dog and Cat. I am not going to do that. I want to describe one real bug, because it is the clearest case I have of what those principles are actually for, and of what it costs when the compiler is not holding the line for you.

The bug

Rustamp can hand a track to the phone next to you. The handshake is signalled by QR codes, so the transfer window opens a camera to read the other device’s reply.

A camera is the one thing in that player that asks the operating system for a permission, so it was guarded: the window will not open a camera from a press the user did not mean. The guard looked like this.

pub fn press_reaches_camera(&self, x: f64, y: f64) -> bool {
    resolve(&self.regions(), x, y, Precision::Coarse)
        .flatten()
        .is_some_and(|action| action == TransferAction::Receive)
}

Read that and it looks careful. It is not. It answers a question about one action while the caller is asking a question about a capability, and those came apart the moment anything else could open a camera.

Three other things could. Send opens one, because the sender has to read the receiver’s answer code. Retry shares the same arm as Send, one line up in the same match. And Fallback, drawn as USE QR, forces the slow transport and opens a camera at the end of the stream, from the draw loop, with no press involved at the moment it happens.

So the guard held for the one button it named and was blind to three others, one of which was the default cell under the gesture that triggered it.

Why four rounds of review did not find it

This is the part worth dwelling on. The defect went through four adversarial review rounds. Each one found a real reachable path. Each one fixed it. Each fix guarded one more action.

Round one guarded the mouse. Round two guarded the finger. Round three added a placement rule so the window would not open under the press. Round four added a second confirming press with a warning line. Every one of those was correct and none of them closed the category, because they were all answering the question the original code had asked: which presses are dangerous?

The right question was: what can open a camera? Nobody asked it for four rounds, including me, because the code did not have a place to put the answer.

The fix, and which letters it is

impl TransferAction {
    /// Every action that can reach `getUserMedia`, directly or after any
    /// number of hops. Exhaustive on purpose: a new variant will not compile
    /// until someone answers for it here.
    pub const fn reaches_camera(self) -> bool {
        match self {
            TransferAction::Receive
            | TransferAction::Send
            | TransferAction::Retry
            | TransferAction::Fallback => true,
            TransferAction::Pick(_)
            | TransferAction::Opus(_)
            | TransferAction::Stop
            | TransferAction::Close => false,
        }
    }
}

There is no wildcard arm. That single absence is the whole design.

Single responsibility, in the sense that actually matters: there is now exactly one place in the program that knows which actions reach a camera, and both the placement rule and the confirmation read it. Before, that knowledge was spread across two call sites that each held a different, wrong copy of it, and copies of a fact drift, silently, because nothing forces them to be compared.

Open for extension, closed for modification, in a form you can feel: adding a ninth variant to TransferAction does not quietly inherit false. It fails to compile, in this function, with the question in front of you. The type system holds the invariant that four humans could not.

That is the version of Open/Closed I find useful. Not “never edit an existing class”, because you will, constantly, but make the places that must be edited announce themselves. A wildcard arm is a promise that future variants are someone else’s problem. Sometimes that is true. For a function whose answer decides whether a camera turns on, it never is.

The other three letters were not involved, which is also worth saying out loud. This was a plain enum and a const fn. No interfaces, no injection, no inversion of anything. The principles paid off here at their smallest scale, in a match statement.

The generalisation I would actually defend

Most guard bugs I have written look like this one. The guard names an instance of the category it meant.

  • if (user.role == 'admin') where the question was may this user do this thing.
  • if (path.endsWith('.jpg')) where the question was is this an image.
  • if (action == Receive) where the question was can this open a camera.

Each one is correct on the day it is written, because on that day the example and the category have the same members. They diverge later, quietly, when someone adds the second member of the category and has no reason to think about a check they have never read.

The cure is not more care. It is to make the category a thing in the code: a method, an enum, a trait, sited where a new member cannot be added without walking past it. That is what SOLID is for. Not tidiness: making the compiler ask the question you will forget to ask.