Java

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

AspectDetails
Metric CategoriesComplexity, Coupling, Cohesion, Coverage
Key ToolsSonarQube, JaCoCo, PIT, SpotBugs
Primary InsightTrends matter more than absolutes
Setup Time2-4 hours for full pipeline
Business Impact40% 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)
Code
Loading 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.

Cost: $2.1M in lost sales, customer trust damaged.
The fix: They implemented mandatory quality gates:
  • 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)
Code
Loading syntax highlighter...

πŸ”¬ Deep Dive

Part 1: Complexity Metrics

Cyclomatic Complexity (McCabe)

What: Number of linearly independent paths through code.
Formula: CC = E - N + 2P
  • E = edges in control flow graph
  • N = nodes
  • P = connected components (usually 1)
Simplified: Count decision points + 1
  • Each if, else if, while, for, case, catch, &&, || adds 1
JAVA(39 lines)
Code
Loading syntax highlighter...
Thresholds:
CCRatingAction
1-10SimpleOK
11-20ComplexReview
21-50High riskRefactor
51+UntestableCritical

Cognitive Complexity (SonarSource)

What: How hard is the code for a human to understand?
Key differences from cyclomatic:
  • Penalizes nesting (nested if = harder than sequential)
  • Ignores shorthand structures (ternary operator)
  • Rewards language features (early returns)
JAVA(23 lines)
Code
Loading syntax highlighter...
Thresholds:
CognitiveRatingAction
0-10ClearOK
11-15ModerateReview
16-25DifficultRefactor
26+Very difficultCritical

NPath Complexity

What: Number of unique execution paths.
Why it matters: Represents minimum tests needed for full path coverage.
JAVA(14 lines)
Code
Loading syntax highlighter...
Threshold: NPath > 200 is concerning, > 1000 is critical.

Part 2: Coupling Metrics

Afferent Coupling (Ca)

What: Number of classes that depend on this class (incoming dependencies).
High Ca means: Class is heavily used. Changes here affect many places. Should be stable.
TEXT(6 lines)
Code
Loading syntax highlighter...

Efferent Coupling (Ce)

What: Number of classes this class depends on (outgoing dependencies).
High Ce means: Class depends on many things. Likely to break when others change.
TEXT(8 lines)
Code
Loading syntax highlighter...

Instability (I)

Formula: I = Ce / (Ca + Ce)
Range: 0 (completely stable) to 1 (completely unstable)
TEXT(2 lines)
Code
Loading syntax highlighter...
Design principle: Dependencies should flow toward stability. Unstable modules should depend on stable modules.
TEXT(14 lines)
Code
Loading syntax highlighter...

Part 3: Cohesion Metrics

LCOM (Lack of Cohesion of Methods)

Variants:
  • LCOM1: Original, rarely used
  • LCOM2: Improved
  • LCOM4: Most practical, used by SonarQube
LCOM4 concept: Methods that share instance variables are connected. LCOM4 = number of connected components.
JAVA(28 lines)
Code
Loading syntax highlighter...
Thresholds:
LCOM4Interpretation
1Perfectly cohesive
2-3Consider splitting
4+Definitely split

Tight Class Cohesion (TCC)

What: Percentage of method pairs that access common attributes.
Formula: TCC = (Connected pairs) / (Total pairs)
JAVA(13 lines)
Code
Loading syntax highlighter...
Threshold: TCC > 0.5 is good, < 0.3 suggests low cohesion.

Part 4: Coverage Metrics

Line Coverage

What: Percentage of code lines executed by tests.
JAVA(11 lines)
Code
Loading syntax highlighter...
Limitation: Line coverage doesn't guarantee logic is tested.

Branch Coverage

What: Percentage of decision branches executed.
JAVA(11 lines)
Code
Loading syntax highlighter...
More meaningful than line coverage - ensures both sides of conditions tested.

Mutation Coverage (Mutation Testing)

What: Percentage of code mutations detected by tests.
How it works:
  1. Tool mutates code (e.g., changes > to >=)
  2. Runs tests
  3. Tests should fail (mutation killed)
  4. If tests pass, mutation survived (weak test)
JAVA(15 lines)
Code
Loading syntax highlighter...
Tool: PIT (pitest.org) for Java
Threshold: Mutation score > 80% indicates strong tests.

Part 5: Tool Configuration

SonarQube Quality Gate

YAML(14 lines)
Code
Loading syntax highlighter...
Recommended Quality Gate:
TEXT(9 lines)
Code
Loading syntax highlighter...

JaCoCo Configuration (Maven)

XML(45 lines)
Code
Loading syntax highlighter...

PIT Mutation Testing (Maven)

XML(26 lines)
Code
Loading syntax highlighter...
Run:
mvn test-compile org.pitest:pitest-maven:mutationCoverage

⚠️ Common Mistakes

Mistake 1: Gaming the Metrics

JAVA(18 lines)
Code
Loading syntax highlighter...

Mistake 2: Chasing 100% Coverage

JAVA(19 lines)
Code
Loading syntax highlighter...

TEXT(10 lines)
Code
Loading 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)
Code
Loading syntax highlighter...
Why does 95% coverage miss critical bugs? Find the metric's blind spot!

βœ… Solution:
The root cause is coverage without meaningful assertions (sometimes called "assertion-free testing"):
Problems with this "test":
  1. No assertions - just executing code
  2. No verification of return values
  3. No boundary condition testing
  4. No negative case testing
Bugs that slip through:
  • total could be 0 or negative (not checked)
  • Inventory availability not verified
  • Customer validation missing
  • Error handling incomplete
The lesson: Coverage measures "code executed" not "code verified". A test that runs code but checks nothing is worthless. Focus on:
  • Assertion density - meaningful checks per test
  • Mutation testing - would changes break tests?
  • Edge case coverage - boundaries, nulls, errors
Better metric: Combine coverage (80%+) with mutation testing score (70%+).

πŸ’» Exercises

Exercise 1: Calculate Cyclomatic Complexity

⭐ Difficulty: Easy | ⏱️ Time: 15 minutes

Task: Calculate CC for this method:
JAVA(27 lines)
Code
Loading syntax highlighter...
βœ… Solution:
TEXT(14 lines)
Code
Loading syntax highlighter...

Exercise 2: Identify Coupling Issues

⭐⭐ Difficulty: Medium | ⏱️ Time: 20 minutes

Task: Calculate Ca, Ce, and I for each component:
TEXT(10 lines)
Code
Loading syntax highlighter...
βœ… Solution:
ComponentCa (Afferent)Ce (Efferent)I = Ce/(Ca+Ce)
ComponentA02 (B, C)2/2 = 1.0 (unstable)
ComponentB2 (A, C)1 (D)1/3 = 0.33
ComponentC1 (A)2 (B, D)2/3 = 0.67
ComponentD2 (B, C)00/2 = 0.0 (stable)
Analysis:
  • 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

Task: Set up a Gradle build that fails if:
  • Line coverage < 80%
  • Branch coverage < 70%
  • Cyclomatic complexity > 15 for any method
βœ… Solution:
KOTLIN(33 lines)
Code
Loading syntax highlighter...

Exercise 4: Create Metrics Dashboard

⭐⭐⭐ Difficulty: Medium-Hard | ⏱️ Time: 25 minutes

Task: Design a metrics dashboard for a team. Define:
  • 5 key metrics to display
  • Thresholds for each (green/yellow/red)
  • Update frequency
βœ… Solution:
TEXT(24 lines)
Code
Loading syntax highlighter...

Exercise 5: Analyze Real Codebase

⭐⭐⭐⭐ Difficulty: Hard | ⏱️ Time: 30 minutes

Task: Analyze a module in your codebase using SonarQube or IntelliJ's analysis. Document:
  • Current metrics
  • Top 3 improvements
  • Expected impact
βœ… Solution Template:
TEXT(20 lines)
Code
Loading syntax highlighter...

🎀 Senior-Level Interview Questions

Question #1: Which metrics do you prioritize and why?

Strong answer:

I prioritize based on defect correlation:

  1. Complexity (Cyclomatic/Cognitive) - Strongest correlation with bugs. CC > 20 has 2x bug rate.
  2. Coverage trends - Not absolute numbers, but declining coverage signals problems.
  3. Coupling (Instability) - High instability in core modules predicts cascade failures.
  4. 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?

Strong answer:

I address specific concerns:

"It slows us down":
  • Show data: 1 hour in quality gates saves 8 hours in bug fixes
  • Start with warnings, graduate to blocks
  • Exclude legacy code initially
"It's arbitrary":
  • Research shows CC > 15 doubles bug probability
  • Adjust thresholds based on our defect data
  • Team can propose changes with evidence
"We need to ship":
  • 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?

Strong answer:
Line coverage: Did the test execute this line?
  • Can be 100% with zero assertions
  • Doesn't verify logic correctness
Mutation coverage: Did the test detect when code changed?
  • Tests must fail when code is mutated
  • Verifies tests actually check behavior

Example:

JAVA(10 lines)
Code
Loading syntax highlighter...

Question #4: How do you set realistic coverage targets?

Strong answer:

I use risk-based differentiation:

Code TypeLine TargetBranch TargetRationale
Core domain90%85%High risk, high value
Services80%70%Business logic
Controllers60%50%Integration tested
DTOs/Config0%0%No behavior to test

Process:

  1. Measure current coverage
  2. Set incremental goals (+5% per quarter)
  3. Higher targets for new code
  4. Track and adjust based on defect correlation

πŸ“ Summary & Key Takeaways

Metric Quick Reference

MetricTargetTool
Cyclomatic Complexity< 10 per methodSonarQube
Cognitive Complexity< 15 per methodSonarQube
LCOM4= 1 (cohesive)SonarQube
InstabilityDepends on layerJDepend
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

  1. Trends over absolutes - Watch direction, not just numbers
  2. Risk-based targets - Critical code needs higher standards
  3. Automate enforcement - Quality gates in CI/CD
  4. 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.

Use metrics to:
  • Find problem areas
  • Track improvement over time
  • Enforce minimum standards
Don't use metrics to:
  • Measure developer performance
  • Replace code review judgment
  • Chase arbitrary numbers
Your next step: Continue to Part 6 (Extract Patterns) to learn the refactoring techniques that improve these metrics.

πŸ“… Review Schedule for This Article

DayTaskTime
Day 1Review Metrics Quick Reference table5 min
Day 3Redo Exercise 1 (Calculate Cyclomatic Complexity)15 min
Day 7Answer interview questions without looking10 min
Day 14Redo Debug This (Coverage Paradox)10 min
Day 30Run SonarQube on current project and review metrics15 min

Next in series: [Part 6: Extract Patterns (Method, Class, Interface)]