---
name: clean-code
description: Pre-implementation guide for writing clean, maintainable code following SOLID principles, proper naming conventions,
and dependency management. Use before implementing any feature to structure code with single responsibility,
minimal complexity, comprehensive testing, and correct dependency flow.
---
WHEN TO APPLY: Before implementing any feature, follow these rules to structure code properly from the start.
IMPORTANT: This skill has been written for typescript. Adapt it to the standards of the requested programming language. For example, rust uses snake_case instead of the here defined camelCase.
Code must be immediately clear to other developers:
- Descriptive names - Variables, methods, classes reveal intent without comments
- Consistent naming - Follow project conventions throughout
- No magic numbers - Use named constants
- Single responsibility - Each unit does one thing well
Avoid repeating logic:
- DRY principle - Extract shared code to methods/classes
- Reuse over rewrite - Search for existing implementations first
- Abstraction - Create shared utilities for common patterns
Keep codebase lean:
- Less code = less bugs - Code is liability
- Minimal classes - Only create when necessary
- Simple solutions - Avoid over-engineering
- Delete unused code - Remove immediately, rely on git history
Every feature needs tests:
- Test coverage - Aim for critical paths at minimum
- Tests pass - All tests green before committing
- Write tests first - TDD when appropriate
Code will be read more than written:
- Future-proof - Consider who maintains this in 6 months
- Refactor continuously - Don't accumulate debt
- Document why, not what - Code shows what, comments explain why
S - Single Responsibility Principle
- Class has one reason to change
- Each class/method does one thing
- Split classes doing multiple jobs
O - Open/Closed Principle
- Open for extension, closed for modification
- Use inheritance/composition for new behavior
- Avoid modifying existing working code
L - Liskov Substitution Principle
- Subclasses replaceable for parent class
- Don't break parent class contracts
- Subclasses should extend, not restrict
I - Interface Segregation Principle
- Many specific interfaces > one general interface
- Clients shouldn't depend on unused methods
- Split fat interfaces into focused ones
D - Dependency Inversion Principle
- Depend on abstractions, not concretions
- High-level modules shouldn't depend on low-level
- Use interfaces/abstract classes for dependencies
Encapsulation
- Hide internal state and implementation
- Expose only necessary public interface
- Use private fields with public methods
Composition Over Inheritance
- Prefer "has-a" over "is-a"
- More flexible than inheritance
- Easier to change behavior at runtime
Program to Interfaces
- Depend on contracts, not implementations
- Enables polymorphism and testability
- Decouples components
- Descriptive -
userAgenotx - Pronounceable -
timestampnotts - Searchable -
MAX_RETRY_COUNTnot5 - No abbreviations -
customerListnotcustLst - Context matters -
user.namenotuser.userName
- Verbs -
calculateTotal(),getUserById() - Reveal intent -
isEligibleForDiscount()notcheck() - Consistent prefixes -
get,set,is,has,create,delete - Avoid generic - Not
processData(),handleInput()
- Nouns -
User,OrderProcessor,EmailService - Single responsibility - Name reflects one job
- Avoid "Manager"/"Helper" - Usually signals unclear responsibility
- Specific -
UserValidatornotValidator
- ALL_CAPS -
MAX_CONNECTIONS,DEFAULT_TIMEOUT - Descriptive - Explain purpose
- Grouped - Related constants in enum/object
- 10 lines max - Longer? Extract methods
- 3 parameters max - More? Use parameter object
- Single level of abstraction - Don't mix high/low level operations
- One purpose - Do one thing, do it well
- Guard clauses first - Handle edge cases early, return
- Main logic - Core functionality at single abstraction level
- No side effects - Don't modify state unexpectedly
- Query/Command separation - Either return value OR modify state, not both
// Good - guard clauses
function processOrder(order) {
if (!order) return null
if (!order.isValid()) return null
if (order.isEmpty()) return null
return order.process()
}
// Bad - nested conditions
function processOrder(order) {
if (order) {
if (order.isValid()) {
if (!order.isEmpty()) {
return order.process()
}
}
}
return null
}
- <200 lines - Larger? Split class
- Single responsibility - One reason to change
- High cohesion - Methods use most fields
- Low coupling - Minimal dependencies on other classes
- Static constants
- Static fields
- Instance fields
- Constructors
- Public methods
- Private methods (organized by caller proximity)
- God classes - Do everything
- Data classes - Only getters/setters, no behavior
- Utility classes - Random static methods
- Manager classes - Vague responsibility
- Function bloat - Many loose functions sharing data/context that should be a class
Convert standalone functions to a class when:
- Shared state - Functions pass same data between each other (hash, element, config)
- Cohesive operations - Functions operate on same concept/entity
- Internal helpers - Private functions only called by other functions in same file
- Lifecycle - Operations have setup/teardown or state transitions
// Bad - function bloat
function applyBlur(element, hash) { ... }
function createOverlay(hash) { ... }
function insertOverlay(element, overlay) { ... }
function unblurImage(element, hash) { ... }
// Good - cohesive class
class BlurManager {
constructor(private element: MediaElement) {}
blur(hash: string): void { ... }
unblur(): void { ... }
private createOverlay(): HTMLElement { ... }
private insertOverlay(overlay: HTMLElement): void { ... }
}
Signs of function bloat:
- Functions in same file passing same parameters
- Helper functions only used internally
- Functions that logically "belong together"
- File named
*Manager.tsor*Utils.tswith loose functions
- Extract to methods -
if (user.isActive())notif (user.status === 'active') - Guard clauses - Early returns for special cases
- Polymorphism - Replace type-checking switches
- Null objects - Instead of null checks everywhere
- Boolean variables -
const isValid = conditionthenif (isValid)
// Good - polymorphism
class Shape {
abstract area(): number
}
class Circle extends Shape {
area() { return Math.PI * r * r }
}
// Bad - type checking
function area(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.r * shape.r
}
}
- Value objects - For domain concepts (Email, Money, UserId)
- No primitive obsession - Don't use string for everything
- Type safety - Objects provide validation and behavior
- Explicit intent -
Emailclass >string
- Encapsulate - Don't expose raw arrays/lists
- Immutability - Return copies, not references
- Meaningful names -
activeUsersnotlist1 - Single type - Avoid mixed-type collections
- Immutable by default - Use
readonly/final - Value objects - Immutable data structures
- References when needed - Shared mutable state only when necessary
- No global state - Pass dependencies explicitly
- Why, not what - Code shows what, comments explain why
- Business rules - Explain domain logic
- Warnings - Gotchas or consequences
- Legal - Copyright, licenses
- TODO - With ticket number and date
- Obvious code - Self-documenting names instead
- Redundant -
// increment iabovei++ - Outdated - Maintain or delete
- Commented code - Delete it, use git history
- Journal comments - Use git log instead
- Extract method - Complex block → named method
- Rename - Variable/method with better name
- Constants - Magic number → named constant
- Type system - Use types to encode constraints
- Use exceptions - Not error codes
- Specific exceptions - Custom types for different errors
- Meaningful messages - Include context
- Handle at boundaries - Let exceptions bubble up
- Don't catch generic - Catch specific exception types
- Fail fast - Validate at entry points
- Guard clauses - Check preconditions early
- Assertions - Document assumptions
- Type system - Use for compile-time checks
- Explicit > implicit - Constructor injection over global state
- Abstractions - Depend on interfaces
- Minimal dependencies - Only what's needed
- No circular - A depends on B, B on A = wrong
- One class per file - Unless tightly coupled
- Related files near - Feature folders, not type folders
- Clear structure - Predictable locations
- Encapsulation - Internal implementation hidden
Use Clean Architecture as proposed by Robert C. Martin. Core idea: separate business logic from external concerns (UI, databases, frameworks). Inner layers contain policies/rules, outer layers contain mechanisms/details. Dependencies point inward only—inner layers never know about outer layers. This makes business logic testable, framework-independent, and easy to change.
Code must form a directed acyclic graph from entry point (src/main.ts) down.
Entry Point (index.ts/main.ts)
│
▼
Bootstrap/Composition Root
│
├──▶ High-Level Orchestrators (Controllers)
│ │
│ ├──▶ Domain Services
│ │ │
│ │ └──▶ Domain Models
│ │
│ └──▶ Infrastructure (API clients, storage)
│
└──▶ Framework Integration (event listeners, UI)
Rules:
- Dependencies flow DOWN only - never up or sideways
- Lower layers NEVER import from higher layers
- Each layer only knows about the layer directly below, potentially skipping a layer or two but not more
Allowed at module level:
- Type definitions and interfaces
- Constants (primitive values only)
- Class/function declarations
- Exports
Forbidden at module level:
newkeyword (except in composition root)- Function calls with side effects
- Event listener registration
- Async operations
// Good - declarations only
export class MyService { ... }
export const MAX_RETRIES = 3
export type Config = { ... }
// Bad - side effects at import
const instance = new MyService() // executes on import
initializeListeners() // side effect on import
chrome.storage.get(...) // async operation on import
Before implementing:
- Identify entry point - Where does execution start?
- List all classes - What needs to be instantiated?
- Map dependencies - What does each class need?
- Order creation - Dependencies before dependents
- Create bootstrap - Single function composing everything
Verify:
- Entry point only calls bootstrap
- All
newkeywords in composition root (except value objects) - No static service methods
- No module-level instantiation
- Dependencies flow downward only
- Classes receive dependencies via constructor
Before writing code:
- Understand requirement - What problem are we solving?
- Check existing code - Is this already implemented?
- Plan structure - Which classes/methods needed?
- Consider tests - How will we test this?
- Name things - Draft names before coding
- Keep it simple - What's the simplest solution?
- Single responsibility - Is each component focused?
- Dependencies - What do we depend on? Can we abstract it?
During implementation:
- Write tests - For expected behavior
- Implement incrementally - Small steps
- Refactor continuously - Clean as you go
- Follow conventions - Match existing patterns
- Avoid duplication - Extract shared code immediately
- Keep methods small - Extract when >10 lines
- Meaningful names - Rename when purpose becomes clear
- Run tests - Verify after each change
- Factory methods for complex construction
- Builder for objects with many parameters
- Strategy for interchangeable algorithms
- Observer for event handling
- Null object for default behavior
- Template method for algorithm structure
- Singletons (global state)
- God objects (too many responsibilities)
- Anemic domain models (data classes)
- Premature optimization
- Speculative generality
- Copy-paste programming
Before implementing, ensure:
- Clear, obvious names for everything
- No duplication - reuse existing code
- Minimal complexity - simplest solution
- Tests planned/written
- Easy to maintain - for future you
Key metrics:
- Methods: ≤10 lines, ≤3 parameters
- Classes: ≤200 lines, single responsibility
- Zero code duplication
- 100% test pass rate
- Self-documenting code with minimal comments
Remember: Clean code is code others can understand and modify with confidence. Write for humans first, machines second.
Sources: Refactoring.Guru Clean Code, Design Patterns