code-quality
Code quality mastery — programming principles (SOLID, DRY, KISS, YAGNI…), refactoring techniques, and design patterns to write clean, maintainable, extensible software.
SKILL.mdskills/code-quality/SKILL.md
GitHubcode-quality
Code quality mastery — programming principles (SOLID, DRY, KISS, YAGNI…), refactoring techniques, and design patterns to write clean, maintainable, extensible software.
Instructions
Apply code quality practices to continuously improve codebases:
Programming Principles
Core principles every codebase should follow regardless of language or paradigm.
SOLID
Five OOP principles that make systems easy to extend and maintain:
| Principle | Full name | Rule |
|---|---|---|
| S | Single Responsibility | A class/module has one reason to change |
| O | Open/Closed | Open for extension, closed for modification |
| L | Liskov Substitution | Subtypes must be substitutable for their base type |
| I | Interface Segregation | Prefer many small interfaces over one fat interface |
| D | Dependency Inversion | Depend on abstractions, not concretions |
S — Single Responsibility
// ❌ one class handles both data and formatting
class Report { generate() {} formatAsPdf() {} sendByEmail() {} }
// ✅ each class has one job
class Report { generate(): ReportData {} }
class PdfRenderer { render(data: ReportData): Buffer {} }
class Mailer { send(to: string, body: Buffer) {} }
O — Open/Closed
// ❌ must edit existing class to add discount type
class Discount { calc(type: string, price: number) {
if (type === 'vip') return price * 0.8;
if (type === 'seasonal') return price * 0.9;
}}
// ✅ add new behaviour by adding a new class
interface Discount { apply(price: number): number; }
class VipDiscount implements Discount { apply(p) { return p * 0.8; } }
class SeasonalDiscount implements Discount { apply(p) { return p * 0.9; } }
L — Liskov Substitution
// ❌ Square overrides setWidth in a way that violates Rectangle's contract
class Rectangle { setWidth(w: number) {} setHeight(h: number) {} area() {} }
class Square extends Rectangle { setWidth(w: number) { super.setWidth(w); super.setHeight(w); } }
// ✅ use a shared Shape interface instead of inheritance
interface Shape { area(): number; }
class Rectangle implements Shape { area() { return this.w * this.h; } }
class Square implements Shape { area() { return this.side ** 2; } }
I — Interface Segregation
// ❌ Printer forced to implement fax it doesn't support
interface Machine { print(): void; scan(): void; fax(): void; }
// ✅ split into focused interfaces
interface Printer { print(): void; }
interface Scanner { scan(): void; }
interface Fax { fax(): void; }
class SimplePrinter implements Printer { print() {} }
D — Dependency Inversion
// ❌ high-level module depends on low-level concrete class
class OrderService { private db = new MySqlDatabase(); }
// ✅ depend on an abstraction; inject the implementation
interface Database { save(data: object): void; }
class OrderService { constructor(private db: Database) {} }
DRY — Don't Repeat Yourself
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." — The Pragmatic Programmer
- Duplication of logic → extract into a shared function, class, or module
- Duplication of data → derive it from a single source of truth; don't copy-paste constants
- Duplication of structure → use generics, templates, or higher-order functions
- What DRY is NOT: don't blindly deduplicate code that looks the same but represents different concepts — premature abstraction is worse than duplication
// ❌ same validation logic copy-pasted in three places
function createUser(email: string) { if (!email.includes('@')) throw new Error('Invalid'); }
function updateUser(email: string) { if (!email.includes('@')) throw new Error('Invalid'); }
// ✅ single source of truth
function assertValidEmail(email: string) { if (!email.includes('@')) throw new Error('Invalid email'); }
function createUser(email: string) { assertValidEmail(email); }
function updateUser(email: string) { assertValidEmail(email); }
KISS — Keep It Simple, Stupid
"Simplicity is the ultimate sophistication." — Leonardo da Vinci
- Write the simplest solution that solves the problem
- Avoid clever tricks, unnecessary abstractions, and premature generalisation
- Code is read far more often than it is written — optimise for the reader
- If you need a comment to explain what code does, the code is probably too complex
// ❌ over-engineered
const isAdult = (age: number) => !!(age >= 18 ? true : false) === true;
// ✅ simple and clear
const isAdult = (age: number) => age >= 18;
YAGNI — You Aren't Gonna Need It
"Always implement things when you actually need them, never when you just foresee that you need them." — Ron Jeffries (XP)
- Don't add functionality, abstractions, or configuration hooks "just in case"
- Every line of speculative code must be maintained, tested, and understood
- Prefer to refactor when the need arises; don't design for imaginary requirements
- Works in tandem with KISS — both fight accidental complexity
Law of Demeter (LoD) — Principle of Least Knowledge
"Only talk to your immediate friends."
A method should only call methods on:
- Its own object (
this) - Objects passed as parameters
- Objects it creates itself
- Its direct component objects
// ❌ "train wreck" — violates LoD
const city = customer.getAddress().getCity().getName();
// ✅ ask the object, don't dig into it
const city = customer.getCityName();
CQS — Command-Query Separation
Every method should be either:
- A Query: returns a value, has no side effects (safe to call multiple times)
- A Command: performs an action/mutation, returns
void
Never both. (See also the separate-query-from-modifier refactoring.)
SoC — Separation of Concerns
Different concerns (UI, business logic, persistence, networking) must live in separate modules with clear boundaries. Changes to one concern should not ripple into others.
- MVC / layered architecture: Controller ↔ Service ↔ Repository
- CSS/HTML/JS separation in frontend
- Domain logic must not import HTTP or DB packages
Fail Fast
Detect and surface errors as early and loudly as possible:
- Validate inputs at the entry point (constructor, API boundary)
- Throw/return errors immediately when a precondition is violated
- Never silently swallow exceptions
- Prefer explicit
null/undefinedchecks over optional chaining that hides bugs
// ❌ silent failure — caller has no idea something went wrong
function parseAge(s: string): number { return parseInt(s) || 0; }
// ✅ fail fast — caller is informed of the problem
function parseAge(s: string): number {
const n = parseInt(s, 10);
if (isNaN(n)) throw new TypeError(`Invalid age: "${s}"`);
return n;
}
Composition over Inheritance
Favour assembling behaviour from small, focused objects (composition) over deep inheritance hierarchies.
- Inheritance creates tight coupling between parent and child classes
- Composition allows behaviour to be mixed in, swapped, or reused across unrelated classes
- Use inheritance only when a genuine is-a relationship exists; use composition for has-a
// ❌ fragile inheritance hierarchy
class Animal { breathe() {} }
class FlyingAnimal extends Animal { fly() {} }
class SwimmingAnimal extends Animal { swim() {} }
// What about a duck? It both flies and swims — multiple inheritance problem.
// ✅ compose capabilities
const canFly = { fly: () => console.log('flying') };
const canSwim = { swim: () => console.log('swimming') };
const duck = Object.assign({ name: 'Duck' }, canFly, canSwim);
duck.fly(); duck.swim();
Guidelines
The following rules are mandatory regardless of language or project type:
Type Safety
- Full type annotations: annotate all function parameters, return types, and variables — no implicit
anyin TypeScript, no unannotated functions in Python - No
anytypes: useunknownand narrow down, or define precise types;@ts-ignoreis forbidden except in generated/vendor code - Runtime validation at boundaries: validate data from external sources (HTTP requests, file reads, env vars, user input) with Zod (TypeScript) or Pydantic / dataclasses (Python) — never trust raw input
- Strict null checks: never access a property on a possibly-null value without an explicit guard
Error Handling
- Never silently swallow exceptions — always handle or re-throw with added context
- Typed error classes: define domain-specific error types (
UserNotFoundError,ValidationError) instead of bareError('message') - Log at the catch boundary — use structured logging (
loguruin Python /console.errorwith context in TypeScript) including relevant IDs (userId, requestId) - Propagate meaningful messages: error messages must tell the caller what went wrong, where, and ideally why
Immutability
- Prefer
constoverlet; preferreadonlyproperties andReadonly<T>in TypeScript - Never mutate function parameters — clone if modification is needed
- Prefer pure functions: same input always produces same output, no hidden side effects
Naming Conventions
- Names must express intent:
getUserByEmail()notgetUser()orfetch() - Booleans read as yes/no questions:
isAuthenticated,hasPermission,canRetry - Constants in
SCREAMING_SNAKE_CASEfor global/module-level values - No single-letter variables except loop indices (
i,j) and well-known math/science notation
Code Size Limits
| Unit | Limit |
|---|---|
| Function/method | ≤ 30 lines |
| File/module | ≤ 300 lines |
| Cyclomatic complexity | ≤ 10 per function |
| Parameters per function | ≤ 4 (use a parameter object beyond this) |
Documentation
- Public functions and classes must have doc-comments (
TSDoc/ Python docstring) - Complex business logic requires an inline comment explaining why, not what
- No commented-out code in committed files — use version control instead
- READMEs must be updated alongside the code they describe
Refactoring Techniques
Improve internal structure without changing external behaviour:
| Skill | Purpose |
|---|---|
extract-method |
Pull fragment into a named method |
inline-method |
Replace trivial delegation with direct body |
extract-variable |
Name a complex expression |
replace-temp-with-query |
Replace temp variable with method call |
replace-magic-number |
Replace literal with named constant |
decompose-conditional |
Extract condition/branches into methods |
guard-clauses |
Eliminate nesting with early returns |
replace-conditional-polymorphism |
Replace type-switches with polymorphism |
extract-class |
Split a class with too many responsibilities |
move-method |
Move method to the class that uses it most |
introduce-parameter-object |
Replace parameter group with object |
separate-query-from-modifier |
Split read/write methods |
Design Patterns
Creational — object creation mechanisms:
| Skill | Purpose |
|---|---|
singleton |
One shared instance |
factory-method |
Subclass decides which class to instantiate |
abstract-factory |
Families of related objects |
builder |
Step-by-step complex object construction |
prototype |
Clone existing objects |
Structural — compose objects into larger structures:
| Skill | Purpose |
|---|---|
adapter |
Convert incompatible interfaces |
bridge |
Decouple abstraction from implementation |
composite |
Tree of uniform single/container objects |
decorator |
Wrap to add behaviour dynamically |
facade |
Simple interface to a complex subsystem |
flyweight |
Share state among many fine-grained objects |
proxy |
Controlled access to another object |
Behavioral — communication between objects:
| Skill | Purpose |
|---|---|
chain-of-responsibility |
Pass request along a handler chain |
command |
Encapsulate request as an object |
iterator |
Traverse collection without exposing structure |
mediator |
Centralise cross-object communication |
memento |
Save and restore object state |
observer |
Notify dependents of state changes |
state |
Alter behaviour when internal state changes |
strategy |
Swap interchangeable algorithms at runtime |
template-method |
Define algorithm skeleton; subclasses fill steps |
visitor |
Separate algorithm from object structure |
Principles
- Refactor before adding features — clean code is easier to extend
- Small, safe steps — run tests after each refactoring
- Apply patterns to solve problems, not to show off — prefer simplicity; use patterns when they reduce coupling or duplication
- Boy Scout Rule — leave code cleaner than you found it
- Principle of Least Surprise — code should behave as a reasonable reader would expect