Technische Schulden systematisch verwalten
Ein Rahmen, um technische Schulden zu erkennen, zu priorisieren und abzubauen, ohne Features zu stoppen: Inventare, Kapazität und messbarer Nutzen.

Technische Schulden sind kein vages Gefühl, dass der Code schlecht ist. Es handelt sich um die messbaren Kosten dafür, sich jetzt für eine schnellere Lösung zu entscheiden, die später zusätzlichen Aufwand erfordert. Wie finanzielle Schulden verursachen sie Zinsen – jedes Feature dauert länger in der Entwicklung, jeder Fehler dauert länger in der Diagnose, jede Einarbeitung dauert länger.
Das Problem ist nicht, dass technische Schulden existieren. Manche Schulden werden bewusst und strategisch eingegangen. Das Problem entsteht, wenn Schulden nicht gemessen, nicht erfasst und nicht priorisiert werden – wenn sie sich still anhäufen, bis die Codebasis so brüchig wird, dass selbst kleine Änderungen riskant sind.
Technische Schulden kategorisieren
Nicht alle technischen Schulden sind gleich. Ein fehlender Datenbankindex kostet Sekunden pro Abfrage. Ein verworrener Abhängigkeitsgraph kostet Tage pro Feature. Das Kategorisieren von Schulden hilft dabei zu priorisieren, was zuerst behoben werden sollte.
// Technical debt classification framework
interface TechnicalDebtItem {
id: string;
title: string;
description: string;
category: DebtCategory;
severity: 'low' | 'medium' | 'high' | 'critical';
interestRate: InterestAssessment;
estimatedPayoffEffort: string; // "2 hours", "1 sprint", "1 quarter"
affectedAreas: string[];
createdDate: string;
lastAssessedDate: string;
}
type DebtCategory =
| 'architecture' // Wrong abstraction, tight coupling, missing boundaries
| 'code-quality' // Duplicated logic, unclear naming, missing types
| 'testing' // Missing tests, flaky tests, slow test suite
| 'infrastructure' // Outdated dependencies, manual deployments, missing monitoring
| 'documentation' // Missing or stale docs, tribal knowledge, unclear APIs
| 'performance' // Slow queries, missing caching, unoptimized assets
| 'security'; // Known vulnerabilities, missing auth checks, weak encryption
interface InterestAssessment {
// How much does this debt slow us down per week?
weeklyTimeCost: string; // "2 hours/week on workarounds"
// Is the interest rate increasing, stable, or decreasing?
trend: 'increasing' | 'stable' | 'decreasing';
// What happens if we never pay this off?
worstCase: string; // "Migration becomes impossible"
}// ❌ Vague debt tracking
const vagueDebt = [
"Code is messy",
"Need to refactor auth",
"Database is slow",
];
// Nobody knows what to fix, how long it'll take, or why it matters
// ✅ Specific, measurable debt items
const specificDebt: TechnicalDebtItem[] = [
{
id: 'DEBT-042',
title: 'User service has no integration tests',
description: 'The /api/users/* endpoints have 0% integration test coverage. ' +
'Last production incident (INC-127) was caused by a regression that unit ' +
'tests did not catch because they mock the database layer.',
category: 'testing',
severity: 'high',
interestRate: {
weeklyTimeCost: '4 hours/week debugging regressions',
trend: 'increasing',
worstCase: 'Data corruption incident affecting all users',
},
estimatedPayoffEffort: '1 sprint',
affectedAreas: ['user-service', 'api-gateway'],
createdDate: '2022-01-15',
lastAssessedDate: '2022-03-20',
},
];Das Schuldeninventar
Führe ein lebendiges Inventar aller bekannten technischen Schulden. Es dient als einzige verlässliche Quelle dafür, was existiert, wie gravierend es ist und was das Team dagegen zu unternehmen plant.
class DebtInventory {
private items: Map<string, TechnicalDebtItem> = new Map();
add(item: TechnicalDebtItem): void {
this.items.set(item.id, item);
}
// Prioritization: score items by impact and effort
prioritize(): TechnicalDebtItem[] {
return [...this.items.values()]
.map(item => ({
item,
score: this.calculatePriorityScore(item),
}))
.sort((a, b) => b.score - a.score)
.map(({ item }) => item);
}
private calculatePriorityScore(item: TechnicalDebtItem): number {
const severityWeight: Record<string, number> = {
critical: 4,
high: 3,
medium: 2,
low: 1,
};
const trendMultiplier: Record<string, number> = {
increasing: 1.5, // Getting worse — fix sooner
stable: 1.0,
decreasing: 0.7, // Getting better on its own — lower priority
};
const effortDiscounting: Record<string, number> = {
// Prefer quick wins — low effort, high impact
'2 hours': 2.0,
'1 day': 1.5,
'1 sprint': 1.0,
'1 quarter': 0.5,
};
const severity = severityWeight[item.severity] ?? 1;
const trend = trendMultiplier[item.interestRate.trend] ?? 1;
const effort = effortDiscounting[item.estimatedPayoffEffort] ?? 1;
return severity * trend * effort;
}
// Summary for stakeholder reporting
summary(): DebtSummary {
const items = [...this.items.values()];
return {
total: items.length,
bySeverity: {
critical: items.filter(i => i.severity === 'critical').length,
high: items.filter(i => i.severity === 'high').length,
medium: items.filter(i => i.severity === 'medium').length,
low: items.filter(i => i.severity === 'low').length,
},
byCategory: this.groupByCategory(items),
estimatedWeeklyInterest: this.totalWeeklyInterest(items),
};
}
private groupByCategory(items: TechnicalDebtItem[]): Record<string, number> {
return items.reduce((acc, item) => {
acc[item.category] = (acc[item.category] ?? 0) + 1;
return acc;
}, {} as Record<string, number>);
}
private totalWeeklyInterest(items: TechnicalDebtItem[]): string {
// Aggregate the weekly cost across all items for reporting
const totalHours = items.reduce((sum, item) => {
const match = item.interestRate.weeklyTimeCost.match(/(\d+)/);
return sum + (match ? parseInt(match[1]) : 0);
}, 0);
return `~${totalHours} hours/week`;
}
}Kapazitätszuteilung: die 80/20-Regel
Der praktischste Ansatz besteht darin, einen festen Prozentsatz der Entwicklungskapazität für den Schuldenabbau einzuplanen. Eine gängige Aufteilung ist 80% Features, 20% Schulden. Das sorgt für stetigen Fortschritt, ohne die Feature-Entwicklung anzuhalten.
// Sprint planning with debt allocation
interface SprintPlan {
totalCapacity: number; // Story points available
featureAllocation: number; // 80% for features
debtAllocation: number; // 20% for debt reduction
features: WorkItem[];
debtItems: TechnicalDebtItem[];
}
function planSprint(
capacity: number,
featureBacklog: WorkItem[],
debtInventory: DebtInventory
): SprintPlan {
const debtAllocation = Math.floor(capacity * 0.2);
const featureAllocation = capacity - debtAllocation;
// Pick the highest-priority debt items that fit in the allocation
const prioritizedDebt = debtInventory.prioritize();
const selectedDebt: TechnicalDebtItem[] = [];
let debtPointsUsed = 0;
for (const item of prioritizedDebt) {
const points = estimatePoints(item);
if (debtPointsUsed + points <= debtAllocation) {
selectedDebt.push(item);
debtPointsUsed += points;
}
}
return {
totalCapacity: capacity,
featureAllocation,
debtAllocation,
features: featureBacklog.slice(0, featureAllocation),
debtItems: selectedDebt,
};
}Opportunistischer Schuldenabbau
Ergänzend zur festen Zuteilung sollten Verbesserungen nach der „Pfadfinderregel" gefördert werden – hinterlasse den Code besser, als du ihn vorgefunden hast. Wenn du für ein Feature eine Datei anfasst, behebe auch die kleinen Schuldenposten in dieser Datei, solange du schon dort bist.
// ❌ Opening a separate PR just to rename a variable
// Overhead of review, CI, deployment for a trivial change
// Gets deprioritized forever
// ✅ Fixing small debt while working on a related feature
// PR title: "Add user notification preferences"
// Commit 1: Add notification preferences API
// Commit 2: Clean up user service imports and naming (while here)
//
// The debt fix ships for free alongside the feature
// Reviewer sees the cleanup is scoped and relevant
// Guidelines for opportunistic fixes:
const opportunisticRules = {
do: [
'Rename unclear variables in files you are modifying',
'Add types to untyped functions you are calling',
'Remove dead code you encounter while navigating',
'Fix linting warnings in changed files',
],
doNot: [
'Refactor entire modules while fixing a bug',
'Change unrelated files in the same PR',
'Reformat code outside your changes',
'Upgrade dependencies as a side effect',
],
};Schulden gegenüber Stakeholdern kommunizieren
Entwicklungsteams tun sich oft schwer zu erklären, warum technische Schulden für Product Manager und Führungskräfte relevant sind. Der Schlüssel liegt darin, Schulden in geschäftliche Auswirkungen zu übersetzen: langsamere Feature-Auslieferung, höhere Vorfallhäufigkeit und längere Einarbeitungszeiten.
// Framing debt in business terms
interface DebtBusinessImpact {
featureVelocityDrag: string;
// "Features that should take 1 sprint are taking 2 sprints
// due to workarounds in the payment module"
incidentFrequency: string;
// "3 of the last 5 production incidents trace back to
// the untested user service endpoints"
onboardingCost: string;
// "New engineers need 2 extra weeks to become productive
// because the build system has 14 undocumented manual steps"
opportunityCost: string;
// "We cannot adopt the new auth provider until we decouple
// the auth module from the user service — blocking the
// SSO feature that 40% of enterprise prospects request"
}Die wichtigsten Erkenntnisse
- Schulden explizit erfassen — ein lebendiges Inventar mit Schweregrad, Kategorie, wöchentlichen Kosten und geschätztem Abbauaufwand führen
- Nach Zinssatz priorisieren — zuerst die Schulden beheben, die am schnellsten schlimmer werden und die höchsten wöchentlichen Umgehungskosten verursachen
- Konsequent 20% der Kapazität einplanen — stetiger Fortschritt zählt mehr als gelegentliche heldenhafte Refactoring-Sprints
- Kleine Schulden opportunistisch beheben — Code in Dateien aufräumen, die ohnehin schon für Feature-Arbeit bearbeitet werden
- Den Nutzen messen — nach der Behebung eines Schuldenpostens prüfen, ob die erwartete Verbesserung tatsächlich eingetreten ist
- In geschäftlichen Begriffen kommunizieren — Schulden für Stakeholder in Auswirkungen auf Feature-Geschwindigkeit, Vorfallhäufigkeit und Einarbeitungskosten übersetzen


