One of the easiest ways to make a codebase harder to maintain is to solve every problem inline.
I prefer small abstractions that do one job clearly. They are easier to test, easier to replace, and easier to understand when I come back a month later.
For example, formatting user-facing labels often starts as a tiny string operation inside a component. That works for one screen, but it usually spreads. Once the same logic appears in two or three places, I would rather extract it.
A simple example
Instead of repeating string cleanup everywhere, I would move it into a utility:
type ProjectStatus = "draft" | "in_review" | "published";
export function formatStatusLabel(status: ProjectStatus) {
return status
.split("_")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
const label = formatStatusLabel("in_review");
// "In Review"
This is not a complex abstraction, and that is the point. Good abstractions do not need to be clever. They just need to remove duplication and make intent obvious.
When I write code, I usually optimize for three things:
- local clarity
- predictable behavior
- low-friction future changes
That tends to produce software that stays useful longer.
