Here's a number that should make you sit up: at Google, where code review is practically a religion, fewer than 25% of changes have more than one reviewer, and over 80% of reviews finish in a single iteration (Modern Code Review: A Case Study at Google). That's how fast review can be when you're not drowning in bugs. But here's the catch: bugs aren't the real enemy. Code smells are. They're the structural problems that don't crash the app today but guarantee pain tomorrow. If you're only reviewing for correctness, you're missing the point.
Imagine you're a backend developer on a small team. You open a pull request that adds a new endpoint to your Python service. It works, tests pass, and you're ready to merge. But your reviewer—let's call him Sam—doesn't just check the logic. He looks at the overall design first, because that's what Google says is the most important thing to cover in a review (Google Engineering Practices - What to look for in a code review). He sees a function with a cyclomatic complexity of 15, a nested if-else monster that's impossible to test. He sees duplicated code from another module that you could have reused. He sees a hardcoded database connection string. He leaves a comment: "This is a code smell. Let's fix it before it becomes a bug."
You're annoyed. You wanted a quick merge. But Sam is right. Here's why: code smells are the root cause of bugs. A complex function is where errors hide. Duplicated code means you fix a bug in one place but miss the other. Hardcoded credentials are a security vulnerability waiting for a compromise. The OWASP Top 10 for 2025 lists Broken Access Control as the #1 risk, and it's often a design flaw, not a runtime error (OWASP Top Ten 2025). You don't see it until someone exploits it.
1. The Cost of Ignoring Smells
Let's put a concrete number on it. SonarQube defines technical debt as the sum of maintainability issue remediation costs, and the default cost to develop one line of code is 30 minutes (SonarQube Server Docs - Understanding measures and metrics). So a function that's 100 lines of tangled logic might carry 50 hours of debt. That's not imaginary—it's a metric. And it compounds. Every time a new developer touches that code, they add more. The technical debt ratio, which is debt divided by the cost to develop the code, gets worse. Under 5% is an A; over 50% is an E (SonarQube (GEANT KB)). You don't want to be an E.
Now, you might say, "But my code is correct." Sure, today. But Google's research shows that the primary purpose of code review is to ensure the overall code health of the codebase improves over time (Google Engineering Practices - Standard of Code Review). Correctness is not enough. A change that adds a bug but cleans up a smell might be a net positive. A change that's correct but adds 200 lines of duplicated garbage is a net negative. That's the standard you should hold.
2. The Smell Hunt: What to Look For
So how do you hunt smells systematically? You don't rely on your eyes alone. You automate. Linters, formatters, static analysis, secret scanners, and dependency scanners should handle style and mechanical checks so human review focuses on logic and architecture (Google Engineering Practices - code review). That's the division of labor. Tools like SonarQube analyze reliability, security, maintainability, coverage, and duplication (SonarQube (GEANT KB)). Security-focused analyzers like Bandit for Python, gosec for Go, and Semgrep can detect dangerous patterns like SQL injection and unsafe deserialization (SonarQube (GEANT KB)). Let the machines find the obvious smells.
But some smells require human judgment. Here's a quick list to keep in mind:
- Complexity: functions with high cyclomatic complexity—each decision point adds a branch, and too many branches means too many paths to test.
- Duplication: the same logic copy-pasted across the codebase, which means fixes don't propagate.
- Hardcoded secrets: credentials in code, which should be caught by secret scanners but sometimes slip through.
- Poor naming: names that don't convey intent, making the code read like a puzzle.
- Missing tests: if a new change isn't tested, it's a smell in itself.
That last one is non-negotiable. Google says tests are expected for all changes (Google Engineering Practices - Small CLs). If you're not writing tests, you're not doing code review.
3. The Small PR Principle
Here's a key insight: smells thrive in large PRs. When you're reviewing 500 lines, you can't see the forest for the trees. Google's data shows that the median change size is about 24 lines, and more than 35% of changes modify only one file (Modern Code Review: A Case Study at Google). That's the sweet spot. Small PRs are reviewed more quickly, more thoroughly, less likely to introduce bugs, easier to merge and roll back, and easier to design well (Google Engineering Practices - Small CLs).
So break your work into small, focused changes. If you're adding a new feature, don't dump it all in one PR. Split it by concern. And keep it under 400 to 500 lines of meaningful change (Google Engineering Practices - code review). That's a hard limit. If you can't fit it, you're doing too much at once.
4. The Field Report: From Smell to Fix
Let's walk through a real scenario. You're reviewing a PR that adds a search endpoint. The author wrote a function that processes user input and queries the database. You spot a potential SQL injection—the input is concatenated into a query string. That's a smell and a security bug. You flag it as blocking. The author fixes it by using a parameterized query. Good. But you also notice the function has a cyclomatic complexity of 12, because it handles multiple search filters with nested ifs. You suggest splitting it into smaller functions. The author does, and now it's testable. The PR grows by 20 lines, but the code quality jumps.
That's the process. You don't just say "LGTM" and move on. You use the 'LGTM with comments' technique when you trust the developer will address minor comments, like sorting imports or removing an unused dependency (Google Engineering Practices - Speed of Code Reviews). But for smells that threaten the codebase, you block.
Quick tip: When you see a smell, don't just say "this is bad." Be specific. Reference the file and line number. Suggest a concrete solution. And if you don't understand something in the code, ask. Google's advice is that your first response to a comment like "I don't understand" is to clarify the code itself, not explain in the review tool, because an explanation in the review tool doesn't help future readers (Google Engineering Practices - Handling reviewer comments).
Takeaway
Code smells are the bugs you haven't found yet. If you ignore them, you're accumulating technical debt that will bite you. Make code review a smell hunt, not just a bug hunt. Automate the mechanical checks, keep your PRs small, and always look at the design first. That's how you keep your codebase healthy and your reviews fast.
Sources
- Google Engineering Practices - Code Review - https://google.github.io/eng-practices/review/
- Google Engineering Practices - What to look for in a code review - https://google.github.io/eng-practices/review/reviewer/looking-for.html
- SonarQube - Understanding measures and metrics - https://docs.sonarsource.com/sonarqube-server/user-guide/code-metrics/metrics-definition
- 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
- OWASP Top Ten 2025 - https://owasp.org/Top10/2025/0x00_2025-Introduction/
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!