code-quality

Code quality mastery — programming principles (SOLID, DRY, KISS, YAGNI…), refactoring techniques, and design patterns to write clean, maintainable, extensible software.

SKILL.md

GitHub

code-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:

  1. Its own object (this)
  2. Objects passed as parameters
  3. Objects it creates itself
  4. 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 / undefined checks 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 any in TypeScript, no unannotated functions in Python
  • No any types: use unknown and narrow down, or define precise types; @ts-ignore is 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 bare Error('message')
  • Log at the catch boundary — use structured logging (loguru in Python / console.error with 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 const over let; prefer readonly properties and Readonly<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() not getUser() or fetch()
  • Booleans read as yes/no questions: isAuthenticated, hasPermission, canRetry
  • Constants in SCREAMING_SNAKE_CASE for 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

References