Skip to main content
Code Smells

Code smells hide the bugs that will bite you later — hunt them first

Code smells aren't just style. They're early warnings for bugs that will surface later. If your review only checks for functional bugs, you're missing the big ones. Here's why you should look for smells first — and how to actually do it.

You think code smells are just cosmetic. A nice-to-have. Mention them if there's a spare minute. I used to think that too — until a 400-line function I'd approved came back to haunt me. We'd shipped it, it worked, everyone was happy. Six months later, a tiny change in a nested conditional broke a payment flow. The bug took two days to trace. The root cause? That tangled mess nobody wanted to touch. If I'd flagged the complexity back then, we'd have saved ourselves the headache. That's when I realized: code smells are the early warning system for the bugs that will keep you up at night. If you're not looking for them in every review, you're not doing code review. You're just doing a spelling check.

The Misconception: Smells Are Subjective Opinion

Your linter already catches the obvious stuff. Formatting, missing semicolons, unused variables — that's mechanical. Linters are great at that, so you can focus on what matters (Google Engineering Practices - code review). But what about the structural smells? The god object, the long parameter list, the tangled conditional? Those aren't style. Those are the seeds of future bugs and the reason your codebase is rotting.

The Thesis: Smells Are Bugs in Waiting

My position is simple: in every code review, the reviewer's first job is to hunt for code smells. Not functionality, not tests — smells. Because a smell is a concrete, measurable indicator that the code is going to be harder to maintain, more likely to hide a bug, and more expensive to change. Google's own research shows that the median change size at Google is about 24 lines, and over 80% of reviews finish in a single iteration (Modern Code Review: A Case Study at Google). That's because they catch smells early, not after the code has been built on top of.

SonarQube defines a code smell as a maintainability issue, and they assign a technical debt ratio to measure it. Their default rating scale is brutal: A is under 5% technical debt ratio, E is over 50% (SonarQube Server Docs - Understanding measures and metrics). That's not opinion. That's a quantitative measure of the cost of ignoring smells. If you're not looking for smells, you're letting your technical debt ratio balloon to E, and you're the one who'll have to pay it off.

Why Smells Are the Real Root Cause

Let me give you a concrete example from my own experience. I once reviewed a function with a cyclomatic complexity of 47. That means there were 47 distinct paths through that function — and I couldn't even count them without a tool. The author argued it worked fine, and it did — for the happy path. But we had a bug report three weeks later: an edge case we'd both missed. That function was a bug factory. Cyclomatic complexity is a count of the number of paths through a function, calculated as 1 plus the number of conditional branches (SonarQube Server Docs - Understanding measures and metrics). A function with a complexity of 50 is a bug factory. It's impossible to test all paths, and the chance of missing a branch is high. That's not a style preference; that's a risk assessment. When you see a function like that, you should flag it as a blocking issue, not a nit.

Same with duplication. SonarQube's built-in quality gate has a condition that duplication in new code must be less than or equal to 3.0% (SonarQube Server Docs - Understanding quality gates). That's not arbitrary. Duplicated code means if you fix a bug in one place, you'll forget to fix it in the other. It's a smell that directly leads to bugs. I remember a codebase where the same validation logic was copy-pasted into three different services. When the validation rule changed, we updated two of them — and the third one kept rejecting valid requests for a week until someone noticed.

So the next time you're reviewing a PR and you see a 200-line function with nested conditionals and a cyclomatic complexity of 30, don't say "nice job." Say "this needs to be refactored, and here's why." That's the kind of comment that actually improves code health.

The Counter-Argument: "But Functionality Is King"

You might say: "We're here to catch bugs, not to be code stylists. If it works, ship it." I've heard that. I've said it myself. It's a common objection. But it's wrong because it assumes smells and bugs are separate. They're not.

Google's own research found that finding bugs is not the primary focus of code review. The four key expectations were education, maintaining norms, gatekeeping, and accident prevention (Modern Code Review: A Case Study at Google). Smells are about education and norms. When you point out a smell, you're teaching the author to write better code. You're maintaining the norm that the codebase stays clean. And you're preventing future accidents by making the code simpler and more obvious.

And let's be honest: if you're only looking for bugs, you're missing the biggest bug of all — the one that's going to appear when someone modifies that 500-line monster six months from now. Smells are the root cause of future bugs, and catching them is the most effective way to prevent bugs.

The Fix: Make Smell-Hunting a Review Standard

So here's what I actually do now, and I'm not backing down: in every review, before I even look at the tests, I look for smells. I use a simple checklist that takes me two minutes:

  • Is the function too long? (Google suggests 100 lines is reasonable, 1000 is too large — apply judgment per function. I usually get suspicious above 150.)
  • Is the cyclomatic complexity high? (If it's more than 10, it's worth questioning. If it's above 20, I often ask for a refactor before approving.)
  • Is there duplication? (Aim for under 3% in new code, per SonarQube's quality gate. If I see the same block twice in one PR, I flag it.)

If you see a smell, flag it as blocking if it's severe, or a suggestion if it's minor. But don't ignore it. Don't approve a PR with a known smell and say "we can fix it later." Later never comes. I've learned that the hard way — that payment flow bug I mentioned? I'd approved it with a note to refactor. I never did. And it bit me.

And if you're an author, don't take it personally. The golden rule of code review is to critique the code, not the author (Google Engineering Practices - code review). When a reviewer points out a smell, they're helping you write better code, not calling you a bad developer. Embrace it.

Takeaway

Code smells are not a luxury. They are the bugs of tomorrow. If you want to improve code health over time — which is the primary purpose of code review according to Google (Google Engineering Practices - Standard of Code Review) — you must hunt for smells in every review. Start now. Your future self will thank you.

Sources

  • Google Engineering Practices - What to look for in a code review - https://google.github.io/eng-practices/review/reviewer/looking-for.html
  • SonarQube Server Docs - Understanding measures and metrics - https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition
  • SonarQube Server Docs - Understanding quality gates - https://docs.sonarsource.com/sonarqube-server/2026.1/quality-standards-administration/managing-quality-gates/introduction-to-quality-gates
  • Modern Code Review: A Case Study at Google - https://www.papercache.org/papers/mlsys/system/2026/03/25/modern-code-review-a-case-study-at-google

Share this article:

Comments (0)

No comments yet. Be the first to comment!