Asked for a technical debt tracker in JavaScript. Here's an implementation that manages debt items with severity levels and reporting.
// Technical Debt Tracker - manages and reports on technical debt items
const SEVERITY_LEVELS = Object.freeze({
LOW: 1,
MEDIUM: 2,
HIGH: 3,
CRITICAL: 4
});
class DebtItem {
constructor({ id, description, severity, createdAt }) {
if (typeof id !== 'string') throw new TypeError('id must be a string');
if (!Object.values(SEVERITY_LEVELS).includes(severity)) {
throw new Error('Invalid severity level');
}
this.id = id;
this.description = description;
this.severity = severity;
this.createdAt = createdAt || new Date();
this.resolved = false;
}
// Calculate the age of the debt in days
getAgeInDays() {
const now = Date.now();
return Math.floor((now - this.createdAt.getTime()) / 86400000);
}
}
class TechnicalDebtTracker {
constructor() {
this.items = new Map();
}
// Add a new debt item to the tracker
addItem(item) {
if (!(item instanceof DebtItem)) {
throw new TypeError('Expected a DebtItem instance');
}
if (this.items.has(item.id)) {
throw new Error(`Duplicate debt item: ${item.id}`);
}
this.items.set(item.id, item);
}
resolveItem(id) {
const item = this.items.get(id);
if (!item) throw new Error(`Item not found: ${id}`);
item.resolved = true;
}
// Generate a summary report of unresolved debt
generateReport() {
const unresolved = Array.from(this.items.values()).filter(i => !i.resolved);
const bySeverity = unresolved.reduce((acc, item) => {
acc[item.severity] = (acc[item.severity] || 0) + 1;
return acc;
}, {});
return {
total: unresolved.length,
bySeverity,
oldest: unresolved.sort((a, b) => b.getAgeInDays() - a.getAgeInDays())[0]
};
}
exportToJSON() {
return JSON.stringify(Array.from(this.items.values()), null, 2);
}
}
const tracker = new TechnicalDebtTracker();
tracker.addItem(new DebtItem({ id: 'TD-001', description: 'Refactor auth module', severity: SEVERITY_LEVELS.HIGH }));
console.log(tracker.generateReport());
Code Review
1. Lines 3-8. Object.freeze on a constants object that's never exported or reassigned. We're defending against threats that don't exist.
2. Lines 12-14. Runtime type checking in a constructor for an internal class. This is JavaScript, not Java. If someone passes a number as id, the report will look weird and they'll figure it out.
3. Lines 24-27. The comment 'Calculate the age of the debt in days' immediately precedes a method called getAgeInDays. Truly earning your keep here.
4. Lines 36-38. Checking instanceof DebtItem means anyone constructing an item from deserialized JSON has to jump through hoops. This will bite us the moment we try to load from disk.
5. Line 61. sort() mutates the array in place while we're calling it inline for a single element. Grabbing 'oldest' by sorting the entire list is also O(n log n) when a single pass would do.
6. Lines 52-62. generateReport returns an object where bySeverity uses numeric keys (1, 2, 3, 4) instead of the human readable names. Whoever consumes this report will have to reverse map SEVERITY_LEVELS themselves.
7. Lines 10-22. Entire DebtItem class exists so we can call new DebtItem({…}) instead of just using a plain object. The only real behavior is getAgeInDays, which could be a one line function.