Reasons, Not VerdictsPart 1 of 3

Cleaner Is Not a Reason

Why design arguments go in circles. "Cleaner" and "decoupled" are verdicts, not reasons, and a reason is a fact that would change your mind if it flipped.

For a long time I could tell when a design was wrong and could not tell you why. I could feel it. Something about the shape of the code was off, the way you can tell a sentence is ungrammatical before you can name the rule it breaks. That feeling is worth something, but not much. It arrives after the code is written, it cannot be explained to a colleague, and it cannot be taught. When someone junior asked me how I had decided, the honest answer was “it seemed cleaner,” and I knew as I said it that I had told them nothing.

This series is about what I found when I tried to replace that feeling with something I could write down. It is in three parts. This one is about why the arguments we have about design so often go nowhere. The second is about what to look at instead. The third is a set of questions to ask about what you find, and a way to tell whether an answer is real.

An argument that goes nowhere

Two engineers are writing a client for an internal HTTP service. Every service in the company will use it. The question on the table is small: when a request fails, should the client retry it?

The first engineer says yes. The whole point of a client library is that callers should not have to think about the network. If every caller has to write its own retry loop, they will write it forty times, forty slightly different ways, and half of them will be wrong. Put it in one place. That is what libraries are for.

The second engineer says no. A client that retries is hiding a failure from the code that has to deal with it. The caller knows what the request means. The client only knows it is bytes. A library that silently repeats a request is making a decision it does not have the information to make. Keep the client honest and let the caller decide.

Both of these are good arguments. Both engineers are experienced. Both would be nodded along to in a design review. And they cannot both be right about the same client, which means at least one of them is being persuasive without being correct, and neither can tell which.

I have been on both sides of this argument. I have won it and lost it, and I noticed at some point that winning had nothing to do with being right. It had to do with who spoke last and who had more seniority in the room. That is a bad way to decide how a system works.

Verdicts in the costume of reasons

Look at the words the argument was made with. Callers should not have to think about the network. Keep the client honest. Put it in one place. Hiding a failure. Each of these sounds like a reason. None of them is one.

Here is the test. A reason is something you could be wrong about. It has a truth value. If I say “this function is called from thirty places,” you can go and count, and either I am right or I am not. If I say “this is cleaner,” what would you count? There is nothing to check, so there is nothing to argue with, so the argument goes in circles, each side restating its verdict a little louder.

The vocabulary of design is full of these. Cleaner. Simpler. More decoupled. More flexible. Scalable. Maintainable. Best practice. Separation of concerns. They are not lies. Usually the person saying them has noticed something real. But the word is a conclusion, and the thing they noticed is the reason, and they have handed you the conclusion and kept the reason to themselves, often without knowing it.

Every one of these words has a question underneath it, and the question is where the argument actually lives.

  • Efficient: in which resource, against what budget? Memory, latency, syscalls, bandwidth? Average or tail?
  • Decoupled: from which change? Name the thing that will change and the file you would not have to edit.
  • Simpler: for whom, doing what? Simpler to write once, or to read for the next ten years, or to test?
  • Flexible: which future do you have in mind, and what does it cost today?
  • Maintainable: which maintenance? Which change, made by whom, how often?

Ask the question underneath and one of two things happens. Either a fact comes out, and now you have something to check, or nothing comes out, and you have learned that the verdict was a preference. Both outcomes are progress. The second one is progress you cannot make any other way.

What a reason looks like

A reason for a design choice is a fact that, if it were different, would make you choose differently.

That is the whole definition, and I want to sit with it because it does a lot of work. It says a reason is a fact, so it can be checked. It says the fact is tied to the choice, so it is not trivia. And it says the tie runs both ways: if the fact flips, the choice flips. That last part is the test. If you claim a reason, you should be able to say what would have to be true for you to pick the other option. If you cannot, you do not have a reason. You have a preference with a fact standing next to it.

A small example, away from retries. Should a timeout be a constant in the code or a value in a configuration file? “Config is more flexible” is a verdict. “Nobody has changed this value since the file was created three years ago” is a fact, and you can check it in the version history in ten seconds. If the fact holds, the constant wins: the config plumbing is code that exists to serve a change that never comes. If someone changed it last month during an incident at two in the morning, the fact has flipped and so has the choice. Same question, opposite answer, and you can say exactly why.

Notice that the flip is not really a yes or no. It is a threshold. Changed zero times, constant. Changed once, probably still a constant. Changed four times by three different people, config. Most reasons are like this: the fact is a quantity, and somewhere along it there is a line where the answer changes. Knowing roughly where the line is turns a decision into a measurement. “Constant, as long as it changes less than about once a year” is a reason with its threshold attached, and it tells the next person exactly when to revisit it.

The discriminator is not in the question

Back to retries. Both engineers are talking about retrying. The client retries, or the client does not. Retrying is the noun in the question, and here is the thing about the noun in the question: both options do it, or do something about it, by construction. It is the one thing they have in common. So it is the last place the deciding fact will be.

This is why the argument feels like a tie. When two options both sound reasonable, it is almost never because they are equally good. It is because the fact that separates them has not been named yet, and the reason it has not been named is that everyone is staring at the verb. The deciding fact is next to the verb. It is in what the retry needs in order to be safe, or in what the caller does after it fails, or in what the other four hundred copies of this client are doing at the same moment. Those are the places to look, and the second part of this series is about how to look there on purpose instead of by luck.

For now, one move. Take the first engineer’s argument, “callers should not have to think about the network,” and ask the question underneath. What is the fact? Something like: the client can handle a failed request at least as well as any caller could. Is that true? Only if the client knows everything the caller knows about the request. Does it? Now that is a question with an answer, and the answer is different for different requests, which is the first hint that “should the client retry” was never one question.

We will get there. The point of this part is smaller. Being stuck between two designs is not a failure, and it is not a sign that the decision does not matter. It is a signal that you are still describing the options in verdicts, and the next thing to do is find a noun.