Stop Treating Code Smells Like Bugs
Every code review I've seen lately reads like a bug hunt. Reviewers go after code smells as if they were defects, demanding refactors and blocking merges over a long function or a duplicated block. That's backwards. Code smells aren't bugs. They're signals. And if you treat them like bugs, you'll waste time, irritate your team, and miss the real issues.
Google's own review standard says the primary purpose of code review is to make sure the overall code health of the codebase is improving over time (Google Engineering Practices - Standard of Code Review). That's not the same as eliminating every smell. A smell is a hint that something might be wrong. A bug is proof that something is wrong. You don't fix a hint the same way you fix a proof.
So how do you tell them apart? And more importantly, how do you review code without drowning in smells? Let's compare two common approaches: the static analysis approach and the human review approach. One of them is overrated. The other is underappreciated.
What a Code Smell Actually Is
SonarQube, a major static analysis platform, defines code smells as maintainability issues (SonarQube (GEANT KB)). Cyclomatic complexity, cognitive complexity, code duplication, and technical debt—these are all smells. They measure how hard the code is to read or change, not whether it's broken.
Cyclomatic complexity is a count of the paths through your code: 1 plus the number of conditional branches, with a minimum of 1 per function (SonarQube Server Docs - Understanding measures and metrics). Cognitive complexity is about how hard the control flow is to understand (SonarQube Server Docs - Understanding measures and metrics). Neither of these tells you if your code has a logic error. They tell you if the code is hard to reason about.
That's valuable, but it's not a bug. A high-complexity function might be perfectly correct. A low-complexity one might be riddled with subtle errors. The smell is a risk factor, not a diagnosis.
The Static Analysis Trap
Static analyzers are great at catching certain bugs. Tools like Bandit for Python, gosec for Go, and Semgrep for multiple languages find real security problems: SQL injection, command injection, path traversal, SSRF, unsafe deserialization (gosec - Go Security Checker; Semgrep Docs - Getting started; Bandit (PyCQA) Docs). That's not a smell. That's a vulnerability. You should definitely fix that.
But most static analyzers also flag smells—and they flag them with the same severity as bugs. SonarQube's quality gate, for example, has conditions like "new code test coverage is greater than or equal to 80.0%" and "duplication in the new code is less than or equal to 3.0%" (SonarQube Server Docs - Understanding quality gates). If you fail a build over 3.1% duplication, you're treating a smell like a bug.
I've seen teams block merges because a function had a complexity of 15. That's absurd. The function might be the clearest thing in the codebase. The metric is a hint, not a verdict.
The Human Review Advantage
Human review, on the other hand, can distinguish between a smell that matters and a smell that doesn't. Google's research on code review at Google found that finding bugs is not the primary focus of code review—education, maintaining norms, gatekeeping, and accident prevention are (Modern Code Review: A Case Study at Google (ICSE-SEIP '18)). That's not to say bugs aren't caught. They are. But the main value is in the conversation, not the checklist.
That's why I argue that human review should focus on design and correctness, not on smells. Google's own guidance says the most important thing to cover in a review is the overall design of the change, before functionality, complexity, tests, naming, and comments (Google Engineering Practices - What to look for in a code review). Complexity is listed, but it's lower priority than design.
When you do look at complexity, ask why it's complex. Is it because the problem is complex? Then it's fine. Is it because the developer over-engineered it? Then it's a problem—but a different kind of problem. Google warns reviewers to be vigilant about over-engineering: code made more generic than it needs to be, or functionality added that isn't needed now (Google Engineering Practices - What to look for in a code review). That's a smell, but it's not a bug. It's a judgment call.
Comparing the Two Approaches
| Criterion | Static Analysis (SonarQube, etc.) | Human Review (Google, etc.) |
|---|---|---|
| Primary focus | Catch bugs and enforce metrics | Improve code health and share knowledge |
| Handles smells | Flags all smells equally, often blocks merge | Can judge whether a smell matters |
| Speed | Instant, but can cause false positives | Slower, but more context-aware |
| Best for | Security vulnerabilities and style consistency | Design, logic, and maintainability tradeoffs |
The table makes it clear: static analysis is great for certain things, but it's not a substitute for human judgment. Use it to catch the bugs it's good at catching—security issues and style violations—and let humans handle the rest.
My Recommendation: Smell, Then Ignore
Here's my specific recommendation. Run static analysis on every change. Fix the security vulnerabilities it finds—those are real bugs. Fix the style issues if your team cares about them. But when it comes to code smells, don't fix them unless a human looks at the code and agrees the smell is causing real maintenance pain.
How do you know if a smell is worth fixing? Ask three questions:
- Is this code likely to be changed again soon? If not, a smell is low priority.
- Does the smell make the code harder to understand for a future reader? If yes, fix it.
- Is the smell a symptom of over-engineering? If yes, simplify.
If the answer to all three is no, leave it alone. And here's the thing: your static analyzer will keep flagging it. You have to learn to ignore the noise. That's not easy, but it's necessary.
One more thing: the size of the change matters. Google says 100 lines is usually a reasonable size, and 1000 lines is usually too large (Google Engineering Practices - Small CLs). At Google, the median change size is about 24 lines (Modern Code Review: A Case Study at Google (ICSE-SEIP '18)). If your changes are that small, you won't have many smells to worry about. Small changes are easier to review, and they're less likely to introduce bugs (Google Engineering Practices - Small CLs). So keep your changes small, and you'll naturally reduce the smell problem.
The One Thing to Remember
Code smells are not bugs. They're signals. Review the code, not the metrics. And remember Google's senior review principle: favor approving a change once it definitely improves the overall code health of the system, even if it isn't perfect—because there is no such thing as perfect code, only better code (Google Engineering Practices - Standard of Code Review).
Sources
- Google Engineering Practices (code review) - https://google.github.io/eng-practices/review/
- SonarQube (GEANT KB) - https://kb.pert.geant.net/pages/viewpage.action?pageId=412221495
- 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 (ICSE-SEIP '18) - https://www.papercache.org/papers/mlsys/system/2026/03/25/modern-code-review-a-case-study-at-google
- Google Engineering Practices - Small CLs - https://google.github.io/eng-practices/review/developer/small-cls.html
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!