General Clean Code Principles
Critical Rules
G5: DRY (Don't Repeat Yourself)
Every piece of knowledge has one authoritative representation.
// Bad - duplication
const taxRate = 0.0825;
const caTotal = subtotal * 1.0825;
const nyTotal = subtotal * 1.07;
// Good - single source of truth
const TAX_RATES: Record<string, number> = { CA: 0.0825, NY: 0.07 };
function calculateTotal(subtotal: number, state: string): number {
return subtotal * (1 + TAX_RATES[state]);
}
G16: No Obscured Intent
Don't be clever. Be clear.
// Bad - what does this do?
return ((x & 0x0f) << 4) | (y & 0x0f);
// Good - obvious intent
return packCoordinates(x, y);
G23: Prefer Polymorphism to If/Else
// Bad - will grow forever
function calculatePay(employee: {
type: "SALARIED" | "HOURLY" | "COMMISSIONED";
salary?: number;
hours?: number;
rate?: number;
base?: number;
commission?: number;
}): number {
if (employee.type === "SALARIED") {
return employee.salary ?? 0;
} else if (employee.type === "HOURLY") {
return (employee.hours ?? 0) * (employee.rate ?? 0);
} else if (employee.type === "COMMISSIONED") {
return (employee.base ?? 0) + (employee.commission ?? 0);
}
return 0;
}
// Good - open/closed principle
interface Employee {
calculatePay(): number;
}
class SalariedEmployee implements Employee {
constructor(private readonly salary: number) {}
calculatePay(): number {
return this.salary;
}
}
class HourlyEmployee implements Employee {
constructor(
private readonly hours: number,
private readonly rate: number,
) {}
calculatePay(): number {
return this.hours * this.rate;
}
}
class CommissionedEmployee implements Employee {
constructor(
private readonly base: number,
private readonly commission: number,
) {}
calculatePay(): number {
return this.base + this.commission;
}
}
G25: Replace Magic Numbers with Named Constants
// Bad
if (elapsedTime > 86400) {
// ...
}
// Good
const SECONDS_PER_DAY = 86400;
if (elapsedTime > SECONDS_PER_DAY) {
// ...
}
G30: Functions Should Do One Thing
If you can extract another function, your function does more than one thing.
G36: Law of Demeter (Avoid Train Wrecks)
// Bad - reaching through multiple objects
const outputDir = context.options.scratchDir.absolutePath;
// Good - one dot
const outputDir = context.getScratchDir();
Composition Over Inheritance
Inheritance couples a subclass to its parent's internals forever. Use it only for a genuine is-a relationship where the subclass is substitutable everywhere the parent is. For reuse, compose.
// Bad - extends to borrow one method, inherits the entire surface
class EmailNotifier extends SmtpClient {
notify(user: User, message: string) {
this.send(user.email, message)
}
}
// Good - holds what it needs
class EmailNotifier {
constructor(private readonly smtp: SmtpClient) {}
notify(user: User, message: string) {
this.smtp.send(user.email, message)
}
}
The composed version takes a fake SmtpClient in tests, can swap transports, and exposes only
notify. The extended version makes every SmtpClient method part of its own public API.
Signals you extended for the wrong reason: an override that throws, an override ignoring parameters the base requires, or a three-deep hierarchy with the real behaviour in the middle.
TypeScript makes the alternatives cheap. Use an interface for the contract, and union types where
subclasses were only ever acting as an enum:
// Bad - a class hierarchy standing in for three shapes
abstract class Shape { abstract area(): number }
// Good - discriminated union, exhaustively checkable
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number }
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2
case "square": return shape.side ** 2
}
}
In React the same rule reads: compose components and extract hooks. There is no component inheritance worth having.
Enforcement Checklist
When reviewing AI-generated code, verify:
- No duplication (G5)
- Clear intent, no magic numbers (G16, G25)
- Polymorphism over conditionals (G23)
- Functions do one thing (G30)
- No Law of Demeter violations (G36)
- Boundary conditions handled (G3)
- Dead code removed (G9)
- Composition preferred over inheritance
- Module reads top-down, callers above callees