Skip to content
Back to Maintenance & LTS

Debt Tracking & Metrics

Measure debt objectively with trend lines so paydown is visible and the policy is data-driven.

Why

Subjective debt assessment ("the codebase feels messy") is unactionable and demoralizing — you can't trend a feeling. Objective metrics — cyclomatic complexity, duplication %, test coverage, outdated-dep count, TODO count — turn debt into numbers you can plot, trend, and set targets against. The act of measuring also changes behavior: when complexity is on a dashboard, engineers refactor to move the number. Without metrics, debt paydown is invisible to leadership, so it gets cut; with metrics, a downward trend is a visible win that sustains the investment. The goal isn't a single number but a small panel of orthogonal metrics that together describe codebase health.

When

Set up the metrics once in CI, then trend them every sprint. Alert if any metric degrades beyond a threshold.

How

1. Pick 4-6 orthogonal metrics: complexity, duplication %, coverage, outdated-dep count, TODO(debt) count, build time
2. Instrument: eslint (complexity/dup), c8/istanbul (coverage), npm outdated (deps), grep (TODOs)
3. Emit to a dashboard: Code Climate, SonarQube, or a simple JSON + Grafana
4. Set thresholds: fail CI if complexity >15 per function, coverage <70%, or outdated-deps >10
5. Trend per sprint: store a snapshot, plot the line, review in sprint review
6. Alert on regression: if a PR raises complexity by >5 or drops coverage by >2%, block it
7. Publish the dashboard to the whole team — visibility is the point

Example

# CI debt-metrics job (GitHub Actions)
- name: Complexity & duplication
  run: npx eslint --rule '{"complexity":["warn",15]}' --format json src/ > complexity.json
- name: Coverage
  run: npx c8 --reporter=json npm test
- name: Outdated deps
  run: npm outdated --json > outdated.json
- name: TODO count
  run: grep -rc "TODO(debt)" src/ | wc -l > todo-count.txt

# Threshold gate (fail the PR)
node scripts/check-debt-thresholds.js
# if complexity > 15 avg || coverage < 70 || outdated > 10 -> exit 1

# Trend snapshot (stored per sprint)
# debt-snapshots/sprint-42.json
{ "complexity": 8.2, "duplication": 3.1, "coverage": 78,
  "outdated": 6, "todos": 14, "buildMs": 42000 }
# Plot in Grafana; review the trend line in sprint review.

Common Problems

  • ⚠Tracking one metric (coverage) and gaming it — 100% coverage with assertion-free tests is worse than 60% with real assertions.
  • ⚠Metrics in CI but not on a dashboard — engineers can't see the trend, so the number doesn't drive behavior.
  • ⚠No regression gate — a PR that raises complexity by 10 ships because CI only checks "does it build," not "does it rot."

Best Practices

  • ✓Track 4-6 orthogonal metrics, not one — a single metric is gameable; a panel is honest.
  • ✓Put a regression gate in CI that blocks PRs degrading any metric beyond a threshold — prevent rot, don't just measure it.
  • ✓Publish the trend dashboard to the whole team and review it in every sprint review — visibility drives paydown.