Clean Code for TypeScript/JavaScript
Practical patterns for writing and refactoring clean, maintainable TypeScript/JavaScript code.
Code Smell Detection
Naming Smells
| Smell | Example | Fix |
|---|---|---|
| Single-letter names | const d = new Date() | const createdAt = new Date() |
| Abbreviations | const usrMgr = ... | const userManager = ... |
| Generic names | data, info, temp, result | Name by what it represents |
| Misleading names | userList (but it's a Set) | users or userSet |
| Encoding types | strName, arrItems | name, items |
Function Smells
| Smell | Indicator | Refactoring |
|---|---|---|
| Too many parameters | >3 params | Extract to options object |
| Flag arguments | process(data, true) | Split into separate functions |
| Side effects hidden | Function does more than name suggests | Rename or split |
| God function | >20 lines or multiple responsibilities | Extract smaller functions |
| Deep nesting | >3 levels of indentation | Early returns, extract functions |
Structure Smells
| Smell | Indicator | Refactoring |
|---|---|---|
| Long file | >300 lines | Split by responsibility |
| Feature envy | Function uses another object's data extensively | Move to that object |
| Data clumps | Same group of params passed together | Extract to type/class |
| Primitive obsession | Using primitives for domain concepts | Create value objects |
| Switch on type | if (type === 'A') ... else if (type === 'B') | Polymorphism or lookup |
Refactoring Patterns
For detailed before/after code examples, see references/refactoring-patterns.md.
Quick reference:
- •Guard clauses — Replace nested if/else with early returns
- •Object lookup — Replace switch/if-else chains with Record mapping
- •Options object — Replace >3 parameters with a single options object
- •Named constants — Replace magic numbers/strings with descriptive constants
- •Extract function — Break god functions into single-responsibility pieces
- •Flatten ternaries — Replace nested ternaries with lookup or function
- •Consolidate duplicates — Extract repeated logic into shared functions
- •Simplify booleans — Return condition directly instead of if/else true/false
- •Array methods — Replace manual loops with filter/map/reduce
- •Destructure — Use destructuring for cleaner property access
Naming Conventions
Functions
- •Actions: verb + noun →
createUser,validateInput,fetchOrders - •Booleans: is/has/can/should prefix →
isValid,hasPermission,canEdit - •Event handlers: handle + event →
handleClick,handleSubmit - •Transformers: to/from/parse/format →
toJSON,fromDTO,parseDate
Variables
- •Booleans: positive names →
isEnablednotisNotDisabled - •Arrays/collections: plural →
users,orderItems - •Counts: noun + Count →
retryCount,errorCount
Constants
- •Module-level: SCREAMING_SNAKE_CASE →
MAX_RETRIES,API_BASE_URL - •Enum-like objects: PascalCase key, values match usage →
Status.Active
Quick Refactoring Checklist
When reviewing code for cleanliness:
- •Names — Can you understand intent without reading implementation?
- •Functions — Does each do exactly one thing? <20 lines?
- •Parameters — More than 3? Extract to object
- •Nesting — More than 2 levels? Use early returns
- •Duplication — Same logic in 2+ places? Extract
- •Magic values — Any unexplained numbers/strings? Name them
- •Comments — Explaining "what"? Rewrite code to be self-explanatory
- •Dead code — Commented-out code or unused vars? Delete
Anti-Patterns to Avoid
Over-Engineering
- •Don't create abstractions for single-use cases
- •Don't add configurability "for the future"
- •Don't wrap simple operations in utility functions
Under-Engineering
- •Don't ignore repeated patterns (3+ occurrences = extract)
- •Don't leave deeply nested code unrefactored
- •Don't use
anyto avoid typing properly
Wrong Abstraction
- •Don't force inheritance when composition works
- •Don't create god classes that do everything
- •Don't split tightly coupled logic across files