Code Smells

Code Smells

“Look, that's why there's rules, understand? So that you think before you break 'em." – Terry Pratchett, Thief of Time

A Code Smell is a hint that something might be off in the design of a component or system: something doing undue harm; something that might not need to be. Calling something a Code Smell is not an attack; it's simply a sign that a closer look is warranted. Labelling a Code Smell make it easier to identify, discuss, and monitor anti-patterns and ultimately to raise the quality of our codebases.

The following list is what is being introduced to a team that is unfamiliar with the concepts of code smells, and refactoring practices in general. I didn't come up with this list – I've pulled heavily from Martin Fowler and Jeff Atwood. But I've curated the list, and I'll be introducing a new smell and refactoring techniques every week, at least for the next while.

  • Long Method: All other things being equal, a shorter method is easier to read, easier to understand, and easier to troubleshoot. Refactor long methods into smaller methods. How small? Shoot for no longer than what you can see on your screen, without scrolling. Related: Single Responsibility Principle.
  • Large Class: Large classes, like long methods, are difficult to read, understand, and troubleshoot. Does the class contain too many responsibilities? Can the large class be restructured or broken into smaller classes? Re: Single Responsibility Principle, Decomposition.
  • Too Many Parameters: A long list of parameters is hard to read, and makes calling and testing the function complicated. It may indicate that the purpose of the function is ill-conceived and that the code should be refactored so the responsibility is assigned in a more clean-cut way. Limit the number of parameters you need in a given method or use an object to combine the parameters.
  • Conditional Complexity: Watch out for large conditional logic blocks, particularly blocks that tend to grow larger or change significantly over time. Consider alternative object-oriented approaches such as decorator, strategy, or state.
  • Cyclomatic Complexity: Too many branches or loops; this may indicate a function needs to be broken up into smaller functions, or that it has potential for simplification/refactoring.
  • Variable Mutations: Mutations that vary widely enough that refactoring the code becomes increasingly difficult, due to the actual value’s status as unpredictable and hard to reason about.
  • Uncontrolled Side Effects: Side effects of coding that commonly cause runtime exceptions, with unit tests unable to capture the exact cause of the problem, and the antithesis to idempotency (the ability to rerun multiple times without changing the result beyond the initial application).
  • Speculative Generality: Write code to solve today’s problems, and worry about tomorrow’s problems when they actually materialize. Everyone loses in the “what if..” school of design. Related: YAGNI: You (Probably) Aren’t Gonna Need It.
  • Dead Code: Ruthlessly delete code that isn’t being used. That’s why we have source control systems!