Who This Is For (and the Misconception)
You believe code smells are merely a style preference—something you can wave off with a “we’ll refactor later.” That’s wrong. A code smell is a surface symptom that often points to a deeper design or maintainability problem, and if you let them accumulate, your codebase turns into a swamp. This walkthrough is for the reviewer who wants to be more than a human linter—who wants to catch the smells that actually hurt your team’s ability to ship. You’re going to learn a practical, step-by-step approach to hunting code smells in review, using concrete numbers and real-world examples. No fluff.
1. Define “Code Smell” for Your Team (and Don’t Be Vague)
Before you can hunt smells, you need a working definition. A code smell is any characteristic in your code that suggests a deeper problem—usually related to maintainability or readability. It’s not a bug, but it’s a red flag. SonarQube, a widely used static analysis platform, explicitly categorizes maintainability issues as “code smells” and tracks them separately from bugs and vulnerabilities (SonarQube, GEANT KB). That’s your cue: treat code smells as a real category, not a random gripe.
But don’t just rely on a tool’s definition. Sit down with your team and agree on what smells you care about. For instance, you might decide that any method with cyclomatic complexity above 10 is a smell worth discussing. SonarQube defines cyclomatic complexity as one plus the number of conditional branches—so a method with 10 if-else paths has a complexity of 11 (SonarQube Docs – Metrics Definition). That’s a concrete, reviewable threshold. If your team doesn’t have a shared language, you’ll end up debating taste instead of tradeoffs.
2. Use Automation to Find the Smells That Are Obvious
You’re a human reviewer, so don’t waste your brainpower on what a machine can catch. Linters and static analyzers are perfect for mechanical smells: duplicated code, overly long functions, or unused variables. SonarQube gives you metrics like code duplication and maintainability index, and its built-in quality gate even has a condition that duplication in new code must be ≤ 3.0% (SonarQube Docs – Quality Gates). That’s a hard number you can enforce without arguing.
For security-specific smells, use dedicated tools: Bandit for Python, gosec for Go, Semgrep for anything. gosec, for instance, scans Go’s AST and SSA representation and can detect taint issues like SQL injection and path traversal (gosec README). The point is: if a tool can flag it, let the tool flag it. Then you, the reviewer, can spend your time on the smells that require judgment—like “this class is doing way too much” or “this abstraction hides a bug.”
Here’s a quick
- run your linter and static analysis before you even open the PR
- check the SonarQube quality gate status
- review only what the tool can’t tell you
That’s your automation baseline. If you’re still manually pointing out “you forgot a semicolon,” you’re doing the machine’s job.
3. Look for Design-Level Smells Before You Nitpick
Now that the mechanical smells are handled, your real job begins. Google’s code review guide says the most important thing to cover is the overall design of the change—before you even look at functionality, complexity, or naming (Google Engineering Practices – What to Look For). So ask: does this code fit the existing architecture? Is it over-engineered or needlessly complex? Does it introduce a new pattern that doesn’t belong?
A classic design smell is the “god object” or a function that does everything. When you see a method that’s 200 lines long, that’s a smell, regardless of whether it works. Google’s research found that the median change size at Google is about 24 lines—more than 10% of changes modify only one line (Modern Code Review: A Case Study at Google). That’s a clue: if your PR is 400 lines, it’s probably too big, and the smells are hiding in the bulk. Google suggests that a 200-line change might be okay if it’s in one file, but spread across 50 files it’s usually too large (Google Engineering Practices – Small CLs). So when you see a massive diff, your first review comment should be “why is this so big?” rather than a line-by-line nitpick.
Warning: If you skip the design review and jump straight to style, you’ll approve a change that looks clean but is architecturally rotten. You’ll be the reviewer who missed the big picture.
4. Use Concrete Metrics to Justify Your Smell Findings
When you point out a smell, don’t just say “this is complex.” Give a number. SonarQube’s maintainability rating scale is a great reference: a technical debt ratio under 5% is rated A, and over 50% is rated E (SonarQube Docs – Metrics Definition). If a new file pushes your tech debt ratio from 4% to 7%, that’s a concrete regression you can flag. Similarly, if you see a function that’s hitting a cyclomatic complexity of 15, you can say “this is getting hard to test—let’s split it.”
Let me give you a real scenario. You’re reviewing a Python function that parses user input. It has a series of if-elif checks for different input formats, plus a couple of try-except blocks. You run Bandit, and it doesn’t flag anything. But you manually count the branches: 8 ifs, 2 try-excepts, and a loop—that’s a cyclomatic complexity of 11 (SonarQube’s formula: 1 + number of conditional branches). You can tell the author: “This has complexity 11, which is above our team’s threshold of 10. Let’s extract the format parsing into a helper.” That’s a smell you caught with judgment, but you backed it with a metric.
And don’t forget security smells. OWASP’s Top 10 for 2025 lists Broken Access Control as the #1 risk, with SSRF folded into that category (OWASP Top 10). If you see a function that fetches a URL based on user input, that’s a smell that could become a vulnerability. gosec can flag taint paths for SSRF, but a human reviewer should also ask “is this input validated?” (gosec README). That’s the kind of smell that matters.
5. Write Comments That Push for Change, Not Just Acknowledge
When you find a smell, your comment should be specific and actionable. Google’s advice is to label comments by severity—nit, suggestion, blocking—and to offer solutions, not just problems (Google Engineering Practices – Code Review). For example, instead of “this code is messy,” write: “This function has a cyclomatic complexity of 12 (blocking). Consider extracting the validation logic into a separate method, like `is_valid_input()`. That would make it easier to test and reduce the complexity.”
Also, use the “LGTM with comments” technique when appropriate. Google says you can approve a change while leaving minor comments that you’re confident the author will address—like sorting imports or fixing a typo (Google Engineering Practices – Speed of Code Reviews). But for a structural smell, don’t give LGTM. Mark it as blocking and explain why. If you let a smell slide with a “suggestion” that never gets addressed, you’re teaching the team that smells are optional.
One more thing: if a reviewer (or you) says “I don’t understand this code,” Google advises that the author should clarify the code itself, not just explain in a comment (Google Engineering Practices – Handling Reviewer Comments). Because if the code is confusing, that’s a smell—it means the logic isn’t self-evident.
6. Set Up Guardrails So Smells Don’t Slip Through
Finally, don’t rely on individual vigilance alone. Use your platform’s features to enforce a baseline. On GitHub, you can require a specific number of approving reviews and require reviews from code owners (GitHub Docs – Protected Branches). On GitLab, Premium and Ultimate let you enforce required approvals from specified groups, and you can even require security team approval for potential vulnerabilities (GitLab Docs – Merge Request Approvals). These aren’t directly about smells, but they create a process where someone must actually look at the code.
More importantly, configure your quality gate to block merges when the new code has serious smells. SonarQube’s built-in “Sonar way” gate includes conditions like no new issues and a coverage requirement of ≥80% on new code (SonarQube Docs – Quality Gates). If you set your gate to fail on a new code duplication rate above 3%, you’ll force authors to refactor before merging. That’s automation working for you.
But remember: the gate can’t catch design-level smells. That’s your job. So make sure your review process has a human step that asks “is this code healthy?”—not just “does it pass CI?”
Quick tip: The single most effective thing you can do is keep your PRs small. Google’s research shows that small changes are reviewed more thoroughly and have fewer bugs (Google Engineering Practices – Small CLs). Smells are easier to spot in a 100-line diff than a 500-line monster.
The Most Important Thing to Remember
Code smells are not a style preference—they’re a leading indicator of future pain. If you let them slide, you’ll accumulate technical debt that makes every future change slower. So be blunt: when you see a smell, name it, give a concrete reason, and set a hard rule to fix it now. Your future self—and your team—will thank you.
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
- Google Engineering Practices - Small CLs - https://google.github.io/eng-practices/review/developer/small-cls.html
- SonarQube Docs - Metrics Definition - https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition
- SonarQube Docs - 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
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!