name: cleanCode description: Refactor/clean/simplify code - eliminate duplication, small functions, readable names. USE WHEN user says "clean", "simplify", "refactor", "readable", "messy", "complex", or code has >15 line functions or copy-paste.
Code Simplification
Core principle: Code reads like English prose.
Influences: Douglas Crockford's "JavaScript: The Good Parts", Robert C. Martin's "Clean Code"
🚨 TypeScript: Before writing TypeScript, run /cleanTypes for type annotation best practices.
1. Small Functions, Clear Names
Target: 3-15 lines per function. Name describes exactly what it does.
// Bad: Generic name, does multiple things (40 lines)
function process(filename) { ... }
// Good: Small functions with precise names
const extractExtension = (filename) => filename.replace(/.*symlink/, '')
const removeSymlinkAndExtension = (filename) => filename.replace(/\.symlink.*$/, '')
const replaceDOTWithDot = (str) => str.replace(/DOT/g, '.')
Naming rules:
- Verbs for actions:
createLink(),handleExistingFile() - Booleans:
isSymlink(),hasExtension(),canWrite() - No generic names:
process(),handle(),do()→ What specifically?
2. No Duplication
If you copy-paste code, stop. Extract a function.
// Bad: 3 similar blocks, 40 lines each
function renderIdentity(json) { /* extract fields, print */ }
function renderCard(json) { /* extract fields, print */ }
function renderLogin(json) { /* extract fields, print */ }
// Good: Extract common pattern
function renderEntry(json) {
const fields = extractFields(json)
fields.forEach(field => printField(field))
}
3. Iterate the Source, Not Type Dispatch
Red flag: Multiple extractTypeA(), extractTypeB(), extractTypeC() functions.
Instead: Iterate the data structure directly.
// Bad: Type-specific extractors + dispatch
const extractIdentity = (json) => [/* hardcode identity fields */]
const extractCard = (json) => [/* hardcode card fields */]
const extractLogin = (json) => [/* hardcode login fields */]
if (isIdentity(json)) return extractIdentity(json)
if (isCard(json)) return extractCard(json)
return extractLogin(json)
// Good: Generic extraction - iterate what exists
const extractFields = (json) =>
Object.entries(json.data || {})
.filter(([key, value]) => value && typeof value === 'string')
.map(([key, value]) => ({
label: humanizeKey(key),
displayValue: shouldMask(key) ? '•••' : value
}))
// Works for all types, no type checking
Key insight: Don't ask "what type is this?" Ask "what data exists?"
4. Module Splitting: ~250 Lines
When a file exceeds 250 lines, look for natural module boundaries.
// Before: links.ts - 247 lines (path utils + file ops + handlers + class + orchestration)
// After: Split by responsibility
// symlink/path-transform.ts - 23 lines
// symlink/file-ops.ts - 40 lines
// symlink/handlers.ts - 43 lines
// symlink/operation.ts - 76 lines
// links.ts - 77 lines (orchestration only)
Don't split prematurely: 200-line focused file > 5 poorly-abstracted 40-line files.
5. Fluent APIs for Sequential Operations
Chain methods to describe operations in natural language.
// Bad: Imperative control flow, needs comments
function safeLink(src: string, dest: string | null) {
// Handle unparseable destination
if (!dest) return { ... }
// Handle existing symlink
if (isSymlink(dest)) { ... }
// Handle existing file
if (fileExists(dest)) { ... }
// Create symlink
fs.mkdirSync(...)
return createLink(src, dest)
}
// Good: Reads like English, no comments needed
const safeLink = (src: string, dest: string | null) =>
new SymlinkOperation(src, dest)
.handleNullDestination()
.handleSymlink()
.handleExistingFile()
.createSymlink()
.result()
Implementation pattern:
class Operation {
private result: Result | null = null
step1() {
if (this.result) return this // Short-circuit if done
// Check condition, maybe set this.result
return this
}
result() { return this.result! }
}
When to use: Sequential operations with decision points, each step is a clear named concern.
6. Explicit Failures Over Silent Filtering
Make failures visible with error results, don't silently skip.
// Bad: Silent failure - lost information
function buildPlan() {
return files.map(transformPath).filter(path => path !== null)
}
// Which files failed? Unknown.
// Good: Explicit failure - trackable
function buildPlan() {
return files.map(file => ({ from: file, to: transformPath(file) }))
}
function executePlan(plan: Array<{from: string, to: string | null}>) {
return plan.map(({ from, to }) => safeLink(from, to))
}
function safeLink(src: string, dest: string | null) {
if (!dest) return { from: src, to: '<unparseable>', success: false }
// ... continue
}
// Now: results.filter(r => !r.success).length shows exactly what failed
7. Eliminate Special Cases
Every special case must justify its existence. Default to uniform handling.
// Bad: Unnecessary special case
if (files.length === 1) {
return handleSingleFile(files[0])
} else {
return handleMultipleFiles(files)
}
// Good: Unified handling (works for n=1 too)
return files.map(handleFile)
Ask: "What if I handle both cases uniformly?"
8. Arrow Functions for Simple Transformations
// Bad: Verbose
function executeSymlinkPlan(plan: SymlinkPlan[]): LinkResult[] {
const results: LinkResult[] = []
for (const { from, to } of plan) {
results.push(safeLink(from, to))
}
return results
}
// Good: Concise
const executeSymlinkPlan = (plan: SymlinkPlan[]) =>
plan.map(({ from, to }) => safeLink(from, to))
Note side effects: Add comment if .map() has side effects (creates files, mutates state).
Refactoring Checklist
Before code is "done":
- Function names read like English? Should describe exactly what they do
- Any copy-pasted code? Extract to function
- Multiple extractType functions? Replace with
Object.entries(source).map() - Any function >20 lines? Break into subfunctions
- File >250 lines? Look for natural module boundaries
- Sequential operations with branches? Consider fluent API
- Silently filtering failures? Make them explicit with error results
- Special cases that can be unified? Handle uniformly when possible
Complexity Limits
- Function >25 lines → Extract subfunctions
- File >250 lines → Consider splitting by responsibility
- Nested blocks >2 deep → Extract function
Self-prompt: "Do I have extractTypeA/B/C functions? Can I iterate the source instead? Would a fluent API make this read better? Are failures explicit? Does the code read like English?"