Zum Inhalt springen

Technische Schulden messen und richtig priorisieren

Praktische Methoden, um technische Schulden zu quantifizieren, nach Geschäftswirkung zu priorisieren und Strategien zu bauen, die Teams umsetzen.

5 Min. Lesezeit
Dashboard mit Kennzahlen zu technischen Schulden, Prioritätswerten und Diagrammen zur Wirkungsanalyse

Jedes Engineering-Team redet über technische Schulden. Nur wenige messen sie tatsächlich. Und noch weniger priorisieren sie anhand von Daten statt Bauchgefühl. Das Ergebnis ist entweder ewiges Aufschieben oder wahllose "Tech-Debt-Sprints", die am Ende die falschen Dinge reparieren.

Wer technische Schulden quantifiziert, macht aus einer vagen Beschwerde eine handhabbare Engineering-Priorität. Sobald der Schuld Zahlen zugeordnet sind, lassen sich rationale Entscheidungen treffen, wann und was zurückgezahlt wird.

Was technische Schulden messbar macht

Technische Schulden sind nicht einfach "unordentlicher Code". Es ist jede Implementierungsentscheidung, die die künftigen Kosten einer Änderung erhöht. Das Schlüsselwort ist "Kosten" – und Kosten lassen sich messen.

tstypescript
// ❌ Treating all technical debt as equal
const techDebt = [
  "Refactor user service",
  "Update dependencies",
  "Fix database schema",
  "Rewrite auth module",
];
 
// Just pick whatever feels important
const nextSprint = techDebt[0];
tstypescript
// ✅ Quantifying debt with impact scores
interface TechDebtItem {
  id: string;
  description: string;
  impactScore: number;      // 1-10: how much it slows development
  frequencyScore: number;   // 1-10: how often teams hit this
  fixEffort: number;        // story points or days
  riskScore: number;        // 1-10: likelihood of causing incidents
  lastIncidentDate?: Date;
  affectedTeams: string[];
}
 
function calculatePriority(item: TechDebtItem): number {
  const painScore = item.impactScore * item.frequencyScore;
  const riskAdjusted = painScore * (1 + item.riskScore / 10);
  const roi = riskAdjusted / item.fixEffort;
  return roi;
}
 
const prioritizedDebt = techDebt
  .map(item => ({ ...item, priority: calculatePriority(item) }))
  .sort((a, b) => b.priority - a.priority);

Die Formel muss nicht perfekt sein. Wichtig ist, dass du die Schuldposten anhand einheitlicher Kriterien vergleichst, statt dich danach zu richten, wer im Sprint Planning am lautesten ruft.

Das RICE-Framework für technische Schulden

Produktteams nutzen RICE (Reach, Impact, Confidence, Effort), um Features zu priorisieren. Dasselbe Framework funktioniert hervorragend auch für technische Schulden, sobald man die Dimensionen anpasst.

tstypescript
interface DebtRICE {
  reach: number;        // Number of developers affected per sprint
  impact: number;       // Time lost per encounter (hours)
  confidence: number;   // How sure are we about the estimates (0-1)
  effort: number;       // Person-weeks to fix
}
 
function riceScore(item: DebtRICE): number {
  return (item.reach * item.impact * item.confidence) / item.effort;
}
 
// Example: Legacy authentication module
const authModuleDebt: DebtRICE = {
  reach: 8,           // 8 developers touch auth weekly
  impact: 3,          // Each loses ~3 hours per encounter
  confidence: 0.8,    // We've measured this in time tracking
  effort: 4,          // 4 person-weeks to modernize
};
 
// Example: Inconsistent error handling
const errorHandlingDebt: DebtRICE = {
  reach: 12,          // Entire team encounters this
  impact: 0.5,        // Minor friction each time
  confidence: 0.6,    // Rough estimate
  effort: 2,          // 2 person-weeks
};
 
console.log("Auth module ROI:", riceScore(authModuleDebt));       // 4.8
console.log("Error handling ROI:", riceScore(errorHandlingDebt)); // 1.8

Die Zahlen erzählen eine klare Geschichte: Das Auth-Modul zu reparieren bringt 2,6-mal mehr Wert pro Aufwandseinheit als die Fehlerbehandlung zu vereinheitlichen. Ohne Quantifizierung greifen Teams oft zur "saubereren" Lösung statt zur wirkungsvolleren.

Entwicklerfriktion direkt messen

Das ehrlichste Maß für technische Schulden ist verlorene Entwicklerzeit. Sie sollte systematisch erfasst werden, statt sie zu schätzen.

tstypescript
// Simple friction tracking system
interface FrictionEvent {
  timestamp: Date;
  developerId: string;
  category: string;
  description: string;
  minutesLost: number;
  codeArea: string;
}
 
class FrictionTracker {
  private events: FrictionEvent[] = [];
 
  record(event: Omit<FrictionEvent, "timestamp">): void {
    this.events.push({ ...event, timestamp: new Date() });
  }
 
  getWeeklyReport(): Map<string, { count: number; totalMinutes: number }> {
    const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
    const recentEvents = this.events.filter(
      e => e.timestamp > oneWeekAgo
    );
 
    const report = new Map<string, { count: number; totalMinutes: number }>();
 
    for (const event of recentEvents) {
      const existing = report.get(event.codeArea) ?? {
        count: 0,
        totalMinutes: 0,
      };
      report.set(event.codeArea, {
        count: existing.count + 1,
        totalMinutes: existing.totalMinutes + event.minutesLost,
      });
    }
 
    return report;
  }
}
tstypescript
// ❌ Vague friction estimates
// "The payment service is annoying to work with"
// "Tests are slow"
// "Deployments take forever"
 
// ✅ Concrete friction data
const tracker = new FrictionTracker();
 
tracker.record({
  developerId: "dev-42",
  category: "slow-tests",
  description: "Payment integration tests take 12 min locally",
  minutesLost: 45,
  codeArea: "payment-service/tests",
});
 
tracker.record({
  developerId: "dev-17",
  category: "unclear-api",
  description: "Had to read source code to understand order API response",
  minutesLost: 30,
  codeArea: "order-service/api",
});

Nach zwei Wochen Friktions-Tracking zeigen sich Muster, die keine noch so gründliche Architekturprüfung zutage fördern würde. Die Daten überraschen Teams oft – die schlimmsten Schulden stecken nicht immer im ältesten Code.

Ein Register für technische Schulden aufbauen

Ein Schuldenregister ist ein lebendiges Inventar bekannter technischer Schulden, das laufend aktualisiert wird. Es dient als einzige verlässliche Quelle dafür, was existiert, warum es wichtig ist und wann es angegangen werden sollte.

tstypescript
interface DebtRegisterEntry {
  id: string;
  title: string;
  createdDate: Date;
  category: "architecture" | "code" | "test" | "infrastructure" | "dependency";
  owner: string;
  status: "identified" | "measured" | "scheduled" | "in-progress" | "resolved";
  businessImpact: string;
  metrics: {
    developerHoursPerMonth: number;
    incidentFrequency: number;
    deploymentImpact: number;
  };
  repaymentPlan?: {
    estimatedEffort: number;
    proposedSprint: string;
    dependencies: string[];
  };
}
 
function generateQuarterlyReport(
  register: DebtRegisterEntry[]
): {
  totalMonthlyHoursLost: number;
  topOffenders: DebtRegisterEntry[];
  resolvedThisQuarter: number;
  trend: "improving" | "stable" | "worsening";
} {
  const active = register.filter(e => e.status !== "resolved");
  const totalHours = active.reduce(
    (sum, e) => sum + e.metrics.developerHoursPerMonth,
    0
  );
 
  const topOffenders = [...active]
    .sort(
      (a, b) =>
        b.metrics.developerHoursPerMonth - a.metrics.developerHoursPerMonth
    )
    .slice(0, 5);
 
  const resolvedThisQuarter = register.filter(
    e =>
      e.status === "resolved" &&
      e.createdDate > new Date(Date.now() - 90 * 24 * 60 * 60 * 1000)
  ).length;
 
  return {
    totalMonthlyHoursLost: totalHours,
    topOffenders,
    resolvedThisQuarter,
    trend: totalHours > 100 ? "worsening" : totalHours > 50 ? "stable" : "improving",
  };
}

Der Quartalsbericht macht aus abstrakten Beschwerden Zahlen, die vor dem Management überzeugen. "Unser Team verliert 120 Entwicklerstunden pro Monat durch technische Schulden" ist ein deutlich stärkeres Argument als "wir brauchen Zeit zum Refactoring".

Schulden-Tracking in den Workflow integrieren

Das beste Schulden-Tracking läuft automatisch als Teil des bestehenden Workflows ab, statt als separater Prozess, der nach zwei Wochen wieder aufgegeben wird.

ymlyaml
# .github/PULL_REQUEST_TEMPLATE/default.md
## Technical Debt Assessment
 
<!-- Check all that apply -->
- [ ] This PR introduces new technical debt
- [ ] This PR repays existing technical debt
- [ ] No debt impact
 
### If introducing debt:
- **Debt ID**: (link to debt register entry)
- **Justification**: 
- **Estimated repayment effort**: 
 
### If repaying debt:
- **Debt ID(s) resolved**: 
- **Measurable improvement**: 
tstypescript
// Automated debt detection in CI
interface DebtSignal {
  type: string;
  severity: "low" | "medium" | "high";
  location: string;
  message: string;
}
 
function analyzeForDebtSignals(
  prDiff: string
): DebtSignal[] {
  const signals: DebtSignal[] = [];
 
  // Detect TODO/HACK/FIXME additions
  const todoPattern = /\+.*(?:TODO|HACK|FIXME|WORKAROUND)(?::?\s*)(.+)/gi;
  let match: RegExpExecArray | null;
 
  while ((match = todoPattern.exec(prDiff)) !== null) {
    signals.push({
      type: "code-comment-debt",
      severity: "medium",
      location: extractFileLocation(prDiff, match.index),
      message: match[1].trim(),
    });
  }
 
  // Detect growing file complexity
  const addedLines = (prDiff.match(/^\+[^+]/gm) ?? []).length;
  const removedLines = (prDiff.match(/^-[^-]/gm) ?? []).length;
 
  if (addedLines > 200 && removedLines < 20) {
    signals.push({
      type: "growing-complexity",
      severity: "low",
      location: "overall",
      message: `Large addition (${addedLines} lines) with minimal removal`,
    });
  }
 
  return signals;
}
 
function extractFileLocation(diff: string, index: number): string {
  const upToIndex = diff.substring(0, index);
  const fileMatch = upToIndex.match(/\+\+\+ b\/(.+)/g);
  return fileMatch ? fileMatch[fileMatch.length - 1].replace("+++ b/", "") : "unknown";
}

Wenn jeder PR Entscheidungen über Schulden sichtbar macht, entwickelt das Team die Gewohnheit, sie bewusst zu steuern. Neue Schulden sind nicht verboten – sie werden anerkannt und für die Rückzahlung eingeplant.

Die 20-Prozent-Regel und nachhaltige Rückzahlung

Viele Teams versuchen, einen festen Prozentsatz jedes Sprints für die Schuldentilgung zu reservieren. Die magische Zahl variiert, aber das Prinzip zählt mehr als der genaue Prozentsatz.

tstypescript
interface SprintAllocation {
  totalCapacity: number;       // story points
  featureWork: number;
  debtRepayment: number;
  bugFixes: number;
  debtPercentage: number;
}
 
function planSprintAllocation(
  totalCapacity: number,
  debtBudgetPercent: number,
  criticalBugs: number
): SprintAllocation {
  const bugAllocation = criticalBugs * 3; // ~3 points per bug
  const debtAllocation = Math.floor(
    (totalCapacity - bugAllocation) * (debtBudgetPercent / 100)
  );
  const featureAllocation = totalCapacity - bugAllocation - debtAllocation;
 
  return {
    totalCapacity,
    featureWork: featureAllocation,
    debtRepayment: debtAllocation,
    bugFixes: bugAllocation,
    debtPercentage: (debtAllocation / totalCapacity) * 100,
  };
}
 
// Example: 40-point sprint, 20% debt budget, 2 critical bugs
const plan = planSprintAllocation(40, 20, 2);
// { featureWork: 27, debtRepayment: 7, bugFixes: 6, debtPercentage: 17.5 }

Der entscheidende Punkt: Schuldentilgung wirkt kumulativ. Langsame Tests in diesem Sprint zu reparieren bedeutet schnellere Feedback-Zyklen im nächsten. Die API-Schicht aufzuräumen bedeutet weniger Bugs im nächsten Monat. Quantifiziere diese Effekte zweiter Ordnung, um fortlaufende Investitionen zu rechtfertigen.

Die wichtigsten Erkenntnisse

Beim Management technischer Schulden geht es nicht darum, alle Schulden zu eliminieren, sondern fundierte Entscheidungen zu treffen, welche Schulden man trägt und welche man zurückzahlt. Erfolgreiche Teams teilen gemeinsame Praktiken: Sie messen Friktion, statt zu raten, sie bewerten Schuldposten nach einheitlichen Kriterien und behandeln die Rückzahlung als kontinuierliche Investition statt als gelegentlichen Sprint.

Fang mit dem Friktions-Tracking an. Zwei Wochen Daten sagen mehr über die tatsächlichen Kosten deiner Codebasis aus als jede Architekturprüfung. Baue ein Register auf, integriere es in deinen PR-Workflow und reserviere einen nachhaltigen Prozentsatz jedes Sprints für die Rückzahlung. Die Zahlen sprechen für sich, wenn Stakeholder fragen, warum die Velocity Quartal für Quartal steigt.

Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX