Object Oriented Programming
One skill covering the full OOP review/design lens: whether something should be a class at all, SOLID, GoF patterns, Tell Don't Ask, and Object Calisthenics. Don't recite theory — point at the specific line, name the violation, propose the concrete refactor.
Core rule: Use a class only when it creates multiple instances with their own state. Otherwise prefer functions, object literals, or types. — Dave Thomas
Step 0 — Should this even be a class?
Before applying any other rule, run new or reviewed code through this first:
- Does it hold instance state (remembers things between method calls)? → No: skip to utility-object check.
- Will it be instantiated more than once with different state? → No: continue below, it's probably not a class.
- Is it a collection of static methods? → convert to standalone functions /
const X = {...} as const. - Does it have exactly one public method (
perform/call/execute/run)? → convert to a function. - Is it invalid immediately after construction (needs setters before use)? → take all required args at once.
- Is it named after a design pattern (Decorator/Factory/Strategy/Command)? → consider composition/functions/HOFs.
- Is it a pure data class (constructor + properties, no behavior)? → use a
type/interface+ object literal. - None of the above? → ✅ legitimately a class. Now apply SOLID, Tell Don't Ask, and (optionally) Calisthenics below.
Full decision tree + walkthrough: references/refactoring-checklist.md
Anti-pattern catalog with before/after code for each numbered item above: references/anti-patterns.md
SOLID — quick reference
| Principle | Rule | Signal it's violated | Fix |
|---|---|---|---|
| SRP | One reason to change | Class handles auth, hashing, and persistence | Split into Validator / Hasher / Repository |
| OCP | Extend via subclass; don't modify proven code | Adding a new format requires editing the existing formatter | Add a subclass without touching the base |
| LSP | Subtypes substitutable without surprising callers | instanceof checks in calling code; subclass throws "not supported" |
Split into separate interfaces instead of one class hierarchy that only partially fits |
| ISP | Many focused interfaces over one fat one | Read-only client forced to implement write methods | Slice the fat interface by capability |
| DIP | Depend on abstractions, not concretions | A service directly instantiates a concrete client (new SendgridClient()) |
Inject an interface/abstract transport instead |
Full annotated TypeScript examples for all five: references/solid.md
Design patterns — quick selector
| Problem | Pattern |
|---|---|
| One shared instance (DB, config, logger) | Singleton |
| Swap algorithms at runtime | Strategy |
| Create families of related objects | Abstract Factory |
| Create objects without naming the concrete class | Factory |
| Construct complex objects step-by-step | Builder |
| Pair a type and utility object under one name | Companion Object |
| Notify subscribers on state change | Observer |
| Add cross-cutting behavior non-invasively | Decorator |
| Intercept/validate/log property access | Proxy |
| Share behavior across unrelated classes | Mixin |
| Reuse instances to reduce memory | Flyweight |
| Decouple via a central hub | Mediator |
When answering a pattern question, cover: what it is (1 sentence), when to use / not use, a minimal runnable
TypeScript example, trade-offs, and any TS-specific caveat (e.g. standard vs experimentalDecorators).
Full pattern examples + trade-offs (Factory, Strategy, Abstract Factory, Builder, Mixins, Decorators, Generics,
open recursion, delegation, structural polymorphism, simulating final): references/patterns.md
Tell, Don't Ask
Don't let callers query an object's state and decide externally — move the decision into the object. Red flags:
getter chains used to make a decision, if (obj.getX() > obj.getLimit()) outside the class, get then set in
sequence from the caller. Legitimate exceptions: DTOs, value objects, serialization/persistence boundaries, and
query methods that genuinely transform data for the caller — don't be a "getter eradicator."
Full guide, refactor steps, and review checklist: references/tell-dont-ask.md
Object Calisthenics (stricter pass)
Once class-shape, SOLID, and Tell Don't Ask are clean, Jeff Bay's 9 rules (one indent level per method, no
else, wrap primitives, first-class collections, one dot per line, no abbreviations, small entities, ≤2 instance
variables, no bare getters/setters) push further. Apply as a lens during deliberate refactor passes or when the
user explicitly wants a more disciplined review — not as a default blocking gate.
Full rule-by-rule checklist with TS/NestJS examples and gotchas: references/object-calisthenics.md
Testing implications
| Pattern | Setup | Testing |
|---|---|---|
| Pure functions | Pass args | Isolated, no mocks |
| Stateful classes | Factories, mocks | Tangled |
| Inheritance chains | Heavy fixtures | Nightmare |
Reference files
references/refactoring-checklist.md— class-vs-function decision treereferences/anti-patterns.md— 9 anti-patterns with TS before/after fixesreferences/solid.md— SOLID, annotated TypeScript examplesreferences/patterns.md— GoF + modern TS pattern catalog with trade-offsreferences/tell-dont-ask.md— encapsulation deep divereferences/object-calisthenics.md— Jeff Bay's 9 rules, NestJS/DDD-flavored
Benchmark
Scenario: .benchmarks/scenarios/solid-001-god-class.md · Run: 2026-08-31 · Log: .benchmarks/runs/2026-08-31/solid-001-god-class.json
| Model | Without | With | Delta |
|---|---|---|---|
| claude-opus-4-8 | 100% | 100% | +0% |
| claude-sonnet-4-6 | 83% | 100% | +17% |
| claude-haiku-4-5 | 100% | 100% | +0% |
SOFT PASS (run 2026-08-31). Sonnet +17; opus/haiku at ceiling on god-class decomposition. Gate per
.agents/skills/skill-optimizer/rules/release-gates.md.