Twelve things wrong, and the one I fixed first
I spent a morning reviewing a low-code automation someone else had built. It worked. Forms came in, notifications went out, the right people got alerted. By the end of the review I had twelve things written down that were wrong with it, and the most useful thing I did that day was not hand over the list of twelve.
Working and maintainable are different properties
The flow did what it was asked to do, every time. That is not nothing, and it is worth saying out loud before anything else, because a review that opens with a defect list teaches the author that shipping working software counts for nothing.
What it could not do was survive a change. The week before, one line of message wording had to change, and the author spent the afternoon hunting through the flow updating the same block over and over. That is the review in a sentence: the cost of this flow is not in running it, it is in touching it.
The three that mattered
The same thing, written many times. One notification block (look up recipients, loop, post a message) had been copied into every branch of a switch, nine times over. The message card itself was defined inline in twelve separate places, fifty-odd lines each, differing only in the destination and one conditional block.
It was not laziness. It was the most reasonable thing to do: it worked once, so copy it for the next case. I wrote code exactly like this for years. But it means every future change is nine edits, or twelve, and that is enough that one gets missed and the copies quietly drift apart. They already had.
Names that document nothing. This was the big one. Most of the actions still carried the names the designer generated: Compose_2, Compose_3, Apply_to_each_7, Condition_2. The conventions were mixed too, some underscores, some spaces, some abbreviated, some spelled out.
That is not a cosmetic complaint. At two in the morning, when the failure notification arrives, this is the difference:
Apply_to_each_7 failed
Send_Approver_Reminder_Emails failed
The first one costs you fifteen minutes: open the designer, find the run, click into the action, read its configuration, work out what it was for, and only then start on the actual problem. The second one has already told you. A verb-noun name (Get_, Send_, Filter_, Build_, Check_) with the specific data in it is documentation that cannot go stale, because it is the thing itself.
Error messages with no information in them. There was error handling, which is more than a lot of flows have. But the alert it produced said, in full, that the flow had errors and please address them as soon as possible. That tells the person receiving it nothing they did not already know from the fact that they received it. An alert needs the action that failed, the error code, the message, and enough of the input to reproduce it.
The nine I wrote down and did not raise
Configuration typed directly into the actions that used it, in more than twenty places, so the thing cannot be moved between environments without a find-and-replace. Nothing logged on successful runs, so “did that go out last Tuesday?” is unanswerable. Nested switches four levels deep that a lookup table would flatten. Three independent lookups running one after another that could run in parallel. Loops doing work a select would do more clearly. Filter conditions comparing against string literals that fail silently, returning nothing, if someone edits a label. Dozens of hardcoded field mappings that break whenever the form is regenerated. No validation of the incoming data. No check that the submitter was entitled to submit for the thing they submitted for.
All real. None of them raised that day.
Twelve problems delivered at once does not read as twelve problems. It reads as a verdict on the person. And there is no order to work through it, so the natural response is to fix whatever is cheapest, which is usually the least important one.
The one I fixed first
We took the message card, the one defined inline twelve times, built it once, put the varying parts in variables, and referenced that single definition everywhere.
I picked that one for a specific reason, and it was not that it was the worst. It was that the payoff is visible immediately. The flow visibly shrank. The next wording change took one edit instead of twelve. Nobody had to take my word for anything.
Once you have seen your own work get smaller and easier in front of you, “don’t repeat yourself” stops being a slogan from a course and becomes a thing you now want. Most of what was left on the list runs downstream of that one idea, and afterwards they were much easier conversations, because they were no longer my opinion about how code should look.
Sequencing a review is a real skill and I don’t think it gets talked about much. Finding twelve problems in an unfamiliar automation is an afternoon’s work that any experienced developer can do. Choosing which single one to start with, so that fixing it makes the author want to fix the rest, is the part that is actually hard.
The thing that was not about code
One more note from the same period, which is a leadership failure and not a technical one. Something broke, was spotted, and then sat for a week before it reached me, because the two people who knew agreed between themselves that it was not quite bad enough to escalate.
Nobody did anything wrong there. I had never told them what “bad enough” meant. If the threshold is not written down, people guess it, and a reasonable person guesses higher than you want, because escalating feels like admitting you could not handle it. “Broken in production and not fixed within a day comes to me the same day, and you will never be in trouble for escalating something that turned out to be small” takes one sentence, and I had not said it.
Names, error messages, escalation thresholds. Different problems, same shape: the cost lands on whoever finds it next, at the worst possible time, and it is nearly free to prevent up front.
