Clean Code

Refactoring Bad Smells in Code

Code smells aren't bugs. Your program still runs. But something is off. There's friction when you try to understand it, change it, or extend it. The term ...

14 Oct 2023

Refactoring Bad Smells in Code

Code smells aren't bugs. Your program still runs. But something is off. There's friction when you try to understand it, change it, or extend it. The term comes from Kent Beck:

"If it stinks, change it." — Grandma Beck, discussing child-rearing philosophy

A code smell is a surface indicator that usually corresponds to a deeper problem. You can't always point to exactly what's wrong. You just know something feels harder than it should.

Here are the smells I encounter most often — and the ones worth learning to recognize.

Mysterious Name

You open a file. There's a function called proc. A variable called d. A class called Manager.

Good code reads like prose. If you have to puzzle over what a name means, the name is wrong. Rename it. The cost is five seconds. The benefit lasts the lifetime of the codebase.

Duplicated Code

The same logic in two places means bugs get fixed in one and survive in the other. It means changes require finding every copy. It means someone will miss one.

When you see identical code structures in multiple places, unify them. Extract a function, a class, a module. Give it one home.

Long Functions

The best functions are short. Not because short is inherently virtuous, but because short functions tend to do one thing. And functions that do one thing are easy to name, easy to test, and easy to reuse.

If your function scrolls for multiple pages, it's doing too much. Split it.

Long Parameter List

In the early days, we passed everything as parameters to avoid globals. That was the right instinct. But a function with eight parameters is its own kind of pain.

Group related parameters into objects. Use configuration types. The function signature should be readable at a glance.

Global Data

Globals can be modified from anywhere. You can't trace who changed them or when. They create invisible dependencies between distant parts of your code.

Minimize global state. Encapsulate it. Control access through well-defined interfaces.

Mutable Data

You update a value here. Code over there expected the old value. Now you have a bug that only manifests under specific timing conditions.

Favor immutability. When data must change, make the change explicit and visible. Use new objects instead of modifying existing ones.

Divergent Change

When one module changes for multiple unrelated reasons, it's doing too many things. A UserService that changes when the database schema changes AND when the email format changes has two responsibilities. Split them.

Smelling is the first step

Recognizing smells is a skill. Fixing them is refactoring. Neither guarantees perfect code. But code that smells less is code that's easier to change — and that's the whole point.

Don't try to fix every smell at once. Fix the one you're touching right now. Leave the code a little better than you found it. Over time, the compound effect is dramatic.