Code Quality Metrics
"You can't improve what you don't measure." Metrics transform subjective code quality discussions into objective data. This article covers the essential metrics for measuring complexity, coupling, cohesion, and coverage - along with practical guidance on setting targets and tracking trends.
π At a Glance
| Aspect | Details |
|---|---|
| Metric Categories | Complexity, Coupling, Cohesion, Coverage |
| Key Tools | SonarQube, JaCoCo, PIT, SpotBugs |
| Primary Insight | Trends matter more than absolutes |
| Setup Time | 2-4 hours for full pipeline |
| Business Impact | 40% reduction in escaped defects |
π― What You'll Learn
After reading this article, you will be able to:
- Calculate cyclomatic and cognitive complexity and understand their thresholds
- Measure coupling with afferent/efferent metrics
- Evaluate cohesion using LCOM variants
- Set up coverage metrics including mutation testing
- Configure SonarQube quality gates for your team
- Interpret trends and take action on degrading metrics
π₯ Production Story: The Quality Gate That Saved Black Friday
An e-commerce company had a tradition: disable all quality checks before major sales events because "we need to ship fast."
Two days before Black Friday 2022, a developer pushed a change that passed unit tests but had cyclomatic complexity of 47 (threshold: 10). The code looked like this:
JAVA(12 lines)CodeLoading syntax highlighter...
On Black Friday, traffic spiked. A rare code path was triggered. The method threw NullPointerException. Orders failed. 23 minutes of downtime during peak traffic.
- Complexity > 15: Block merge
- Coverage < 80% for changed lines: Block merge
- No new critical/blocker issues: Block merge
Next year, same traffic spike, zero incidents. The quality gate caught 47 potentially problematic changes in the weeks leading up to Black Friday.
π§ Mental Model: The Quality Metrics Dashboard
TEXT(32 lines)CodeLoading syntax highlighter...
π¬ Deep Dive
Part 1: Complexity Metrics
Cyclomatic Complexity (McCabe)
CC = E - N + 2P- E = edges in control flow graph
- N = nodes
- P = connected components (usually 1)
- Each
if,else if,while,for,case,catch,&&,||adds 1
JAVA(39 lines)CodeLoading syntax highlighter...
| CC | Rating | Action |
|---|---|---|
| 1-10 | Simple | OK |
| 11-20 | Complex | Review |
| 21-50 | High risk | Refactor |
| 51+ | Untestable | Critical |
Cognitive Complexity (SonarSource)
- Penalizes nesting (nested
if= harder than sequential) - Ignores shorthand structures (ternary operator)
- Rewards language features (early returns)
JAVA(23 lines)CodeLoading syntax highlighter...
| Cognitive | Rating | Action |
|---|---|---|
| 0-10 | Clear | OK |
| 11-15 | Moderate | Review |
| 16-25 | Difficult | Refactor |
| 26+ | Very difficult | Critical |
NPath Complexity
JAVA(14 lines)CodeLoading syntax highlighter...
Part 2: Coupling Metrics
Afferent Coupling (Ca)
TEXT(6 lines)CodeLoading syntax highlighter...
Efferent Coupling (Ce)
TEXT(8 lines)CodeLoading syntax highlighter...
Instability (I)
I = Ce / (Ca + Ce)TEXT(2 lines)CodeLoading syntax highlighter...
TEXT(14 lines)CodeLoading syntax highlighter...
Part 3: Cohesion Metrics
LCOM (Lack of Cohesion of Methods)
- LCOM1: Original, rarely used
- LCOM2: Improved
- LCOM4: Most practical, used by SonarQube
JAVA(28 lines)CodeLoading syntax highlighter...
| LCOM4 | Interpretation |
|---|---|
| 1 | Perfectly cohesive |
| 2-3 | Consider splitting |
| 4+ | Definitely split |
Tight Class Cohesion (TCC)
TCC = (Connected pairs) / (Total pairs)JAVA(13 lines)CodeLoading syntax highlighter...
Part 4: Coverage Metrics
Line Coverage
JAVA(11 lines)CodeLoading syntax highlighter...
Branch Coverage
JAVA(11 lines)CodeLoading syntax highlighter...
Mutation Coverage (Mutation Testing)
- Tool mutates code (e.g., changes
>to>=) - Runs tests
- Tests should fail (mutation killed)
- If tests pass, mutation survived (weak test)
JAVA(15 lines)CodeLoading syntax highlighter...
Part 5: Tool Configuration
SonarQube Quality Gate
YAML(14 lines)CodeLoading syntax highlighter...
TEXT(9 lines)CodeLoading syntax highlighter...
JaCoCo Configuration (Maven)
XML(45 lines)CodeLoading syntax highlighter...
PIT Mutation Testing (Maven)
XML(26 lines)CodeLoading syntax highlighter...
mvn test-compile org.pitest:pitest-maven:mutationCoverageβ οΈ Common Mistakes
Mistake 1: Gaming the Metrics
JAVA(18 lines)CodeLoading syntax highlighter...
Mistake 2: Chasing 100% Coverage
JAVA(19 lines)CodeLoading syntax highlighter...
Mistake 3: Ignoring Trends
TEXT(10 lines)CodeLoading syntax highlighter...
π Debug This: The Coverage Paradox
A developer reports: "We hit 95% code coverage but bugs are still slipping through. Our quality gate passes but production keeps failing. How is this possible?"
JAVA(32 lines)CodeLoading syntax highlighter...
- No assertions - just executing code
- No verification of return values
- No boundary condition testing
- No negative case testing
totalcould be 0 or negative (not checked)- Inventory availability not verified
- Customer validation missing
- Error handling incomplete
- Assertion density - meaningful checks per test
- Mutation testing - would changes break tests?
- Edge case coverage - boundaries, nulls, errors
π» Exercises
Exercise 1: Calculate Cyclomatic Complexity
β Difficulty: Easy | β±οΈ Time: 15 minutes
JAVA(27 lines)CodeLoading syntax highlighter...
TEXT(14 lines)CodeLoading syntax highlighter...
Exercise 2: Identify Coupling Issues
ββ Difficulty: Medium | β±οΈ Time: 20 minutes
TEXT(10 lines)CodeLoading syntax highlighter...
| Component | Ca (Afferent) | Ce (Efferent) | I = Ce/(Ca+Ce) |
|---|---|---|---|
| ComponentA | 0 | 2 (B, C) | 2/2 = 1.0 (unstable) |
| ComponentB | 2 (A, C) | 1 (D) | 1/3 = 0.33 |
| ComponentC | 1 (A) | 2 (B, D) | 2/3 = 0.67 |
| ComponentD | 2 (B, C) | 0 | 0/2 = 0.0 (stable) |
- ComponentD is most stable (I=0) - good for foundational code
- ComponentA is unstable (I=1) - okay for high-level application code
- Consider: Does stability match abstraction level?
Exercise 3: Configure Quality Gate
βββ Difficulty: Medium-Hard | β±οΈ Time: 20 minutes
- Line coverage < 80%
- Branch coverage < 70%
- Cyclomatic complexity > 15 for any method
KOTLIN(33 lines)CodeLoading syntax highlighter...
Exercise 4: Create Metrics Dashboard
βββ Difficulty: Medium-Hard | β±οΈ Time: 25 minutes
- 5 key metrics to display
- Thresholds for each (green/yellow/red)
- Update frequency
TEXT(24 lines)CodeLoading syntax highlighter...
Exercise 5: Analyze Real Codebase
ββββ Difficulty: Hard | β±οΈ Time: 30 minutes
- Current metrics
- Top 3 improvements
- Expected impact
TEXT(20 lines)CodeLoading syntax highlighter...
π€ Senior-Level Interview Questions
Question #1: Which metrics do you prioritize and why?
I prioritize based on defect correlation:
-
Complexity (Cyclomatic/Cognitive) - Strongest correlation with bugs. CC > 20 has 2x bug rate.
-
Coverage trends - Not absolute numbers, but declining coverage signals problems.
-
Coupling (Instability) - High instability in core modules predicts cascade failures.
-
Cohesion (LCOM) - Identifies classes that should be split.
I track trends weekly, not absolute values. A stable 75% coverage is better than fluctuating 80-90%.
Question #2: How do you handle resistance to quality gates?
I address specific concerns:
- Show data: 1 hour in quality gates saves 8 hours in bug fixes
- Start with warnings, graduate to blocks
- Exclude legacy code initially
- Research shows CC > 15 doubles bug probability
- Adjust thresholds based on our defect data
- Team can propose changes with evidence
- Expedited path exists (documented exception)
- Technical debt must be tracked and scheduled
- Block only on critical issues, warn on others
Question #3: What's the difference between line coverage and mutation coverage?
- Can be 100% with zero assertions
- Doesn't verify logic correctness
- Tests must fail when code is mutated
- Verifies tests actually check behavior
Example:
JAVA(10 lines)CodeLoading syntax highlighter...
Question #4: How do you set realistic coverage targets?
I use risk-based differentiation:
| Code Type | Line Target | Branch Target | Rationale |
|---|---|---|---|
| Core domain | 90% | 85% | High risk, high value |
| Services | 80% | 70% | Business logic |
| Controllers | 60% | 50% | Integration tested |
| DTOs/Config | 0% | 0% | No behavior to test |
Process:
- Measure current coverage
- Set incremental goals (+5% per quarter)
- Higher targets for new code
- Track and adjust based on defect correlation
π Summary & Key Takeaways
Metric Quick Reference
| Metric | Target | Tool |
|---|---|---|
| Cyclomatic Complexity | < 10 per method | SonarQube |
| Cognitive Complexity | < 15 per method | SonarQube |
| LCOM4 | = 1 (cohesive) | SonarQube |
| Instability | Depends on layer | JDepend |
| Line Coverage | > 80% | JaCoCo |
| Branch Coverage | > 70% | JaCoCo |
| Mutation Score | > 80% | PIT |
Quality Gate Checklist
- Blocker/Critical issues = 0
- Coverage on new code >= 80%
- Duplicated lines < 3%
- Complexity per method < 15
- Maintainability rating A
Key Principles
- Trends over absolutes - Watch direction, not just numbers
- Risk-based targets - Critical code needs higher standards
- Automate enforcement - Quality gates in CI/CD
- Correlate with outcomes - Track metrics vs defects
π Conclusion
Metrics transform subjective debates into objective discussions. But remember: metrics are indicators, not goals. A codebase can have perfect metrics and still be poorly designed if the metrics are gamed.
- Find problem areas
- Track improvement over time
- Enforce minimum standards
- Measure developer performance
- Replace code review judgment
- Chase arbitrary numbers
π Review Schedule for This Article
| Day | Task | Time |
|---|---|---|
| Day 1 | Review Metrics Quick Reference table | 5 min |
| Day 3 | Redo Exercise 1 (Calculate Cyclomatic Complexity) | 15 min |
| Day 7 | Answer interview questions without looking | 10 min |
| Day 14 | Redo Debug This (Coverage Paradox) | 10 min |
| Day 30 | Run SonarQube on current project and review metrics | 15 min |