Skip to main content
Code Smells

Are Code Smells the Wrong Metric? A Case for Cognitive Complexity

I argue that chasing code smells hurts code review. The real target should be cognitive complexity and maintainability, not isolated smells.

Why I'm Done Chasing Code Smells

Conventional wisdom says hunt for code smells in every review. I've stopped. The term is so broad that it's become a distraction, leading reviewers to nitpick trivialities while the codebase rots in ways that matter more. The evidence from Google's own engineering practices points elsewhere: the primary purpose of review is to ensure overall code health improves over time, not to enforce a checklist of smells (Google Engineering Practices - Standard of Code Review). Chase that goal, and you'll realize that most 'code smells' are red herrings. I'd rather focus on cognitive complexity — a measurable, readable-focused metric that actually predicts whether future engineers can understand the code (SonarQube Server Docs - Understanding measures and metrics).

What Are We Really Smelling For?

Code smells as a concept have been with us since the '90s, but they've never been crisply defined. SonarQube classifies 'code smells' as maintainability issues, lumping together everything from long methods to inconsistent naming (SonarQube (GEANT KB)). That's a grab bag, and it invites reviewers to become pedants. Meanwhile, the metrics that matter — cyclomatic complexity, cognitive complexity, duplication, test coverage — are concrete and measurable. Cognitive complexity, in particular, was designed by SonarQube to quantify how hard it is to understand control flow. Unlike cyclomatic complexity, which merely counts paths, cognitive complexity accounts for nesting and readability. It's a better proxy for the human cost of maintainability. So when I review, I ask: 'Is this function too hard to understand?' not 'Does this method have too many lines?'

The Over-Engineering Trap

One of the most common 'smells' I see flagged is speculative generality — code written to be more generic than needed. Google's own review guidance warns about exactly this, advising developers to solve the problem they know needs solving now, not a problem they speculate might need solving in the future (Google Engineering Practices - What to look for in a code review). Yet I've watched reviewers demand abstractions because 'this would be useful later.' That's backwards. Over-engineering is a code smell, but it's not the kind you can catch with a linter. It requires judgment about the present and future. In one recent review, a developer had introduced a factory pattern to handle three different payment providers when only one existed. The change was 400 lines when it could have been 40. I asked, 'What problem does this solve today?' The author admitted none. We cut it. That's the kind of conversation that improves code health, not a discussion about whether a method is 15 lines too long.

Small Changes, Big Clarity

The most powerful anti-smell tool I know is the small change. Google's research shows that at Google, the median change size is about 24 lines, with more than 35% modifying only a single file (Modern Code Review: A Case Study at Google (ICSE-SEIP '18)). That's not because Google engineers are lazy; it's because small changes are easier to review thoroughly, less likely to introduce bugs, and easier to design well (Google Engineering Practices - Small CLs). When a pull request balloons to 500 lines, smells multiply and reviewers miss the forest for the trees. I've found that insisting on small, focused changes eliminates many smells by construction. If a change is too large to understand in one sitting, it's too large. Google's guidance says 100 lines is a reasonable size and 1000 is too large (Google Engineering Practices - Small CLs). I've internalized that. When I see a 300-line PR that touches 15 files, I ask the author to split it. The resulting reviews are sharper, and the codebase stays cleaner.

Automation Should Smell, Not Humans

Here's my contrarian take: let machines flag the smells you can automate, and save human attention for design and logic. SonarQube and GitLab's Code Quality report will happily list hundreds of potential issues, from code duplications to complexity hotspots (GitLab Docs - Code Quality analysis). But a human reviewer who spends time on those is wasting effort. Google's practice is clear: linters, formatters, and static analysis should handle the mechanical checks so that human review focuses on logic and architecture (Google Engineering Practices (code review)). I've seen teams where reviewers spend 20 minutes arguing over whether a variable name is too vague when a linter could catch it in seconds. That's not code review; it's noise. Instead, I run SonarQube's analysis and look at the maintainability rating. If the technical debt ratio is over 5%, I know there's a problem worth investigating (SonarQube Server Docs - Understanding measures and metrics). But I don't recite the list of issues; I dig into the cognitive complexity hotspots and ask the author to explain the design.

What I Actually Look For in Review

So, what replaces the smell hunt? I review for three things: design, edge cases, and tests. First, I look at the overall design of the change, as Google recommends, before I even glance at functionality (Google Engineering Practices - What to look for in a code review). Is this the right approach? Does it fit the architecture? Second, I think about edge cases and concurrency. Google specifically warns that deadlocks and race conditions are very hard to detect by running code, so I actively look for them (Google Engineering Practices - What to look for in a code review). Third, I ensure tests are present and meaningful. Tests should be added in the same change as production code, and they must be correct, sensible, and useful because tests don't test themselves (Google Engineering Practices - What to look for in a code review). A recent example: a developer changed how we handle session tokens. The code was clean, but there was no test for token expiration. I asked for it. The test caught a subtle bug. That's worth more than any smell discussion.

Takeaway

Stop treating code smells as the enemy. They're a distraction. Instead, measure what matters: cognitive complexity, maintainability rating, and test coverage. Use tools like SonarQube to flag the mechanical issues, but let human reviewers focus on design, edge cases, and tests. When you review, ask 'Is this code healthy?' not 'Does it smell?' Your codebase will improve faster, and you'll enjoy the review process more.

Sources

  • Google Engineering Practices - Standard of Code Review - https://google.github.io/eng-practices/review/reviewer/standard.html
  • Google Engineering Practices - Small CLs - https://google.github.io/eng-practices/review/developer/small-cls.html
  • Google Engineering Practices - What to look for in a code review - https://google.github.io/eng-practices/review/reviewer/looking-for.html
  • Modern Code Review: A Case Study at Google (ICSE-SEIP '18) - https://www.papercache.org/papers/mlsys/system/2026/03/25/modern-code-review-a-case-study-at-google
  • SonarQube Server Docs - Understanding measures and metrics - https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition
  • GitLab Docs - Code Quality analysis - https://docs.gitlab.com/user/project/merge_requests/code_quality/

Share this article:

Comments (0)

No comments yet. Be the first to comment!