The best pull request I merged last month deleted 300 lines and added 40. It wasn’t clever. It just noticed that three things were the same thing.
Some habits that keep pointing me in that direction.
Wait for the third case
Two similar blocks of code are a coincidence. Three are a pattern. Abstracting at two almost always produces the wrong shape, because you’re generalising from a sample that can’t tell you which parts vary.
The cost of waiting is a bit of duplication. The cost of guessing early is an abstraction everyone has to work around.
Push conditionals to the edges
A function that takes a flag and branches on it is usually two functions wearing a trench coat:
| Instead of | Prefer |
|---|---|
render(data, { compact: true }) |
renderCompact(data) |
fetchUser(id, withPosts) |
fetchUser(id) + fetchPosts(id) |
The caller almost always knows which case it wants. Making it say so removes a branch from the callee and a question from the reader.
Make the data structure carry the weight
A lot of branching is a data-modelling problem in disguise. If you find yourself
writing the same if (type === ...) chain in four places, the type probably
wants to be a lookup:
const handlers = {
email: sendEmail,
sms: sendSms,
push: sendPush,
} as const;
await handlers[channel](payload);
Four chains become one map. Adding a channel becomes one line, in one place, that the type checker verifies.
Delete before you refactor
Before improving a module, check whether anything still calls it. A surprising share of “hard to refactor” code is code nobody reads any more. Deleting it is free and always correct.
None of this is new. It’s just the short list I actually reach for.
Questions or corrections — I read every email.
Email me