
Code Simplifier
- 1.3k installs
- 186 repo stars
- Updated July 24, 2026
- pproenca/dot-skills
code-simplifier provides documented workflows for Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior. Use when simplifying code, reducing complexit
About
The code-simplifier skill code simplification skill for improving clarity consistency and maintainability while preserving exact behavior Use when simplifying code reducing complexity cleaning up recent changes applying refactoring patterns or improving readability Triggers on tasks involving code cleanup simplification refactoring or readability improvements Community Code Simplification Best Practices Comprehensive code simplification guide for AI agents and LLMs Contains 47 rules across 8 categories prioritized by impact from critical context discovery behavior preservation to incremental language idioms Each rule includes detailed explanations real-world examples comparing incorrect vs correct implementations and specific impact metrics Context First Understand project conventions before making any changes 2 Behavior Preservation Change how code is written never what it does 3 Scope Discipline Focus on recently modified code keep diffs small 4 Clarity Over Brevity Explicit readable code beats clever one-liners When to Apply Reference these guidelines when Simplifying or cleaning up recently modified code Reducing nesting complexity or duplication Improving naming and readabili.
- **Context First**: Understand project conventions before making any changes
- **Behavior Preservation**: Change how code is written, never what it does
- **Scope Discipline**: Focus on recently modified code, keep diffs small
- **Clarity Over Brevity**: Explicit, readable code beats clever one-liners
- Simplifying or cleaning up recently modified code
Code Simplifier by the numbers
- 1,336 all-time installs (skills.sh)
- +67 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #91 of 1,356 Code Review & Quality skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
code-simplifier capabilities & compatibility
- Capabilities
- **context first**: understand project convention · **behavior preservation**: change how code is wr · **scope discipline**: focus on recently modified · **clarity over brevity**: explicit, readable cod · simplifying or cleaning up recently modified cod
- Use cases
- documentation
What code-simplifier says it does
# Community Code Simplification Best Practices Comprehensive code simplification guide for AI agents and LLMs.
Contains 47 rules across 8 categories, prioritized by impact from critical (context discovery, behavior preservation) to incremental (language idioms).
npx skills add https://github.com/pproenca/dot-skills --skill code-simplifierAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 186 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 24, 2026 |
| Repository | pproenca/dot-skills ↗ |
How do I use code-simplifier for the task described in its SKILL.md triggers?
Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior. Use when simplifying code, reducing complexity, cleaning up recent changes, applying.
Who is it for?
Teams invoking code-simplifier when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior. Use when simplifying code, reducing complexity, cleaning up recent changes, applying refactoring patterns
What you get
Step-by-step guidance grounded in code-simplifier documentation and reference files.
- simplified source code
- readability improvements
By the numbers
- Contains 47 simplification rules across 8 categories
Files
Community Code Simplification Best Practices
Comprehensive code simplification guide for AI agents and LLMs. Contains 47 rules across 8 categories, prioritized by impact from critical (context discovery, behavior preservation) to incremental (language idioms). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics.
Core Principles
1. Context First: Understand project conventions before making any changes 2. Behavior Preservation: Change how code is written, never what it does 3. Scope Discipline: Focus on recently modified code, keep diffs small 4. Clarity Over Brevity: Explicit, readable code beats clever one-liners
When to Apply
Reference these guidelines when:
- Simplifying or cleaning up recently modified code
- Reducing nesting, complexity, or duplication
- Improving naming and readability
- Applying language-specific idiomatic patterns
- Reviewing code for maintainability issues
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | Context Discovery | CRITICAL | ctx- | 4 |
| 2 | Behavior Preservation | CRITICAL | behave- | 6 |
| 3 | Scope Management | HIGH | scope- | 5 |
| 4 | Control Flow Simplification | HIGH | flow- | 9 |
| 5 | Naming and Clarity | MEDIUM-HIGH | name- | 6 |
| 6 | Duplication Reduction | MEDIUM | dup- | 5 |
| 7 | Dead Code Elimination | MEDIUM | dead- | 5 |
| 8 | Language Idioms | LOW-MEDIUM | idiom- | 7 |
Quick Reference
1. Context Discovery (CRITICAL)
- `ctx-read-claude-md` - Always read CLAUDE.md before simplifying
- `ctx-detect-lint-config` - Check for linting and formatting configs
- `ctx-follow-existing-patterns` - Match existing code style in file and project
- `ctx-project-over-generic` - Project conventions override generic best practices
2. Behavior Preservation (CRITICAL)
- `behave-preserve-outputs` - Preserve all return values and outputs
- `behave-preserve-errors` - Preserve error messages, types, and handling
- `behave-preserve-api` - Preserve public function signatures and types
- `behave-preserve-side-effects` - Preserve side effects (logging, I/O, state changes)
- `behave-no-semantics-change` - Forbid subtle semantic changes
- `behave-verify-before-commit` - Verify behavior preservation before finalizing
3. Scope Management (HIGH)
- `scope-recent-code-only` - Focus on recently modified code only
- `scope-minimal-diff` - Keep changes small and reviewable
- `scope-no-unrelated-refactors` - No unrelated refactors
- `scope-no-global-rewrites` - Avoid global rewrites and architectural changes
- `scope-respect-boundaries` - Respect module and component boundaries
4. Control Flow Simplification (HIGH)
- `flow-early-return` - Use early returns to reduce nesting
- `flow-guard-clauses` - Use guard clauses for preconditions
- `flow-no-nested-ternaries` - Never use nested ternary operators
- `flow-explicit-over-dense` - Prefer explicit control flow over dense expressions
- `flow-flatten-nesting` - Flatten deep nesting to maximum 2-3 levels
- `flow-single-responsibility` - Each code block should do one thing
- `flow-positive-conditions` - Prefer positive conditions over double negatives
- `flow-optional-chaining` - Use optional chaining and nullish coalescing
- `flow-boolean-simplification` - Simplify boolean expressions
5. Naming and Clarity (MEDIUM-HIGH)
- `name-intention-revealing` - Use intention-revealing names
- `name-nouns-for-data` - Use nouns for data, verbs for actions
- `name-avoid-abbreviations` - Avoid cryptic abbreviations
- `name-consistent-vocabulary` - Use consistent vocabulary throughout
- `name-avoid-generic` - Avoid generic names
- `name-string-interpolation` - Prefer string interpolation over concatenation
6. Duplication Reduction (MEDIUM)
- `dup-rule-of-three` - Apply the rule of three
- `dup-no-single-use-helpers` - Avoid single-use helper functions
- `dup-extract-for-clarity` - Extract only when it improves clarity
- `dup-avoid-over-abstraction` - Prefer duplication over premature abstraction
- `dup-data-driven` - Use data-driven patterns over repetitive conditionals
7. Dead Code Elimination (MEDIUM)
- `dead-remove-unused` - Delete unused code artifacts
- `dead-delete-not-comment` - Delete code, never comment it out
- `dead-remove-obvious-comments` - Remove comments that state the obvious
- `dead-keep-why-comments` - Keep comments that explain why, not what
- `dead-remove-todo-fixme` - Remove stale TODO/FIXME comments
8. Language Idioms (LOW-MEDIUM)
- `idiom-ts-strict-types` - Use strict types over any (TypeScript)
- `idiom-ts-const-assertions` - Use const assertions and readonly (TypeScript)
- `idiom-rust-question-mark` - Use ? for error propagation (Rust)
- `idiom-rust-iterator-chains` - Use iterator chains when clearer (Rust)
- `idiom-python-comprehensions` - Use comprehensions for simple transforms (Python)
- `idiom-go-error-handling` - Handle errors immediately (Go)
- `idiom-prefer-language-builtins` - Prefer language and stdlib builtins
Workflow
1. Discover context: Read CLAUDE.md, lint configs, examine existing patterns 2. Identify scope: Focus on recently modified code unless asked to expand 3. Apply transformations: Use rules in priority order (CRITICAL first) 4. Verify behavior: Ensure outputs, errors, and side effects remain identical 5. Keep diffs minimal: Small, focused changes that are easy to review
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
{Rule Title}
{1-3 sentences explaining WHY this matters for code simplification. Focus on maintainability, readability, and behavior preservation.}
Incorrect ({what's wrong}):
```{language} {Bad code example - production-realistic, not strawman} {// Comments explaining the problem}
**Correct ({what's right}):**
{Good code example - minimal diff from incorrect} {// Comments explaining the benefit}
{Optional sections as needed:}
**Alternative ({context}):**
{Alternative approach when applicable}
**When NOT to Apply:**
- {Exception 1 - when this rule doesn't apply}
- {Exception 2 - legitimate reasons to skip this rule}
**Benefits:**
- {Benefit 1 - specific, measurable improvement}
- {Benefit 2 - additional positive outcome}
Reference: [{Reference Title}]({Reference URL})
{
"version": "1.0.6",
"organization": "Community",
"technology": "Code Simplification",
"date": "January 2026",
"abstract": "Comprehensive code simplification guide for AI agents and LLMs. Contains 45 rules across 8 categories, prioritized by impact from critical (context discovery, behavior preservation) to incremental (language idioms). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation.",
"references": [
"https://refactoring.com/catalog/",
"https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882",
"https://google.github.io/styleguide/",
"https://rust-lang.github.io/api-guidelines/",
"https://www.typescriptlang.org/docs/handbook/",
"https://go.dev/doc/effective_go",
"https://peps.python.org/pep-0008/"
]
}
Code Simplifier Skill
A comprehensive code simplification skill for AI agents and LLMs, containing 45 rules across 8 categories.
Overview
This skill guides code simplification with a focus on:
- Context-first approach: Understand project conventions before making changes
- Behavior preservation: Change how code is written, never what it does
- Scope discipline: Focus on recently modified code, keep diffs small
- Clarity over brevity: Explicit, readable code beats clever one-liners
Structure
code-simplifier/
├── SKILL.md # Entry point with quick reference
├── README.md # This file
├── metadata.json # Version, organization, references
├── references/
│ ├── _sections.md # Category definitions and ordering
│ ├── ctx-*.md # Context Discovery rules (5)
│ ├── behave-*.md # Behavior Preservation rules (6)
│ ├── scope-*.md # Scope Management rules (5)
│ ├── flow-*.md # Control Flow rules (7)
│ ├── name-*.md # Naming and Clarity rules (5)
│ ├── dup-*.md # Duplication Reduction rules (5)
│ ├── dead-*.md # Dead Code Elimination rules (5)
│ └── idiom-*.md # Language Idioms rules (7)
└── assets/
└── templates/
└── _template.md # Template for new rulesGetting Started
# No installation required - this is a documentation-only skill
# For development/validation:
pnpm install # Install validation dependencies (optional)
pnpm build # Build/compile AGENTS.md from source rules
pnpm validate # Validate skill structure and content1. Read SKILL.md for an overview and quick reference 2. Check references/_sections.md to understand category priorities 3. Reference individual rules as needed during code simplification tasks
Creating a New Rule
1. Copy assets/templates/_template.md to references/{prefix}-{slug}.md 2. Fill in frontmatter: title, impact, impactDescription, tags 3. Write WHY explanation (1-3 sentences) 4. Add incorrect and correct code examples 5. Update SKILL.md quick reference section
Rule File Structure
Each rule file follows this format:
---
title: Rule Title
impact: CRITICAL|HIGH|MEDIUM-HIGH|MEDIUM|LOW-MEDIUM|LOW
impactDescription: Quantified impact (e.g., "reduces nesting by 40%")
tags: prefix, technique, related-concepts
---
## Rule Title
WHY this matters (1-3 sentences).
**Incorrect (what's wrong):**
\`\`\`language
Bad example
\`\`\`
**Correct (what's right):**
\`\`\`language
Good example - minimal diff from incorrect
\`\`\`File Naming Convention
- Prefix must match category:
ctx-,behave-,scope-,flow-,name-,dup-,dead-,idiom- - Slug should be descriptive:
early-return,preserve-outputs,rule-of-three - Examples:
flow-early-return.md,behave-preserve-api.md,dup-rule-of-three.md
Impact Levels
| Level | Description | When to Use |
|---|---|---|
| CRITICAL | Must follow, violations cause bugs or breaking changes | Context discovery, behavior preservation |
| HIGH | Strongly recommended, violations harm maintainability | Scope management, control flow |
| MEDIUM-HIGH | Recommended for clarity and consistency | Naming conventions |
| MEDIUM | Good practice, apply when beneficial | Duplication, dead code |
| LOW-MEDIUM | Nice to have, language-specific optimizations | Language idioms |
Scripts
Validate the skill structure and content:
node ~/.claude/plugins/cache/dot-claude/dev-skill/*/scripts/validate-skill.js ./code-simplifierContributing
1. Follow the rule template exactly 2. Ensure the first tag matches the file prefix 3. Use production-realistic code examples (no foo/bar/baz) 4. Quantify impact where possible 5. Include "When NOT to Apply" section for complex rules 6. Run validation before submitting
Sections
This file defines all sections, their ordering, impact levels, and descriptions. The section ID (in parentheses) is the filename prefix used to group rules.
---
1. Context Discovery (ctx)
Impact: CRITICAL Description: Before making any changes, understand project conventions by reading CLAUDE.md, linting configs, and existing patterns. Project standards override generic guidance. Scoping to recently modified code is covered in the Scope Management section.
2. Behavior Preservation (behave)
Impact: CRITICAL Description: Simplification must never change what code does - only how it is written. Outputs, error handling, side effects, and API surfaces must remain identical.
3. Scope Management (scope)
Impact: HIGH Description: Focus on recently modified code only. Keep diffs small and reviewable. Avoid unrelated refactors, global rewrites, or architectural changes.
4. Control Flow Simplification (flow)
Impact: HIGH Description: Reduce nesting through early returns and guard clauses. Avoid nested ternaries. Prefer explicit control flow over dense expressions.
5. Naming and Clarity (name)
Impact: MEDIUM-HIGH Description: Use precise, intention-revealing names. Nouns for data, verbs for actions. Descriptive names over implicit abbreviations.
6. Duplication Reduction (dup)
Impact: MEDIUM Description: Extract helpers when code appears 3+ times and extraction improves clarity. Avoid single-use abstractions that obscure intent.
7. Dead Code Elimination (dead)
Impact: MEDIUM Description: Remove unused code, redundant comments, and obsolete patterns. Delete rather than comment out. Keep comments that explain "why" not "what".
8. Language Idioms (idiom)
Impact: LOW-MEDIUM Description: Apply language-specific patterns that improve clarity: TypeScript strict types, Rust ownership, Python comprehensions, Go error handling.
Forbid Subtle Semantic Changes
The most dangerous simplifications are those that look equivalent but have different semantics. Null vs undefined, loose vs strict equality, truthy vs explicit checks - these distinctions matter. Code that appears cleaner may silently change behavior for edge cases that existing tests do not cover.
Incorrect (changes null/undefined semantics):
// Before: explicitly checks for null
function getDisplayName(user: User | null): string {
if (user === null) {
return 'Anonymous';
}
return user.name;
}
// After "simplification": truthy check
function getDisplayName(user: User | null): string {
return user ? user.name : 'Anonymous';
}
// Breaks: user with name = '' or name = 0 (if polymorphic)Correct (preserve explicit null check):
function getDisplayName(user: User | null): string {
return user === null ? 'Anonymous' : user.name;
}Incorrect (changes equality semantics):
// Before: intentional loose equality for null/undefined
function isEmpty(value) {
return value == null; // Catches both null and undefined
}
// After "simplification": strict equality
function isEmpty(value) {
return value === null;
}
// Breaks: isEmpty(undefined) now returns falseCorrect (preserve loose equality when intentional):
function isEmpty(value) {
return value == null; // Intentionally catches null and undefined
}Incorrect (changes short-circuit semantics):
# Before: returns 0 if items is empty list
def first_or_zero(items):
return items[0] if items else 0
# After "simplification": uses or
def first_or_zero(items):
return (items and items[0]) or 0
# Breaks: returns 0 when items[0] is 0, empty string, False, etc.Correct (preserve original semantics):
def first_or_zero(items):
return items[0] if items else 0Incorrect (changes nil vs empty slice semantics):
// Before: returns nil when no results found
func findUsers(query string) []User {
rows := db.Query(query)
if len(rows) == 0 {
return nil
}
return rows
}
// After "simplification": always returns initialized slice
func findUsers(query string) []User {
results := make([]User, 0)
rows := db.Query(query)
results = append(results, rows...)
return results
}
// Breaks: if findUsers("inactive") == nil { showEmptyState() }
// json.Marshal(nil slice) = "null", json.Marshal(empty slice) = "[]"When NOT to Apply
- When fixing a bug caused by incorrect semantics
- When the semantic difference is explicitly documented and approved
- When migrating to stricter types with full test coverage
Benefits
- Edge cases continue working correctly
- No silent failures in production
- Behavior matches developer expectations
- Tests remain valid without modification
Preserve Public Function Signatures and Types
Public APIs are contracts with consumers. Changing parameter order, making required params optional, narrowing accepted types, or modifying return types breaks every caller. Internal simplification must never leak into public interfaces.
Incorrect (changes parameter order):
// Before: well-established API
function formatDate(date: Date, format: string, locale?: string): string {
// ...
}
// After "simplification": reordered for "consistency"
function formatDate(format: string, date: Date, locale?: string): string {
// ...
}
// Breaks: formatDate(new Date(), 'YYYY-MM-DD')Correct (preserve original signature, simplify internals):
function formatDate(date: Date, format: string, locale?: string): string {
const loc = locale ?? 'en-US';
return new Intl.DateTimeFormat(loc, parseFormat(format)).format(date);
}Incorrect (narrows accepted types):
# Before: accepts any iterable
def process_items(items: Iterable[str]) -> list[str]:
return [transform(item) for item in items]
# After "simplification": requires list
def process_items(items: list[str]) -> list[str]:
return [transform(item) for item in items]
# Breaks: process_items(generator_expression)Correct (preserves type flexibility):
def process_items(items: Iterable[str]) -> list[str]:
return [transform(item) for item in items]Incorrect (makes required parameter optional):
// Before: explicit required config
func NewClient(config Config) *Client {
return &Client{config: config}
}
// After "simplification": optional with default
func NewClient(config ...Config) *Client {
cfg := Config{}
if len(config) > 0 {
cfg = config[0]
}
return &Client{config: cfg}
}
// Breaks: compile-time safety, caller may forget configCorrect (preserve required parameter):
func NewClient(config Config) *Client {
return &Client{config: config}
}Benefits
- All existing callers continue working
- No breaking changes in library versions
- Type safety maintained for consumers
- Documentation remains accurate
Preserve Error Messages, Types, and Handling
Error behavior is part of your API contract. Changing exception types, error messages, or when errors are thrown breaks catch blocks, monitoring systems, and user expectations. Simplify error handling code only when the observable error behavior remains identical.
Incorrect (changes exception type):
// Before: throws specific error type
function parseConfig(json: string): Config {
try {
return JSON.parse(json);
} catch (e) {
throw new ConfigParseError(`Invalid config: ${e.message}`);
}
}
// After "simplification": throws generic error
function parseConfig(json: string): Config {
return JSON.parse(json); // Now throws SyntaxError instead
}
// Breaks: catch (e) { if (e instanceof ConfigParseError) ... }Correct (preserves error type and message format):
function parseConfig(json: string): Config {
try {
return JSON.parse(json);
} catch (e) {
throw new ConfigParseError(`Invalid config: ${(e as Error).message}`);
}
}Incorrect (changes error message):
# Before: specific error message
def validate_age(age: int) -> None:
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age cannot exceed 150")
# After "simplification": combined validation
def validate_age(age: int) -> None:
if not 0 <= age <= 150:
raise ValueError("Invalid age") # Different message!
# Breaks: tests checking for specific error messagesCorrect (preserves original error messages):
def validate_age(age: int) -> None:
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age cannot exceed 150")When NOT to Apply
- When explicitly tasked with improving error messages
- When error messages contain sensitive information that should be removed
- When consolidating truly duplicate error paths (same message, same type)
Benefits
- Catch blocks continue working correctly
- Monitoring and alerting rules remain valid
- User-facing error messages stay consistent
- Error documentation stays accurate
Preserve All Return Values and Outputs
Code simplification must never alter what a function returns. Even "equivalent" values like empty array vs null, or reordered object keys, can break downstream consumers. Every output - return values, yielded items, emitted events - must remain byte-for-byte identical.
Incorrect (changes return type from null to undefined):
// Before: returns null for missing users
function findUser(id: string): User | null {
const user = users.find(u => u.id === id);
if (!user) {
return null;
}
return user;
}
// After "simplification": now returns undefined
function findUser(id: string): User | undefined {
return users.find(u => u.id === id);
}
// Breaks: if (findUser(id) === null) { ... }Correct (preserves null return type):
function findUser(id: string): User | null {
return users.find(u => u.id === id) ?? null;
}Incorrect (changes output ordering):
# Before: returns sorted by insertion order
def get_config():
return {"host": "localhost", "port": 8080, "debug": True}
# After "simplification": alphabetical order
def get_config():
return dict(sorted({"debug": True, "host": "localhost", "port": 8080}.items()))
# Breaks: code that relies on iteration order or serializationCorrect (preserves original ordering):
def get_config():
return {"host": "localhost", "port": 8080, "debug": True}Benefits
- Downstream code continues working without modification
- Tests remain valid without updates
- API contracts stay honored
- Serialized outputs remain compatible
Preserve Side Effects (Logging, I/O, State Changes)
Side effects are often the most important part of a function. Logging enables debugging and auditing. I/O operations persist data. State mutations coordinate systems. Removing or reordering side effects during simplification can cause silent failures that only surface in production.
Incorrect (removes logging side effect):
// Before: logs for audit trail
async function transferFunds(from: Account, to: Account, amount: number) {
logger.info(`Transfer initiated: ${from.id} -> ${to.id}, amount: ${amount}`);
await from.debit(amount);
await to.credit(amount);
logger.info(`Transfer completed: ${from.id} -> ${to.id}`);
}
// After "simplification": removed "noisy" logging
async function transferFunds(from: Account, to: Account, amount: number) {
await from.debit(amount);
await to.credit(amount);
}
// Breaks: audit compliance, debugging capabilityCorrect (preserve all logging):
async function transferFunds(from: Account, to: Account, amount: number) {
logger.info(`Transfer initiated: ${from.id} -> ${to.id}, amount: ${amount}`);
await from.debit(amount);
await to.credit(amount);
logger.info(`Transfer completed: ${from.id} -> ${to.id}`);
}Incorrect (changes I/O timing):
# Before: writes happen in sequence
def save_order(order: Order) -> None:
db.save(order)
cache.invalidate(f"order:{order.id}")
search_index.update(order)
# After "simplification": parallel writes
async def save_order(order: Order) -> None:
await asyncio.gather(
db.save(order),
cache.invalidate(f"order:{order.id}"),
search_index.update(order)
)
# Breaks: cache invalidation may happen before db.save completesCorrect (preserve operation order):
def save_order(order: Order) -> None:
db.save(order)
cache.invalidate(f"order:{order.id}")
search_index.update(order)Incorrect (changes state mutation order):
// Before: specific mutation order
func (s *Server) Shutdown() {
s.mu.Lock()
s.accepting = false
s.mu.Unlock()
s.drainConnections()
s.closeListeners()
}
// After "simplification": reordered
func (s *Server) Shutdown() {
s.closeListeners() // Moved up
s.mu.Lock()
s.accepting = false
s.mu.Unlock()
s.drainConnections()
}
// Breaks: new connections may be accepted after listeners closedCorrect (preserve mutation order):
func (s *Server) Shutdown() {
s.mu.Lock()
s.accepting = false
s.mu.Unlock()
s.drainConnections()
s.closeListeners()
}Benefits
- Debugging and monitoring capabilities preserved
- Data consistency maintained
- Race conditions avoided
- Audit trails remain intact
Verify Behavior Preservation Before Finalizing Changes
Never assume a simplification preserves behavior - verify it. Run existing tests, manually test edge cases, and review diffs for semantic changes. The confidence that code "looks equivalent" is insufficient. Verification must be explicit and documented.
Incorrect (commits without verification):
// Developer thinks: "This is obviously the same"
// Before
function isValid(x: number): boolean {
if (x > 0) {
if (x < 100) {
return true;
}
}
return false;
}
// After "simplification" - commits directly
function isValid(x: number): boolean {
return x > 0 && x < 100;
}
// Forgot to run tests, missed that NaN handling differsCorrect (verifies before committing):
// 1. Run existing tests first
// $ npm test -- --grep "isValid"
// 2. Check edge cases manually
console.log(isValid(NaN)); // Before: false, After: false ✓
console.log(isValid(0)); // Before: false, After: false ✓
console.log(isValid(100)); // Before: false, After: false ✓
console.log(isValid(50)); // Before: true, After: true ✓
// 3. Review diff for semantic changes
// git diff --word-diff
// 4. Then commit with verification note
function isValid(x: number): boolean {
return x > 0 && x < 100;
}Verification Checklist
Before finalizing any simplification:
1. Run existing tests - All tests must pass without modification 2. Check edge cases - null, undefined, empty, zero, negative, boundary values 3. Review error paths - Exceptions, error messages, failure modes 4. Verify side effects - Logging, I/O, state changes still occur 5. Test with real data - If available, run against production-like inputs 6. Review the diff - Look for type changes, reordering, removed code
Verification Commands by Language
# TypeScript/JavaScript
npm test
npx tsc --noEmit
# Python
pytest
mypy .
# Go
go test ./...
go vet ./...
# Rust
cargo test
cargo clippyWhen Tests Are Insufficient
If existing tests do not cover the modified code:
1. Add tests before simplifying - Write tests that capture current behavior 2. Use snapshot testing - Capture outputs before and after 3. Property-based testing - Generate inputs to find edge cases 4. Manual verification - Document what was manually checked
Benefits
- Catches behavior changes before they reach production
- Builds confidence in simplifications
- Documents verification for reviewers
- Creates a safety net for future changes
Check for Linting and Formatting Configs
Linting and formatting tools enforce project-wide code standards automatically. Simplifications that ignore these configs will fail CI checks or introduce inconsistent formatting. Always detect and respect ESLint, Prettier, rustfmt, Biome, and similar tool configurations before making changes.
Incorrect (ignoring project's Prettier config):
// Simplified code uses single quotes, but project's .prettierrc specifies double quotes
const message = 'Hello, world';
const items = ['one', 'two', 'three'];
function greet(name) {
return 'Hello, ' + name;
}Correct (matching project's Prettier config with double quotes):
// .prettierrc: { "singleQuote": false }
const message = "Hello, world";
const items = ["one", "two", "three"];
function greet(name) {
return "Hello, " + name;
}Config files to check:
- JavaScript/TypeScript:
.eslintrc.*,.prettierrc.*,biome.json,.editorconfig - Rust:
rustfmt.toml,.rustfmt.toml - Python:
pyproject.toml,.flake8,.black,ruff.toml - Go:
.golangci.yml
Benefits:
- CI passes on first push
- Code matches existing project formatting
- No manual reformatting required after review
References:
- Run
npm run lintor equivalent before committing - Use editor integrations to auto-format on save
Match Existing Code Style in File and Project
Every codebase develops its own idioms and patterns over time. Simplifications that introduce foreign patterns, even if technically better, create jarring inconsistencies. Before simplifying, study the surrounding code to understand naming conventions, error handling approaches, and structural patterns already in use.
Incorrect (introducing arrow functions in a file using function declarations):
// Existing file uses function declarations throughout
function validateEmail(email) {
return email.includes("@");
}
function validatePhone(phone) {
return phone.length === 10;
}
// New simplified code introduces arrow functions
const validateName = (name) => name.length > 0;
const validateAge = (age) => age >= 0 && age <= 150;Correct (matching existing function declaration pattern):
// Existing file uses function declarations throughout
function validateEmail(email) {
return email.includes("@");
}
function validatePhone(phone) {
return phone.length === 10;
}
// Simplified code follows existing pattern
function validateName(name) {
return name.length > 0;
}
function validateAge(age) {
return age >= 0 && age <= 150;
}Patterns to observe:
- Function declaration style (arrow vs function keyword)
- Naming conventions (camelCase, snake_case, prefixes)
- Import organization and grouping
- Error handling approach (try/catch, Result types, error callbacks)
- Comment style and documentation format
When NOT to use:
- When explicitly refactoring to modernize the entire file
- When existing patterns are acknowledged anti-patterns by the team
Benefits:
- Code reads as if written by one author
- Easier to maintain and understand
- Reduces cognitive load during code review
Project Conventions Override Generic Best Practices
Generic best practices are starting points, not absolute rules. Teams adopt specific conventions for good reasons: domain requirements, performance constraints, team preferences, or historical decisions. When project conventions conflict with generic guidance, always follow the project's established approach.
Incorrect (applying generic "arrow function" pattern against project convention):
// Project convention: use function declarations for top-level functions
// Developer applies generic "arrow functions are cleaner" simplification
// Existing file uses function declarations throughout
function validateEmail(email) {
return email.includes("@");
}
// Developer "simplifies" new additions to arrow functions
const validatePhone = (phone) => /^\d{10}$/.test(phone);
const validateAge = (age) => age >= 0 && age <= 150;Correct (following project's function declaration convention):
// Project convention documented: "Use function declarations for all named functions"
// This team prefers hoisting behavior and consistent style
function validateEmail(email) {
return email.includes("@");
}
function validatePhone(phone) {
return /^\d{10}$/.test(phone);
}
function validateAge(age) {
return age >= 0 && age <= 150;
}Common conflicts:
- Early returns vs explicit else blocks
- Inline conditionals vs extracted functions
- ORM usage vs raw SQL (some teams mandate one)
- Dependency injection style
- Test naming conventions
When to propose changes to conventions:
- Open a discussion with the team first
- Document the reasoning for the change
- Apply consistently across the codebase, not piecemeal
Benefits:
- Respects team decisions and domain knowledge
- Avoids religious debates about style
- Simplifications are accepted without friction
References:
- Check team wikis, ADRs (Architecture Decision Records)
- Ask maintainers when conventions seem unusual
Always Read CLAUDE.md Before Simplifying
Project-specific instructions in CLAUDE.md define how code should be written, tested, and structured. Ignoring these instructions leads to simplifications that violate project standards, causing rework and rejected changes. Reading CLAUDE.md first ensures your simplifications align with the team's established practices.
Incorrect (simplifying without reading project instructions):
// Developer simplifies by removing "unnecessary" error handling
// Not knowing CLAUDE.md requires explicit error boundaries
function fetchUser(id: string) {
return api.get(`/users/${id}`);
}Correct (reading CLAUDE.md first, respecting error handling requirement):
// CLAUDE.md specifies: "All API calls must have explicit error handling"
function fetchUser(id: string) {
try {
return api.get(`/users/${id}`);
} catch (error) {
throw new UserFetchError(id, error);
}
}When NOT to use:
- When CLAUDE.md does not exist in the project
- For exploratory analysis where no changes will be made
Benefits:
- Simplifications are accepted on first review
- Maintains consistency with team expectations
- Avoids introducing patterns the team has explicitly rejected
References:
- Check
.claude/CLAUDE.md,CLAUDE.md, and parent directory CLAUDE.md files - Look for
AGENTS.mdor similar instruction files
Delete Code, Never Comment It Out
Commented-out code is visual noise that raises unanswerable questions: Is this a work in progress? A rollback option? An alternative approach? Code archaeology shows commented blocks are almost never uncommented - they just accumulate. Version control exists specifically to recover old code; use it.
Incorrect (commented code preserved):
export async function fetchUserData(userId) {
// const cachedUser = cache.get(userId);
// if (cachedUser) {
// return cachedUser;
// }
const response = await api.get(`/users/${userId}`);
// Old implementation - keeping just in case
// const response = await fetch(`${API_URL}/users/${userId}`, {
// headers: { 'Authorization': `Bearer ${token}` },
// cache: 'no-store'
// });
// if (!response.ok) {
// throw new Error(`HTTP ${response.status}`);
// }
// const data = await response.json();
// TODO: add caching back when Redis is configured
// cache.set(userId, response.data, TTL);
return response.data;
}Correct (clean code, history in git):
export async function fetchUserData(userId) {
const response = await api.get(`/users/${userId}`);
return response.data;
}Incorrect (multiple commented alternatives):
class OrderProcessor:
def calculate_tax(self, order):
# V1: Simple percentage
# return order.subtotal * 0.08
# V2: State-based tax
# tax_rate = TAX_RATES.get(order.state, 0.0)
# return order.subtotal * tax_rate
# V3: External tax service (current)
return self.tax_service.calculate(order)
# V4: Planned - tax by item category
# total_tax = 0
# for item in order.items:
# rate = CATEGORY_RATES.get(item.category, DEFAULT_RATE)
# total_tax += item.price * rate
# return total_taxCorrect (single implementation, alternatives in git history or issues):
class OrderProcessor:
def calculate_tax(self, order):
return self.tax_service.calculate(order)Incorrect (debug code commented):
func ProcessBatch(items []Item) error {
// fmt.Printf("DEBUG: Processing %d items\n", len(items))
for i, item := range items {
// fmt.Printf("DEBUG: Item %d: %+v\n", i, item)
if err := process(item); err != nil {
// fmt.Printf("DEBUG: Error at %d: %v\n", i, err)
return fmt.Errorf("item %d: %w", i, err)
}
}
// fmt.Println("DEBUG: Batch complete")
return nil
}Correct (use proper logging or delete debug code):
func ProcessBatch(items []Item) error {
for i, item := range items {
if err := process(item); err != nil {
return fmt.Errorf("item %d: %w", i, err)
}
}
return nil
}Recovery Workflow
When you need old code back: 1. git log --oneline -20 - find the commit 2. git show <commit>:path/to/file - view the old version 3. git checkout <commit> -- path/to/file - restore if needed
When NOT to Apply
- Active debugging session (delete before committing)
- Code review showing before/after for context (use diff view instead)
- Documentation explicitly showing what NOT to do
Benefits
- Files are 10-30% shorter without commented code blocks
- Clear intent: every line of code has a purpose
- No confusion about what's active vs inactive
- git blame shows actual history, not comment archaeology
- Encourages proper use of version control
Keep Comments That Explain Why, Not What
Code shows what happens; only comments can explain why. Keep comments that capture business decisions, workarounds, non-obvious constraints, and historical context. These comments prevent future developers from "fixing" intentional behavior or repeating mistakes that led to the current implementation.
Incorrect (removing valuable context):
// Before removing comments:
function processPayment(amount: number): boolean {
// Visa requires amounts in cents, not dollars
const centAmount = Math.round(amount * 100);
// Retry limit set by PCI compliance audit (see JIRA-4521)
const maxRetries = 3;
// Must sleep 100ms between retries - payment gateway rate limits
// Discovered during Black Friday 2023 outage
for (let i = 0; i < maxRetries; i++) {
if (gateway.charge(centAmount)) return true;
sleep(100);
}
return false;
}
// After overzealous cleanup - WRONG:
function processPayment(amount: number): boolean {
const centAmount = Math.round(amount * 100);
const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
if (gateway.charge(centAmount)) return true;
sleep(100);
}
return false;
}
// Future dev removes sleep(100) as "unnecessary" -> outage
// Future dev changes maxRetries to 10 -> compliance violationCorrect (preserve why comments):
function processPayment(amount: number): boolean {
// Visa requires amounts in cents, not dollars
const centAmount = Math.round(amount * 100);
// Retry limit set by PCI compliance audit (JIRA-4521)
const maxRetries = 3;
// 100ms delay required by payment gateway rate limits (Black Friday 2023 postmortem)
for (let i = 0; i < maxRetries; i++) {
if (gateway.charge(centAmount)) return true;
sleep(100);
}
return false;
}Comments worth keeping:
# Business rule: orders over $10k require manual approval (legal requirement)
if order.total > 10000:
order.status = "pending_review"
# HACK: Safari doesn't support this API before v15, polyfill only for Safari
# Remove after dropping Safari 14 support (EOL March 2024)
if is_safari and version < 15:
load_polyfill()
# WARNING: DO NOT change sort order - downstream systems depend on this
# exact ordering for invoice reconciliation (see incident #892)
transactions.sort(key=lambda t: (t.date, t.id))
# Performance: using raw SQL because ORM generates N+1 queries here
# Benchmarked: 50ms vs 2.3s for 1000 records
cursor.execute(RAW_QUERY, params)
# Counterintuitive: we check for empty BEFORE validation because
# empty arrays are valid (user clearing their cart) but the validator
# rejects them. Business confirmed this behavior in Q3 product review.
if not items:
return Success(empty_cart)Comments to remove vs keep:
// REMOVE: States the obvious
const isActive = user.status === 'active'; // Check if user is active
// KEEP: Explains non-obvious business rule
const isActive = user.status === 'active'; // "suspended" users can still view but not purchase
// REMOVE: Describes the code
for (const item of items) { // Loop through items
process(item);
}
// KEEP: Explains why this approach was chosen
for (const item of items) { // forEach breaks on async - must use for...of
await process(item);
}
// REMOVE: Documents obvious type
let count: number = 0; // Number to track count
// KEEP: Documents why type is unusual
let count: number = 0; // Must be number (not bigint) for legacy API compatibilityCategories of Why Comments Worth Keeping
1. Business rules: Legal, compliance, contractual requirements 2. Workarounds: Browser bugs, library quirks, API limitations 3. Performance: Why a non-obvious approach was faster 4. Historical: Incidents, postmortems, decisions that led here 5. Warnings: Things that look wrong but are intentional
Benefits
- Preserves institutional knowledge
- Prevents regression of intentional behavior
- Reduces "why is this here?" Slack messages
- Makes code archaeology faster during incidents
- Protects against well-meaning "cleanup" that breaks things
Remove Comments That State the Obvious
Comments that repeat what the code says add noise without value. They double the reading burden and inevitably drift out of sync with the code they describe. Well-named variables and functions are self-documenting; comments should add context the code cannot express.
Incorrect (comments repeat the code):
// Import React
import React from 'react';
// User interface
interface User {
id: string; // The user's ID
name: string; // The user's name
email: string; // The user's email
age: number; // The user's age
}
// Function to get user by ID
function getUserById(id: string): User | null {
// Find the user in the array
const user = users.find(u => u.id === id);
// If user is not found, return null
if (!user) {
return null;
}
// Return the user
return user;
}
// Increment counter
counter++;
// Set loading to true
setLoading(true);
// Loop through items
for (const item of items) {
// Process the item
processItem(item);
}Correct (self-documenting code, no redundant comments):
import React from 'react';
interface User {
id: string;
name: string;
email: string;
age: number;
}
function getUserById(id: string): User | null {
return users.find(u => u.id === id) ?? null;
}
counter++;
setLoading(true);
for (const item of items) {
processItem(item);
}Incorrect (comments describe obvious logic):
def calculate_total(items):
# Initialize total to zero
total = 0
# Loop through each item
for item in items:
# Add the item price to total
total += item.price
# If item has discount, subtract it
if item.discount:
total -= item.discount
# Return the total
return totalCorrect (code speaks for itself):
def calculate_total(items):
total = 0
for item in items:
total += item.price
if item.discount:
total -= item.discount
return totalIncorrect (trivial getter/setter comments):
public class Product {
private String name;
private double price;
/**
* Gets the name.
* @return the name
*/
public String getName() {
return name;
}
/**
* Sets the name.
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the price.
* @return the price
*/
public double getPrice() {
return price;
}
}Correct (skip trivial documentation):
public class Product {
private String name;
private double price;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
}What Makes a Comment Obvious?
- Restates the identifier name:
// the user's nameforuserName - Describes the language construct:
// loop through itemsfor a for loop - Explains standard operations:
// return the result - Documents trivial getters/setters with no logic
When NOT to Apply
- Regex patterns that need explanation
- Complex algorithms that benefit from step descriptions
- Non-obvious hardcoded numbers or business rules
- Workarounds for known bugs or platform quirks
- Public API documentation (JSDoc, docstrings)
Benefits
- 30-50% fewer comment lines without information loss
- Comments that remain are actually valuable
- Reduces maintenance burden of keeping comments in sync
- Forces better naming instead of relying on comments
- Faster code scanning without visual noise
Remove Stale TODO/FIXME Comments
TODO and FIXME comments are promises that rarely get kept. They accumulate over months and years until codebases have hundreds of stale reminders that everyone ignores. Either convert TODOs to tracked issues with owners and deadlines, or delete them. If something matters, it belongs in your issue tracker.
Incorrect (accumulated stale TODOs):
export class UserService {
// TODO: add caching
// FIXME: this is slow
// TODO: refactor this whole class (2019-03-15)
// HACK: temporary fix for login bug
// XXX: revisit this later
async getUser(id: string): Promise<User> {
// TODO: handle the case where user doesn't exist
// FIXME: should validate id format
const user = await this.db.query(`SELECT * FROM users WHERE id = ?`, [id]);
// TODO: add proper error handling
// TODO: log this somewhere
// NOTE: Bob said to fix this - not sure what he meant
return user;
}
// TODO(john): finish implementing this
// TODO: write tests
async updateUser(id: string, data: Partial<User>): Promise<void> {
// FIXME: SQL injection vulnerability!!!
await this.db.query(`UPDATE users SET ${data} WHERE id = ${id}`);
}
}Correct (clean code, issues in tracker):
export class UserService {
async getUser(id: string): Promise<User | null> {
const user = await this.db.query(`SELECT * FROM users WHERE id = ?`, [id]);
return user ?? null;
}
async updateUser(id: string, data: Partial<User>): Promise<void> {
// Security: parameterized query prevents SQL injection
const setClauses = Object.keys(data).map(k => `${k} = ?`).join(', ');
await this.db.query(
`UPDATE users SET ${setClauses} WHERE id = ?`,
[...Object.values(data), id]
);
}
}
// Issue tracker entries created:
// - PROJ-1234: Add Redis caching layer to UserService
// - PROJ-1235: Add input validation for user ID format
// - PROJ-1236: Implement proper error handling in data layerIncorrect (TODOs with no actionable path):
# TODO: make this better
def process_data(data):
# FIXME: ugly code
result = []
for item in data:
# TODO: optimize this
if item.value > 0:
result.append(item)
# TODO: think about edge cases
return result
# TODO: might need to change this someday
MAX_RETRIES = 3
# FIXME: ???
def mystery_function():
passCorrect (either fix it or delete the noise):
def process_data(data):
return [item for item in data if item.value > 0]
MAX_RETRIES = 3
def mystery_function():
pass # Intentionally empty - interface placeholder for pluginsWhen a TODO is acceptable (temporarily):
// Acceptable: Specific, dated, with ticket reference
// TODO(2024-Q1): Remove after migrating to v2 API (JIRA-5678)
const legacyAdapter = createLegacyAdapter();
// Acceptable: Blocking issue documented
// FIXME: Blocked on upstream PR#123 - using workaround until merged
const result = workaroundFunction();
// Acceptable: Part of active PR/branch
// TODO: Add error handling - will address in next commitTODO Triage Process
When you find a TODO, decide: 1. Fix it now - If it takes < 5 minutes, just do it 2. Create issue - If it's real work, track it properly 3. Delete it - If it's vague, stale, or no longer relevant
Signs a TODO is Stale
- Date older than 6 months
- Author no longer on team
- References completed work or old versions
- Vague ("make better", "fix this", "???")
- No one knows why it's there
Benefits
- Issue tracker becomes single source of truth
- TODO count stays near zero (easy to spot new ones)
- Urgent FIXMEs don't get lost in noise
- Code review can focus on real issues
- New developers don't inherit archaeological debt
References
- Most IDEs can extract TODOs to tasks: "TODO to Issue" extensions
- CI tools can fail builds on TODO count thresholds
grep -r "TODO\|FIXME" --include="*.ts" | wc -lfor auditing
Delete Unused Code Artifacts
Unused variables, functions, imports, and parameters are noise that obscures the real logic. They force readers to track dead references, bloat bundle sizes, and create phantom dependencies that complicate refactoring. Modern tooling catches unused code - delete it immediately rather than leaving it "just in case."
Incorrect (unused artifacts throughout):
import { useState, useEffect, useCallback, useMemo } from 'react';
import { formatDate, parseDate, isValidDate } from './dateUtils';
import { API_URL, MAX_RETRIES, TIMEOUT } from './constants';
interface UserListProps {
users: User[];
onSelect: (user: User) => void;
showAvatar: boolean; // Never used in component
maxItems: number; // Never used in component
}
export function UserList({ users, onSelect, showAvatar, maxItems }: UserListProps) {
const [selectedId, setSelectedId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false); // Never used
const [error, setError] = useState<Error | null>(null); // Never used
const sortedUsers = useMemo(() => users, [users]); // Memo does nothing
const handleClick = useCallback((user: User) => {
setSelectedId(user.id);
onSelect(user);
}, [onSelect]);
const unusedHelper = (data: unknown) => { // Never called
return formatDate(data as Date);
};
return (
<ul>
{sortedUsers.map(user => (
<li key={user.id} onClick={() => handleClick(user)}>
{user.name}
</li>
))}
</ul>
);
}Correct (only what's needed):
import { useState, useCallback } from 'react';
interface UserListProps {
users: User[];
onSelect: (user: User) => void;
}
export function UserList({ users, onSelect }: UserListProps) {
const [selectedId, setSelectedId] = useState<string | null>(null);
const handleClick = useCallback((user: User) => {
setSelectedId(user.id);
onSelect(user);
}, [onSelect]);
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => handleClick(user)}>
{user.name}
</li>
))}
</ul>
);
}Incorrect (unused function parameters):
def calculate_discount(price, quantity, customer_type, region, currency, promo_code):
"""Only price and quantity are actually used."""
return price * quantity * 0.9
def process_order(order, config, logger, metrics, cache):
"""Only order is used."""
return order.totalCorrect (remove unused parameters or mark intentionally ignored):
def calculate_discount(price, quantity):
return price * quantity * 0.9
def process_order(order, _config, _logger, _metrics, _cache):
"""Underscore prefix signals intentionally unused (interface compliance)."""
return order.totalWhen NOT to Apply
- Interface compliance requires the parameter signature (use underscore prefix)
- Public API where removal would be a breaking change
- Parameters used only in debug/logging modes
- Imports used only in type annotations (may appear unused to some tools)
Benefits
- 20-40% reduction in lines of code in cluttered files
- Faster code comprehension for new developers
- Smaller bundle sizes (tree-shaking works better)
- Clearer dependency graphs for refactoring tools
- Eliminates false positives in static analysis
References
- ESLint: no-unused-vars, no-unused-imports
- TypeScript: noUnusedLocals, noUnusedParameters
- Python: F401 (unused import), F841 (unused variable)
Prefer Duplication Over Premature Abstraction
Three similar lines of code are often better than a premature abstraction. The wrong abstraction creates coupling between unrelated concepts, making future changes ripple across the codebase. Duplication is cheap to fix later when you understand the true pattern; bad abstractions are expensive to unwind because other code depends on them.
Incorrect (premature abstraction couples unrelated concepts):
// "Both format currency, let's make it generic!"
function formatValue(value: number, type: 'price' | 'salary' | 'discount'): string {
const symbol = type === 'discount' ? '-$' : '$';
const decimals = type === 'salary' ? 0 : 2;
const prefix = type === 'price' ? '' : ' ';
return `${prefix}${symbol}${value.toFixed(decimals)}`;
}
// Now price formatting changes require checking salary and discount logic
// And discount needs a negative sign but price doesn't
// The abstraction becomes a mess of special casesCorrect (explicit duplication until patterns emerge):
function formatPrice(cents: number): string {
return `$${(cents / 100).toFixed(2)}`;
}
function formatSalary(annual: number): string {
return `$${annual.toLocaleString()}`;
}
function formatDiscount(cents: number): string {
return `-$${(cents / 100).toFixed(2)}`;
}
// Each can evolve independently
// formatPrice might add currency symbols later
// formatSalary might add "per year" suffix
// formatDiscount might show percentage insteadIncorrect (abstraction forces unrelated code to change together):
class EntityProcessor:
"""Processes users, orders, and products - they're all 'entities'!"""
def process(self, entity, entity_type):
if entity_type == 'user':
self._validate_user(entity)
self._save_user(entity)
elif entity_type == 'order':
self._validate_order(entity)
self._calculate_totals(entity)
self._save_order(entity)
elif entity_type == 'product':
self._validate_product(entity)
self._update_inventory(entity)
self._save_product(entity)
# Adding a new entity type or changing one affects this god class
# User processing doesn't need inventory; order doesn't need user validationCorrect (separate concerns, accept some duplication):
class UserService:
def create_user(self, data):
self._validate(data)
return self.repository.save(data)
class OrderService:
def create_order(self, data):
self._validate(data)
self._calculate_totals(data)
return self.repository.save(data)
class ProductService:
def create_product(self, data):
self._validate(data)
self._update_inventory(data)
return self.repository.save(data)
# Yes, each has _validate and save - that's okay
# They can diverge without affecting each otherIncorrect (abstracting over superficial similarity):
// "Both take a slice and return one element!"
func getOne[T any](items []T, selector func([]T) T) T {
return selector(items)
}
// Callers become puzzling
first := getOne(users, func(u []User) User { return u[0] })
last := getOne(orders, func(o []Order) Order { return o[len(o)-1] })
random := getOne(products, func(p []Product) Product {
return p[rand.Intn(len(p))]
})Correct (just write the simple code):
first := users[0]
last := orders[len(orders)-1]
random := products[rand.Intn(len(products))]
// Obvious, no abstraction neededIncorrect (DRY violation fear leads to tight coupling):
// Shared validation "to avoid duplication"
const sharedValidation = {
validateEmail: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
validatePhone: (phone) => /^\d{10}$/.test(phone),
};
// Now used by User, Contact, Company, Lead...
// Phone validation needs to change for international numbers
// But only for Company and Lead, not User
// The "shared" code becomes a liabilityCorrect (duplicate until requirements clarify):
// User module
function validateUserPhone(phone) {
return /^\d{10}$/.test(phone); // US only for users
}
// Company module
function validateCompanyPhone(phone) {
return /^\+?[\d\s-]{10,}$/.test(phone); // International for business
}
// When you KNOW they should be the same, then extractThe Sandi Metz Rule
"Duplication is far cheaper than the wrong abstraction." If you're unsure whether code should be shared:
1. Duplicate it 2. Wait for the third occurrence 3. Look for the true underlying pattern 4. Then extract with confidence
When Abstraction IS Appropriate
- Business rule that must be consistent (tax calculation, authentication)
- Algorithm that is complex and needs isolated testing
- Pattern that has appeared 3+ times with identical structure
- Domain concept with a clear name in the ubiquitous language
Benefits
- Code changes are localized to one area
- New requirements don't break unrelated features
- Easier to understand - each module is self-contained
- Refactoring is straightforward when patterns emerge
Use Data-Driven Patterns Over Repetitive Conditionals
When you have repetitive if/else or switch statements that map inputs to outputs, replace them with data structures like maps, lookup tables, or configuration objects. Data-driven code is easier to extend (add a line, not a branch), easier to test (validate the data structure), and less prone to copy-paste errors.
Incorrect (repetitive conditionals):
function getStatusColor(status: string): string {
if (status === 'pending') {
return '#FFA500';
} else if (status === 'approved') {
return '#00FF00';
} else if (status === 'rejected') {
return '#FF0000';
} else if (status === 'cancelled') {
return '#808080';
} else if (status === 'processing') {
return '#0000FF';
} else if (status === 'completed') {
return '#00AA00';
} else {
return '#000000';
}
}
// Adding a new status = new branch + risk of typoCorrect (data-driven lookup):
const STATUS_COLORS: Record<string, string> = {
pending: '#FFA500',
approved: '#00FF00',
rejected: '#FF0000',
cancelled: '#808080',
processing: '#0000FF',
completed: '#00AA00',
};
function getStatusColor(status: string): string {
return STATUS_COLORS[status] ?? '#000000';
}
// Adding a new status = one line in the mapIncorrect (switch statement for validation rules):
def validate_field(field_type, value):
if field_type == 'email':
if not re.match(r'^[\w.-]+@[\w.-]+\.\w+$', value):
return 'Invalid email format'
elif field_type == 'phone':
if not re.match(r'^\d{10}$', value):
return 'Phone must be 10 digits'
elif field_type == 'zip':
if not re.match(r'^\d{5}(-\d{4})?$', value):
return 'Invalid ZIP code'
elif field_type == 'ssn':
if not re.match(r'^\d{3}-\d{2}-\d{4}$', value):
return 'Invalid SSN format'
# ... 20 more field types
return NoneCorrect (configuration-driven validation):
FIELD_VALIDATORS = {
'email': (r'^[\w.-]+@[\w.-]+\.\w+$', 'Invalid email format'),
'phone': (r'^\d{10}$', 'Phone must be 10 digits'),
'zip': (r'^\d{5}(-\d{4})?$', 'Invalid ZIP code'),
'ssn': (r'^\d{3}-\d{2}-\d{4}$', 'Invalid SSN format'),
}
def validate_field(field_type: str, value: str) -> str | None:
if field_type not in FIELD_VALIDATORS:
return None
pattern, error_message = FIELD_VALIDATORS[field_type]
if not re.match(pattern, value):
return error_message
return None
# New field type = one line in FIELD_VALIDATORSIncorrect (factory with repetitive instantiation):
func CreateHandler(handlerType string) Handler {
switch handlerType {
case "json":
return &JSONHandler{
ContentType: "application/json",
Encoder: json.Marshal,
}
case "xml":
return &XMLHandler{
ContentType: "application/xml",
Encoder: xml.Marshal,
}
case "yaml":
return &YAMLHandler{
ContentType: "application/yaml",
Encoder: yaml.Marshal,
}
// ... more handlers
}
return nil
}Correct (registry pattern):
type HandlerConfig struct {
ContentType string
Encoder func(any) ([]byte, error)
}
var handlerRegistry = map[string]HandlerConfig{
"json": {"application/json", json.Marshal},
"xml": {"application/xml", xml.Marshal},
"yaml": {"application/yaml", yaml.Marshal},
}
func CreateHandler(handlerType string) Handler {
config, ok := handlerRegistry[handlerType]
if !ok {
return nil
}
return NewHandler(config)
}
// Adding a handler = one line in registryIncorrect (under-abstraction - missing data-driven opportunity):
function calculateShipping(country) {
if (country === 'US') return 5.99;
if (country === 'CA') return 9.99;
if (country === 'MX') return 12.99;
if (country === 'UK') return 15.99;
if (country === 'DE') return 15.99;
if (country === 'FR') return 15.99;
if (country === 'JP') return 25.99;
if (country === 'AU') return 29.99;
return 35.99; // International
}
// Europe has same price but requires 3 linesCorrect (data structure reveals patterns):
const SHIPPING_RATES = {
domestic: { US: 5.99 },
northAmerica: { CA: 9.99, MX: 12.99 },
europe: { UK: 15.99, DE: 15.99, FR: 15.99, ES: 15.99, IT: 15.99 },
pacific: { JP: 25.99, AU: 29.99, NZ: 29.99 },
default: 35.99,
};
function calculateShipping(country) {
for (const region of Object.values(SHIPPING_RATES)) {
if (typeof region === 'object' && country in region) {
return region[country];
}
}
return SHIPPING_RATES.default;
}
// Structure reveals business logic: regions have shared pricingWhen NOT to Use Data-Driven
- When branches have complex logic, not just value mapping
- When there are only 2-3 cases and they're unlikely to grow
- When the "data" would be as complex as the code it replaces
Benefits
- Adding new cases is a configuration change, not code change
- Data can be loaded from external sources (database, config files)
- Easier to test - validate the data structure separately
- Business rules are visible at a glance in the data structure
- Reduces cyclomatic complexity significantly
Extract Only When It Improves Clarity
Extraction should make code easier to understand, not just shorter. If the extracted function name doesn't communicate intent better than the original code, keep the code inline. A helper called processData() or handleStuff() forces readers to look inside anyway. The goal is comprehension, not line count reduction.
Incorrect (extraction hides intent behind vague name):
async function checkout(cart: Cart): Promise<Order> {
const order = await processCartData(cart);
await finalizeOrder(order);
return order;
}
// What does this do? Must read implementation
async function processCartData(cart: Cart): Promise<Order> {
const items = cart.items.filter(item => item.quantity > 0);
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * 0.08;
return { items, subtotal, tax, total: subtotal + tax };
}Correct (inline code that speaks for itself):
async function checkout(cart: Cart): Promise<Order> {
const items = cart.items.filter(item => item.quantity > 0);
const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
const tax = subtotal * 0.08;
const order: Order = { items, subtotal, tax, total: subtotal + tax };
await this.orderRepository.save(order);
await this.emailService.sendConfirmation(order);
return order;
}
// Business logic is immediately visibleIncorrect (extracting trivial operations):
def create_report(data):
filtered = filter_empty_records(data)
sorted_data = sort_by_date(filtered)
return format_as_table(sorted_data)
def filter_empty_records(data):
return [r for r in data if r] # One-liner hidden behind function
def sort_by_date(data):
return sorted(data, key=lambda x: x['date']) # One-liner hidden
def format_as_table(data):
# Actually complex - this extraction is justified
...Correct (extract complex, inline simple):
def create_report(data):
records = [r for r in data if r] # Filter empty - obvious
records.sort(key=lambda x: x['date']) # Sort by date - obvious
return format_as_table(records) # Complex formatting - good extraction
def format_as_table(records):
"""Convert records to ASCII table with headers and alignment."""
# 30 lines of table formatting logic
...Incorrect (generic names that require reading implementation):
func HandleRequest(r *Request) (*Response, error) {
if err := doValidation(r); err != nil {
return nil, err
}
result := doProcessing(r)
return doFormatting(result), nil
}
func doValidation(r *Request) error { ... }
func doProcessing(r *Request) *Result { ... }
func doFormatting(r *Result) *Response { ... }
// "do" prefix is a code smell - names don't describe behaviorCorrect (names that describe actual behavior):
func HandleRequest(r *Request) (*Response, error) {
if err := validateRequestSchema(r); err != nil {
return nil, err
}
inventory := checkInventoryLevels(r.Items)
return formatInventoryResponse(inventory), nil
}
func validateRequestSchema(r *Request) error { ... }
func checkInventoryLevels(items []Item) *InventoryStatus { ... }
func formatInventoryResponse(status *InventoryStatus) *Response { ... }
// Each name tells you what the function does without reading itIncorrect (DRY obsession - extracting unrelated similar code):
// "These both iterate arrays, let's extract!"
function processItems(items, processor) {
const results = [];
for (const item of items) {
results.push(processor(item));
}
return results;
}
// Callers now obscure their intent
const prices = processItems(products, p => p.price);
const names = processItems(users, u => u.name);Correct (use language features, keep intent clear):
const prices = products.map(p => p.price);
const names = users.map(u => u.name);
// Array.map is universally understood - no custom abstraction neededSigns That Extraction Improves Clarity
- The function name explains a "why" that code alone doesn't convey
- The function encapsulates a business rule that has a domain name
- Readers can understand the calling code without reading the helper
- The extracted logic is cohesive and represents one concept
Signs That Extraction Hurts Clarity
- You struggle to name the function (vague names like
process,handle,do) - The function name is longer than the code it contains
- Readers still need to check the implementation to understand the caller
- The extraction groups unrelated operations
Benefits
- Code reads like documentation of business logic
- Reduced navigation between files and functions
- Function names serve as accurate documentation
- New team members understand code faster
Avoid Single-Use Helper Functions
Do not extract code into a helper function if it will only be called from one place. Single-use helpers fragment logic across multiple locations, forcing readers to jump between files or scroll to understand what should be a single coherent operation. The cure is worse than the disease - you trade local complexity for distributed complexity.
Incorrect (single-use helper obscures logic):
// In userService.ts
async function createUser(data: UserInput): Promise<User> {
const validated = await validateUserInput(data);
const normalized = normalizeUserData(validated);
const user = await saveUser(normalized);
await sendWelcomeNotification(user);
return user;
}
// In userHelpers.ts - only called once!
function normalizeUserData(data: ValidatedUserInput): NormalizedUser {
return {
email: data.email.toLowerCase().trim(),
name: data.name.trim(),
createdAt: new Date(),
};
}
// Reader must now open another file to understand createUserCorrect (inline single-use logic):
async function createUser(data: UserInput): Promise<User> {
const validated = await validateUserInput(data);
const normalized: NormalizedUser = {
email: validated.email.toLowerCase().trim(),
name: validated.name.trim(),
createdAt: new Date(),
};
const user = await saveUser(normalized);
await sendWelcomeNotification(user);
return user;
}
// All logic visible in one placeIncorrect (over-extraction for "cleanliness"):
class OrderProcessor:
def process_order(self, order):
self._validate_inventory(order)
self._calculate_totals(order)
self._apply_discounts(order)
self._save_order(order)
def _validate_inventory(self, order):
# 3 lines of code, called only here
for item in order.items:
if item.quantity > self.inventory[item.sku]:
raise InsufficientInventoryError(item.sku)
def _calculate_totals(self, order):
# 2 lines of code, called only here
order.subtotal = sum(item.price * item.quantity for item in order.items)
order.total = order.subtotal + order.shipping
# ... more single-use private methods
# Class is now 150 lines but process_order reads like a mystery novelCorrect (inline when logic is simple and single-use):
class OrderProcessor:
def process_order(self, order):
# Validate inventory
for item in order.items:
if item.quantity > self.inventory[item.sku]:
raise InsufficientInventoryError(item.sku)
# Calculate totals
order.subtotal = sum(item.price * item.quantity for item in order.items)
order.total = order.subtotal + order.shipping
# Apply discounts (this IS complex - worth extracting)
self._apply_discount_rules(order)
self.repository.save(order)
# Comments provide structure; complex logic gets extractedIncorrect (extracting for testability when integration test suffices):
func ProcessPayment(order *Order) error {
if err := validatePaymentDetails(order); err != nil {
return err
}
return chargeCard(order)
}
// Extracted "for testability" but only called once
func validatePaymentDetails(order *Order) error {
if order.Amount <= 0 {
return errors.New("invalid amount")
}
if order.CardToken == "" {
return errors.New("missing card token")
}
return nil
}Correct (test through the public interface):
func ProcessPayment(order *Order) error {
if order.Amount <= 0 {
return errors.New("invalid amount")
}
if order.CardToken == "" {
return errors.New("missing card token")
}
return chargeCard(order)
}
// Test ProcessPayment with invalid inputs directlyWhen Single-Use Helpers ARE Appropriate
- Function exceeds 50 lines and has clear logical sections
- Logic requires extensive comments that would benefit from a descriptive name
- Testing the helper in isolation provides significant value
- The helper encapsulates a complex algorithm that deserves its own tests
Benefits
- Code flows linearly without jumping between locations
- Reduces file count and cognitive load
- Makes debugging easier - stack traces are shorter
- Faster code review - reviewers see complete logic in context
Apply the Rule of Three
Wait until code appears three times before extracting a helper. The first duplication might be coincidental. The second confirms a pattern exists. The third proves extraction is worthwhile. Extracting too early creates abstractions for patterns that never materialize, adding cognitive overhead without reducing maintenance burden.
Incorrect (extracting after first duplication):
// Two similar validation calls - someone extracts immediately
function validateUser(user: User) {
if (!user.email.includes('@')) throw new Error('Invalid email');
}
function validateContact(contact: Contact) {
if (!contact.email.includes('@')) throw new Error('Invalid email');
}
// Over-engineered "solution" after seeing just 2 occurrences
function validateEmail(entity: { email: string }, entityName: string) {
if (!entity.email.includes('@')) {
throw new Error(`Invalid ${entityName} email`);
}
}
// Now every caller needs to understand this abstraction
// And if requirements diverge, the abstraction becomes awkwardCorrect (wait for the third occurrence):
// First occurrence
function validateUser(user: User) {
if (!user.email.includes('@')) throw new Error('Invalid email');
}
// Second occurrence - note it, don't extract yet
function validateContact(contact: Contact) {
if (!contact.email.includes('@')) throw new Error('Invalid email');
}
// Third occurrence - NOW extract with confidence
function validateOrder(order: Order) {
if (!order.customerEmail.includes('@')) throw new Error('Invalid email');
}
// After third occurrence, extract with full understanding of the pattern
function isValidEmail(email: string): boolean {
return email.includes('@');
}Incorrect (under-extraction - ignoring obvious patterns):
# Same exact logic copy-pasted 5 times
def process_user_csv(filepath):
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
return [row for row in reader if row.get('active') == 'true']
def process_order_csv(filepath):
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
return [row for row in reader if row.get('active') == 'true']
def process_product_csv(filepath):
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
return [row for row in reader if row.get('active') == 'true']
# Bug fix needed? Change it in 5 placesCorrect (extract after pattern is established):
def load_active_records(filepath: str) -> list[dict]:
"""Load records from CSV where active='true'."""
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
return [row for row in reader if row.get('active') == 'true']
# Clear, single source of truth
users = load_active_records('users.csv')
orders = load_active_records('orders.csv')
products = load_active_records('products.csv')When NOT to Apply
- Security-critical code (authentication, authorization) - extract immediately
- Complex algorithms where bugs are likely - extract on second occurrence
- When business logic dictates the pattern will recur (known requirements)
Benefits
- Abstractions are based on real patterns, not speculation
- Fewer premature abstractions that need refactoring later
- Code readers encounter simpler, more concrete code
- Easier to understand what each piece of code actually does
Simplify Boolean Expressions
Complex boolean expressions with double negations, redundant comparisons, and tangled logic slow readers down and hide bugs. Apply De Morgan's laws, remove double negations, and compare directly to booleans to make conditions express their intent in the simplest possible form.
Incorrect (double negations and redundant comparisons):
function shouldShowBanner(user: User, settings: Settings): boolean {
if (!(!user.isSubscribed)) {
return false;
}
if (user.dismissed !== true && !settings.bannersDisabled) {
return true;
}
return false;
}
function canAccess(role: string, isActive: boolean): boolean {
if (!(role !== 'admin' && role !== 'moderator') === false) {
return false;
}
return isActive !== false;
}Correct (simplified booleans):
function shouldShowBanner(user: User, settings: Settings): boolean {
if (user.isSubscribed) {
return false;
}
return !user.dismissed && !settings.bannersDisabled;
}
function canAccess(role: string, isActive: boolean): boolean {
if (role !== 'admin' && role !== 'moderator') {
return false;
}
return isActive;
}Incorrect (Python - verbose boolean logic):
def is_valid_order(order):
if not (order.total <= 0) and not (len(order.items) == 0):
if order.customer is not None and order.customer.verified == True:
return True
return FalseCorrect (Python - direct boolean expressions):
def is_valid_order(order):
has_items = len(order.items) > 0
has_positive_total = order.total > 0
has_verified_customer = order.customer is not None and order.customer.verified
return has_items and has_positive_total and has_verified_customerIncorrect (Go - nested negations in error checks):
func validateConfig(config *Config) error {
if !(config.Host == "" || config.Port == 0) {
if !(config.Timeout < 0) {
return nil
}
}
return errors.New("invalid configuration")
}Correct (Go - positive conditions):
func validateConfig(config *Config) error {
if config.Host == "" || config.Port == 0 {
return errors.New("invalid configuration")
}
if config.Timeout < 0 {
return errors.New("invalid configuration")
}
return nil
}Key Simplification Rules
| Pattern | Simplifies To |
|---|---|
!!x | x |
!(a && b) | `!a \ |
| `!(a \ | \ |
x === true | x |
x === false | !x |
x !== true | !x |
!(x !== y) | x === y |
When NOT to Apply
- When boolean variables are nullable (
x === trueexplicitly excludesnull/undefined) - When intermediate boolean names add meaningful domain context (extracting
isEligiblefrom a complex expression is good) - When the simplified form is actually harder to read than the expanded form
Benefits
- Conditions express intent directly without mental inversion
- Fewer logical operators means fewer places for bugs
- Named intermediate booleans document the business logic
- Code reviewers catch logical errors faster in simplified expressions
Use Early Returns to Reduce Nesting
Deep nesting forces readers to mentally track multiple conditions simultaneously. Early returns eliminate wrapper conditions by handling edge cases immediately and exiting. This creates a linear reading flow where each line is at the same indentation level, dramatically improving comprehension.
Incorrect (deeply nested logic):
function processOrder(order: Order): Result {
if (order) {
if (order.items.length > 0) {
if (order.customer) {
if (order.customer.isActive) {
if (order.paymentMethod) {
const total = calculateTotal(order.items);
if (total > 0) {
const discount = getDiscount(order.customer);
const finalTotal = total - discount;
return chargeCustomer(order.customer, finalTotal);
} else {
return { error: 'Invalid total' };
}
} else {
return { error: 'No payment method' };
}
} else {
return { error: 'Customer inactive' };
}
} else {
return { error: 'No customer' };
}
} else {
return { error: 'No items' };
}
} else {
return { error: 'No order' };
}
}Correct (early returns, flat structure):
function processOrder(order: Order): Result {
if (!order) {
return { error: 'No order' };
}
if (order.items.length === 0) {
return { error: 'No items' };
}
if (!order.customer) {
return { error: 'No customer' };
}
if (!order.customer.isActive) {
return { error: 'Customer inactive' };
}
if (!order.paymentMethod) {
return { error: 'No payment method' };
}
const total = calculateTotal(order.items);
if (total <= 0) {
return { error: 'Invalid total' };
}
const discount = getDiscount(order.customer);
const finalTotal = total - discount;
return chargeCustomer(order.customer, finalTotal);
}Benefits
- Reading flow is top-to-bottom without mental stack
- Error conditions are visually grouped at the top
- Happy path is clearly visible at the bottom
- Each condition can be understood in isolation
- Easier to add new validations without restructuring
Related
- See also `flow-guard-clauses` for validating preconditions at function entry
When NOT to Apply
- When the nested structure represents meaningful domain hierarchy
- In functional programming contexts where early return breaks composition
- When using exhaustive pattern matching that requires all branches
Prefer Explicit Control Flow Over Dense Expressions
Dense one-liners optimize for code golf, not comprehension. When multiple operations are chained or combined into a single expression, readers must mentally unpack each step. Explicit code may take more lines, but each line does one obvious thing. Future maintainers (including yourself) will thank you.
Incorrect (dense chained operations):
const result = data.filter(x => x.active).map(x => x.value).reduce((a, b) => a + b, 0) / data.filter(x => x.active).length || 0;Correct (explicit steps with meaningful names):
const activeItems = data.filter(item => item.active);
const values = activeItems.map(item => item.value);
const sum = values.reduce((total, value) => total + value, 0);
const average = activeItems.length > 0 ? sum / activeItems.length : 0;Incorrect (boolean logic golfing):
const canProceed = !!(user && user.verified && (user.role === 'admin' || (user.role === 'member' && user.subscription?.active && new Date(user.subscription.expiresAt) > new Date())));Correct (readable boolean expression):
const isVerifiedUser = user?.verified ?? false;
const isAdmin = user?.role === 'admin';
const hasActiveSubscription =
user?.role === 'member' &&
user?.subscription?.active &&
new Date(user.subscription.expiresAt) > new Date();
const canProceed = isVerifiedUser && (isAdmin || hasActiveSubscription);Incorrect (clever short-circuit abuse):
function updateUser(id: string, data: Partial<User>) {
const user = db.get(id) || (throwError('Not found'), null);
data.email && validateEmail(data.email) || (throwError('Invalid email'), null);
data.age && (data.age < 0 || data.age > 150) && (throwError('Invalid age'), null);
return db.update(id, { ...user, ...data, updatedAt: Date.now() });
}Correct (explicit validation flow):
function updateUser(id: string, data: Partial<User>) {
const user = db.get(id);
if (!user) {
throw new NotFoundError(`User ${id} not found`);
}
if (data.email !== undefined) {
if (!validateEmail(data.email)) {
throw new ValidationError('Invalid email format');
}
}
if (data.age !== undefined) {
if (data.age < 0 || data.age > 150) {
throw new ValidationError('Age must be between 0 and 150');
}
}
return db.update(id, {
...user,
...data,
updatedAt: Date.now(),
});
}Incorrect (compressed array building):
const items = [...(showArchived ? archivedItems : []), ...activeItems.filter(i => !i.hidden), ...(includeDeleted && deletedItems || [])].sort((a, b) => a.date - b.date).slice(0, limit);Correct (step-by-step array construction):
const items: Item[] = [];
if (showArchived) {
items.push(...archivedItems);
}
const visibleActive = activeItems.filter(item => !item.hidden);
items.push(...visibleActive);
if (includeDeleted && deletedItems) {
items.push(...deletedItems);
}
items.sort((a, b) => a.date - b.date);
const limitedItems = items.slice(0, limit);Benefits
- Each line can be understood independently
- Intermediate values can be logged/inspected during debugging
- Modifications don't require understanding entire expression
- Variable names serve as documentation
- Easier to add error handling at specific steps
When NOT to Apply
- Simple transformations:
const names = users.map(u => u.name) - Standard idioms:
const value = input ?? defaultValue - Well-known patterns:
array.filter(Boolean)
Flatten Deep Nesting to Maximum 2-3 Levels
Code nested beyond 2-3 levels becomes exponentially harder to understand. Each level requires holding more context in working memory. When you find yourself at 4+ levels, it's a signal to extract functions, use early returns, or restructure the logic. Flat code is maintainable code.
Incorrect (excessive nesting):
function processTransactions(accounts: Account[]) {
for (const account of accounts) {
if (account.isActive) {
for (const transaction of account.transactions) {
if (transaction.status === 'pending') {
if (transaction.amount > 0) {
for (const rule of account.rules) {
if (rule.applies(transaction)) {
if (rule.action === 'flag') {
if (transaction.amount > rule.threshold) {
transaction.flagged = true;
transaction.flagReason = rule.reason;
notifyCompliance(account, transaction);
}
}
}
}
}
}
}
}
}
}Correct (flattened with extraction and early continues):
function processTransactions(accounts: Account[]) {
const activeAccounts = accounts.filter(a => a.isActive);
for (const account of activeAccounts) {
processPendingTransactions(account);
}
}
function processPendingTransactions(account: Account) {
const pendingTransactions = account.transactions.filter(
t => t.status === 'pending' && t.amount > 0
);
for (const transaction of pendingTransactions) {
applyRules(account, transaction);
}
}
function applyRules(account: Account, transaction: Transaction) {
for (const rule of account.rules) {
if (!rule.applies(transaction)) {
continue;
}
if (rule.action !== 'flag') {
continue;
}
if (transaction.amount <= rule.threshold) {
continue;
}
transaction.flagged = true;
transaction.flagReason = rule.reason;
notifyCompliance(account, transaction);
}
}Incorrect (nested conditionals for validation):
function validateForm(form: FormData): ValidationResult {
if (form.email) {
if (isValidEmail(form.email)) {
if (form.password) {
if (form.password.length >= 8) {
if (form.password === form.confirmPassword) {
if (form.acceptedTerms) {
return { valid: true };
} else {
return { valid: false, error: 'Must accept terms' };
}
} else {
return { valid: false, error: 'Passwords do not match' };
}
} else {
return { valid: false, error: 'Password too short' };
}
} else {
return { valid: false, error: 'Password required' };
}
} else {
return { valid: false, error: 'Invalid email' };
}
} else {
return { valid: false, error: 'Email required' };
}
}Correct (flat validation chain):
function validateForm(form: FormData): ValidationResult {
if (!form.email) {
return { valid: false, error: 'Email required' };
}
if (!isValidEmail(form.email)) {
return { valid: false, error: 'Invalid email' };
}
if (!form.password) {
return { valid: false, error: 'Password required' };
}
if (form.password.length < 8) {
return { valid: false, error: 'Password too short' };
}
if (form.password !== form.confirmPassword) {
return { valid: false, error: 'Passwords do not match' };
}
if (!form.acceptedTerms) {
return { valid: false, error: 'Must accept terms' };
}
return { valid: true };
}Incorrect (nested object traversal):
function getDeepValue(config: Config) {
if (config) {
if (config.settings) {
if (config.settings.display) {
if (config.settings.display.theme) {
if (config.settings.display.theme.colors) {
return config.settings.display.theme.colors.primary;
}
}
}
}
}
return DEFAULT_COLOR;
}Correct (optional chaining or extraction):
function getDeepValue(config: Config) {
return config?.settings?.display?.theme?.colors?.primary ?? DEFAULT_COLOR;
}
// Or for complex logic:
function getDeepValue(config: Config) {
const theme = getTheme(config);
if (!theme?.colors) {
return DEFAULT_COLOR;
}
return theme.colors.primary;
}
function getTheme(config: Config): Theme | undefined {
return config?.settings?.display?.theme;
}Flattening Strategies
1. Early returns/continues - Exit the current scope ASAP 2. Extract helper functions - Each handles one level of complexity 3. Filter before iterate - Move conditions out of loops 4. Use optional chaining - For safe property access 5. Use lookup tables - Replace nested conditionals with data
Benefits
- Each function fits in working memory
- Code can be tested in smaller units
- Easier to parallelize or optimize specific parts
- Clearer separation of concerns
When NOT to Apply
- State machines with inherent nesting
- Parser/compiler code with recursive descent
- When extraction would require excessive parameter passing
Use Guard Clauses for Preconditions
Guard clauses validate all preconditions at the function's entry point and exit immediately if validation fails. This pattern creates a clear contract: if execution passes the guards, all required conditions are met. Readers don't need to trace conditions through the entire function body.
Incorrect (validations scattered throughout):
function sendNotification(user: User, message: string, channel: Channel): void {
let result;
if (user && user.email) {
if (message && message.trim().length > 0) {
const sanitized = sanitizeMessage(message);
if (channel === 'email') {
if (user.emailVerified) {
result = emailService.send(user.email, sanitized);
} else {
console.log('Email not verified');
}
} else if (channel === 'sms') {
if (user.phone) {
if (user.phoneVerified) {
result = smsService.send(user.phone, sanitized);
} else {
console.log('Phone not verified');
}
} else {
console.log('No phone number');
}
}
} else {
console.log('Empty message');
}
} else {
console.log('Invalid user');
}
return result;
}Correct (guard clauses at entry):
function sendNotification(user: User, message: string, channel: Channel): void {
// Guard clauses - all preconditions checked upfront
if (!user?.email) {
throw new ValidationError('User must have an email address');
}
if (!message?.trim()) {
throw new ValidationError('Message cannot be empty');
}
if (!['email', 'sms'].includes(channel)) {
throw new ValidationError(`Invalid channel: ${channel}`);
}
const sanitized = sanitizeMessage(message);
// Channel-specific guards
if (channel === 'email') {
if (!user.emailVerified) {
throw new ValidationError('Email address not verified');
}
return emailService.send(user.email, sanitized);
}
if (channel === 'sms') {
if (!user.phone) {
throw new ValidationError('User must have a phone number for SMS');
}
if (!user.phoneVerified) {
throw new ValidationError('Phone number not verified');
}
return smsService.send(user.phone, sanitized);
}
}Incorrect (null checks interleaved with logic):
function calculateShipping(order: Order): number {
let shipping = 0;
if (order) {
const items = order.items;
if (items) {
for (const item of items) {
if (item && item.weight) {
shipping += item.weight * RATE_PER_KG;
}
}
if (order.address) {
if (order.address.country !== 'US') {
shipping *= INTERNATIONAL_MULTIPLIER;
}
}
}
}
return shipping;
}Correct (guards enforce valid state):
function calculateShipping(order: Order): number {
if (!order) {
throw new Error('Order is required');
}
if (!order.items?.length) {
throw new Error('Order must have items');
}
if (!order.address) {
throw new Error('Order must have an address');
}
let shipping = 0;
for (const item of order.items) {
shipping += (item.weight ?? 0) * RATE_PER_KG;
}
if (order.address.country !== 'US') {
shipping *= INTERNATIONAL_MULTIPLIER;
}
return shipping;
}Benefits
- Function contract is immediately visible
- Invalid states fail fast before causing side effects
- Core logic operates on guaranteed valid data
- Testing is simplified - guards can be tested independently
- Debugging is easier - failures point to specific validation
Related
- See also `flow-early-return` for reducing nesting with early returns in general
When NOT to Apply
- When partial/graceful degradation is required (use defaults instead)
- In hot paths where exceptions are too expensive
- When building tolerant readers that handle malformed input
Never Use Nested Ternary Operators
Nested ternary operators create write-only code. The human brain processes branching logic linearly, but nested ternaries force simultaneous evaluation of multiple conditions. Even with formatting, the precedence and flow become ambiguous. One nested ternary can make an entire function unreadable.
Incorrect (nested ternaries):
function getStatusBadge(user: User): string {
return user.isAdmin
? user.isActive
? 'admin-active'
: 'admin-inactive'
: user.isPremium
? user.isActive
? 'premium-active'
: 'premium-inactive'
: user.isActive
? 'basic-active'
: 'basic-inactive';
}Correct (explicit conditionals):
function getStatusBadge(user: User): string {
const status = user.isActive ? 'active' : 'inactive';
if (user.isAdmin) {
return `admin-${status}`;
}
if (user.isPremium) {
return `premium-${status}`;
}
return `basic-${status}`;
}Incorrect (ternary chain for value selection):
const priority = task.isUrgent
? task.isImportant
? 1
: 2
: task.isImportant
? 3
: 4;Correct (lookup object or switch):
const priorityMatrix = {
'urgent-important': 1,
'urgent-normal': 2,
'normal-important': 3,
'normal-normal': 4,
};
const urgency = task.isUrgent ? 'urgent' : 'normal';
const importance = task.isImportant ? 'important' : 'normal';
const priority = priorityMatrix[`${urgency}-${importance}`];Incorrect (inline nested ternary in JSX):
function UserAvatar({ user }: Props) {
return (
<div className={
user.status === 'online'
? user.isPremium
? 'avatar-premium-online'
: 'avatar-online'
: user.status === 'away'
? 'avatar-away'
: 'avatar-offline'
}>
{user.name}
</div>
);
}Correct (helper function with clear logic):
function getAvatarClass(user: User): string {
if (user.status === 'online') {
return user.isPremium ? 'avatar-premium-online' : 'avatar-online';
}
if (user.status === 'away') {
return 'avatar-away';
}
return 'avatar-offline';
}
function UserAvatar({ user }: Props) {
return (
<div className={getAvatarClass(user)}>
{user.name}
</div>
);
}Acceptable Single-Level Ternary
// OK: Single ternary for simple binary choice
const label = isLoading ? 'Loading...' : 'Submit';
// OK: Nullish coalescing or optional chaining
const name = user?.name ?? 'Anonymous';
// NOT OK: Even two levels is too much
const label = isLoading ? 'Loading...' : isDisabled ? 'Disabled' : 'Submit';Benefits
- Code flow is immediately apparent
- Debugging is possible (can set breakpoints)
- Modifications don't require understanding entire expression
- Code review can focus on logic, not parsing
When NOT to Apply
- Single-level ternaries for simple binary choices are acceptable
- Ternaries inside template literals for simple value selection
Use Optional Chaining and Nullish Coalescing
Deep null-checking creates pyramids of if statements that obscure the actual logic. Optional chaining (?.) and nullish coalescing (??) collapse these checks into readable expressions that clearly show the intent: "get this value, or use a fallback."
Incorrect (nested null checks):
function getUserCity(user: User | null): string {
if (user) {
if (user.address) {
if (user.address.city) {
return user.address.city;
}
}
}
return 'Unknown';
}
function getDisplayName(profile: Profile | null): string {
let displayName = 'Anonymous';
if (profile !== null && profile !== undefined) {
if (profile.nickname !== null && profile.nickname !== undefined) {
displayName = profile.nickname;
} else if (profile.firstName !== null && profile.firstName !== undefined) {
displayName = profile.firstName;
}
}
return displayName;
}Correct (optional chaining and nullish coalescing):
function getUserCity(user: User | null): string {
return user?.address?.city ?? 'Unknown';
}
function getDisplayName(profile: Profile | null): string {
return profile?.nickname ?? profile?.firstName ?? 'Anonymous';
}Incorrect (Python - verbose None checks):
def get_config_value(config, section, key, default=None):
if config is not None:
section_data = config.get(section)
if section_data is not None:
value = section_data.get(key)
if value is not None:
return value
return defaultCorrect (Python - dictionary chaining):
def get_config_value(config, section, key, default=None):
if config is None:
return default
return config.get(section, {}).get(key, default)When NOT to Apply
- When you need to distinguish between
null,undefined, andfalse/0/''(use explicit checks) - When each null check has different error handling or logging
- When the falsy-value semantics of
||are intentionally different from??
Benefits
- Eliminates 3-8 lines of null-checking per access chain
- Reduces nesting by 2-4 levels
- Intent is immediately clear: "access this path or use default"
- Fewer intermediate variables needed
Prefer Positive Conditions Over Double Negatives
The human brain processes positive statements faster than negative ones. Double negatives require mental inversion that slows comprehension and increases error rates. Write conditions that state what IS true, not what ISN'T NOT true.
Incorrect (double negatives):
if (!user.isNotVerified) {
sendWelcomeEmail(user);
}
if (!isNotEmpty(list)) {
return 'No items';
}
if (!config.disableCache !== false) {
useCache();
}
const cannotProceed = !isValid || !hasPermission;
if (!cannotProceed) {
proceed();
}Correct (positive conditions):
if (user.isVerified) {
sendWelcomeEmail(user);
}
if (isEmpty(list)) {
return 'No items';
}
if (config.enableCache) {
useCache();
}
const canProceed = isValid && hasPermission;
if (canProceed) {
proceed();
}Incorrect (negative function names):
function isNotAdmin(user: User): boolean {
return user.role !== 'admin';
}
function hasNoErrors(result: Result): boolean {
return result.errors.length === 0;
}
function isDisabled(feature: Feature): boolean {
return !feature.enabled;
}
// Usage becomes confusing
if (!isNotAdmin(user) && !isDisabled(feature)) {
showAdminPanel();
}Correct (positive function names):
function isAdmin(user: User): boolean {
return user.role === 'admin';
}
function isValid(result: Result): boolean {
return result.errors.length === 0;
}
function isEnabled(feature: Feature): boolean {
return feature.enabled;
}
// Usage is clear
if (isAdmin(user) && isEnabled(feature)) {
showAdminPanel();
}Incorrect (negative variable names):
const notFound = items.indexOf(target) === -1;
if (!notFound) {
processItem(target);
}
const isInvalid = !schema.validate(data);
if (!isInvalid) {
save(data);
}
let hideModal = true;
if (!hideModal) {
modal.show();
}Correct (positive variable names):
const found = items.indexOf(target) !== -1;
if (found) {
processItem(target);
}
// Or even better with includes:
if (items.includes(target)) {
processItem(target);
}
const isValid = schema.validate(data);
if (isValid) {
save(data);
}
let showModal = false;
if (showModal) {
modal.show();
}Incorrect (confusing boolean logic):
// What does this even mean?
if (!(user.inactive || !user.emailConfirmed) && !config.skipValidation) {
allowAccess();
}Correct (clear positive logic):
const isActiveUser = !user.inactive;
const hasConfirmedEmail = user.emailConfirmed;
const shouldValidate = !config.skipValidation;
if (isActiveUser && hasConfirmedEmail && shouldValidate) {
allowAccess();
}
// Or extract to a well-named function
function canAccessSystem(user: User, config: Config): boolean {
const isActiveUser = !user.inactive;
const hasConfirmedEmail = user.emailConfirmed;
const requiresValidation = !config.skipValidation;
return isActiveUser && hasConfirmedEmail && requiresValidation;
}Refactoring Patterns
| Negative Form | Positive Form |
|---|---|
!isNotValid | isValid |
!isEmpty | hasItems |
!isDisabled | isEnabled |
notFound === false | found === true or just found |
!user.inactive | user.isActive (may need data change) |
errors.length === 0 | isValid or hasNoErrors |
Benefits
- Conditions read like natural language
- Fewer mental inversions when reading
- Reduced chance of logic errors
- Easier to combine conditions with && and ||
- Self-documenting variable and function names
When NOT to Apply
- When the negative is the natural domain concept (e.g.,
isDeprecated,wasDeleted) - When matching an external API that uses negative naming
- In guard clauses where
if (!x) returnis idiomatic - When
!conditionis clearer than creating a new named positive
Each Code Block Should Do One Thing
When a code block (function, loop, conditional branch) tries to do multiple things, it becomes harder to name, test, and modify. Each block should have one clear purpose. If you can't describe what a block does without using "and", split it up.
Incorrect (loop doing multiple things):
function processOrders(orders: Order[]) {
let totalRevenue = 0;
const flaggedOrders: Order[] = [];
const customerOrderCounts: Map<string, number> = new Map();
const emailsToSend: Email[] = [];
for (const order of orders) {
// Calculate revenue
totalRevenue += order.total;
// Flag suspicious orders
if (order.total > 10000 || order.items.length > 50) {
flaggedOrders.push(order);
}
// Count orders per customer
const count = customerOrderCounts.get(order.customerId) || 0;
customerOrderCounts.set(order.customerId, count + 1);
// Prepare confirmation emails
if (order.status === 'completed') {
emailsToSend.push({
to: order.customerEmail,
subject: 'Order Confirmed',
body: generateOrderEmail(order),
});
}
// Update inventory
for (const item of order.items) {
inventory.decrement(item.sku, item.quantity);
}
}
return { totalRevenue, flaggedOrders, customerOrderCounts, emailsToSend };
}Correct (separate single-purpose functions):
function processOrders(orders: Order[]): ProcessingResult {
return {
totalRevenue: calculateTotalRevenue(orders),
flaggedOrders: findSuspiciousOrders(orders),
customerOrderCounts: countOrdersByCustomer(orders),
emailsToSend: prepareConfirmationEmails(orders),
};
}
function calculateTotalRevenue(orders: Order[]): number {
return orders.reduce((sum, order) => sum + order.total, 0);
}
function findSuspiciousOrders(orders: Order[]): Order[] {
return orders.filter(
order => order.total > 10000 || order.items.length > 50
);
}
function countOrdersByCustomer(orders: Order[]): Map<string, number> {
const counts = new Map<string, number>();
for (const order of orders) {
const current = counts.get(order.customerId) || 0;
counts.set(order.customerId, current + 1);
}
return counts;
}
function prepareConfirmationEmails(orders: Order[]): Email[] {
return orders
.filter(order => order.status === 'completed')
.map(order => ({
to: order.customerEmail,
subject: 'Order Confirmed',
body: generateOrderEmail(order),
}));
}
function updateInventory(orders: Order[]): void {
for (const order of orders) {
for (const item of order.items) {
inventory.decrement(item.sku, item.quantity);
}
}
}Incorrect (function with multiple responsibilities):
async function handleUserSignup(data: SignupData) {
// Validate
if (!data.email || !isValidEmail(data.email)) {
throw new Error('Invalid email');
}
if (!data.password || data.password.length < 8) {
throw new Error('Password too short');
}
// Check existing
const existing = await db.users.findByEmail(data.email);
if (existing) {
throw new Error('Email already registered');
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(data.password, salt);
// Create user
const user = await db.users.create({
email: data.email,
password: hashedPassword,
createdAt: new Date(),
});
// Send welcome email
await emailService.send({
to: user.email,
template: 'welcome',
data: { name: user.name },
});
// Create audit log
await auditLog.record({
action: 'user_signup',
userId: user.id,
timestamp: new Date(),
});
// Initialize settings
await db.userSettings.create({
userId: user.id,
theme: 'light',
notifications: true,
});
return user;
}Correct (orchestrator with single-purpose helpers):
async function handleUserSignup(data: SignupData): Promise<User> {
validateSignupData(data);
await ensureEmailNotTaken(data.email);
const user = await createUser(data);
await sendWelcomeEmail(user);
await recordSignupAudit(user);
await initializeUserSettings(user);
return user;
}
function validateSignupData(data: SignupData): void {
if (!data.email || !isValidEmail(data.email)) {
throw new ValidationError('Invalid email');
}
if (!data.password || data.password.length < 8) {
throw new ValidationError('Password must be at least 8 characters');
}
}
async function ensureEmailNotTaken(email: string): Promise<void> {
const existing = await db.users.findByEmail(email);
if (existing) {
throw new ConflictError('Email already registered');
}
}
async function createUser(data: SignupData): Promise<User> {
const hashedPassword = await hashPassword(data.password);
return db.users.create({
email: data.email,
password: hashedPassword,
createdAt: new Date(),
});
}
async function hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt(10);
return bcrypt.hash(password, salt);
}Signs a Block Does Too Much
- Name includes "and" or describes multiple actions
- Multiple unrelated variables being modified
- Comments separating different "sections"
- Difficult to write a focused unit test
- Changes to one part risk breaking another
Benefits
- Each piece can be tested in isolation
- Functions can be reused in different contexts
- Changes are localized - modify one thing without risking others
- Names become self-documenting
- Easier to parallelize independent operations
Handle Errors Immediately After the Call
Go's explicit error handling is a feature, not a burden. Always check errors immediately after a function call returns—don't defer error checks or accumulate them. This pattern makes control flow explicit and ensures errors are handled in context where you have the most information.
Incorrect (deferred or accumulated error checking):
// Bad: errors accumulate, hard to know which failed
func processFiles(paths []string) error {
var lastErr error
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
lastErr = err // Previous errors lost
}
result, err := process(data)
if err != nil {
lastErr = err
}
err = save(result)
if err != nil {
lastErr = err
}
}
return lastErr
}
// Bad: ignoring error
func quickRead(path string) []byte {
data, _ := os.ReadFile(path) // Silent failure
return data
}Correct (handle immediately with context):
func processFiles(paths []string) error {
for _, path := range paths {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading %s: %w", path, err)
}
result, err := process(data)
if err != nil {
return fmt.Errorf("processing %s: %w", path, err)
}
if err := save(result); err != nil {
return fmt.Errorf("saving result for %s: %w", path, err)
}
}
return nil
}
// When you need to continue despite errors, collect them explicitly
func processAllFiles(paths []string) error {
var errs []error
for _, path := range paths {
if err := processFile(path); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", path, err))
continue // Explicit decision to continue
}
}
return errors.Join(errs...) // Go 1.20+
}Wrapping Errors for Context
// Always add context when propagating
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config: %w", err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}When Ignoring Errors is Acceptable
// Explicitly document why it's safe to ignore
_ = writer.Close() // Best effort cleanup, already returning another error
// Or use a helper for intentional ignores
func must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}Benefits
- Errors handled with full context available
- Control flow is explicit and linear
- Error wrapping builds informative stack traces
- No hidden failures or silent data corruption
Prefer Language and Standard Library Builtins
Every language provides optimized, well-tested utilities for common operations. Manual implementations are almost always buggier, slower, and harder to maintain. Before writing utility code, check if the language or standard library already provides it.
Incorrect (manual implementations):
// TypeScript: Manual array operations
function unique<T>(arr: T[]): T[] {
const seen = new Set<T>();
const result: T[] = [];
for (const item of arr) {
if (!seen.has(item)) {
seen.add(item);
result.push(item);
}
}
return result;
}
function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
const result: Record<string, T[]> = {};
for (const item of arr) {
const k = String(item[key]);
if (!result[k]) result[k] = [];
result[k].push(item);
}
return result;
}Correct (use builtins):
// TypeScript: Use Set and Object.groupBy (ES2024)
const uniqueItems = [...new Set(arr)];
const grouped = Object.groupBy(arr, item => item.category);Incorrect (Python manual operations):
# Python: Manual implementations
def flatten(nested_list):
result = []
for sublist in nested_list:
for item in sublist:
result.append(item)
return result
def find_max_by_key(items, key_func):
max_item = None
max_val = float('-inf')
for item in items:
val = key_func(item)
if val > max_val:
max_val = val
max_item = item
return max_itemCorrect (Python stdlib):
# Python: Use itertools and builtins
from itertools import chain
flattened = list(chain.from_iterable(nested_list))
max_item = max(items, key=key_func)
# Other commonly overlooked builtins
from collections import Counter, defaultdict
from functools import lru_cache, reduce
from pathlib import Path # Instead of os.path string manipulationIncorrect (Go manual operations):
// Go: Manual slice operations
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
func min(a, b int) int {
if a < b {
return a
}
return b
}Correct (Go stdlib and generics):
// Go 1.21+: Use slices and cmp packages
import (
"slices"
"cmp"
)
found := slices.Contains(slice, item)
minimum := min(a, b) // builtin since Go 1.21
slices.SortFunc(items, func(a, b Item) int {
return cmp.Compare(a.Priority, b.Priority)
})Incorrect (Rust manual operations):
// Rust: Manual implementations
fn join_strings(strings: &[String], sep: &str) -> String {
let mut result = String::new();
for (i, s) in strings.iter().enumerate() {
if i > 0 {
result.push_str(sep);
}
result.push_str(s);
}
result
}Correct (Rust stdlib):
// Rust: Use standard library
let joined = strings.join(", ");
// Other commonly overlooked std features
use std::collections::HashMap;
let counts: HashMap<_, _> = items.iter().fold(HashMap::new(), |mut acc, x| {
*acc.entry(x).or_insert(0) += 1;
acc
});
// Or use itertools crate for more complex operationsCommon Builtins to Know
| Operation | TypeScript | Python | Go | Rust |
|---|---|---|---|---|
| Unique | new Set() | set() | slices.Compact | .dedup() |
| Sort | .sort() | sorted() | slices.Sort | .sort() |
| Find | .find() | next(x for...) | slices.Index | .find() |
| Group | Object.groupBy | itertools.groupby | manual/lo | .group_by() |
| Flatten | .flat() | chain.from_iterable | manual | .flatten() |
Benefits
- Battle-tested implementations with edge cases handled
- Often optimized at the language/runtime level
- Familiar to other developers reading your code
- Reduces maintenance burden and potential bugs
Use Comprehensions for Simple Transforms
Python comprehensions (list, dict, set, generator) are idiomatic for simple transformations and filtering. They're more concise and often faster than equivalent loops. However, complex comprehensions with multiple conditions or nested logic become unreadable—use regular loops instead.
Incorrect (loop for simple transform):
# Verbose for a simple operation
result = []
for user in users:
if user.is_active:
result.append(user.email)
# Building dict inefficiently
lookup = {}
for item in items:
lookup[item.id] = item.nameCorrect (comprehension for simple transform):
# Clear and Pythonic
result = [user.email for user in users if user.is_active]
# Dict comprehension
lookup = {item.id: item.name for item in items}
# Set comprehension for unique values
unique_categories = {product.category for product in products}
# Generator for memory efficiency with large data
total = sum(order.amount for order in orders if order.status == "completed")Incorrect (complex comprehension hurts readability):
# Too complex - multiple conditions and transformations
result = [
transform_data(item.value)
for category in categories
for item in category.items
if item.is_valid and item.value > threshold
if not item.is_deleted
]Correct (loop for complex logic):
# Clear with explicit logic
result = []
for category in categories:
for item in category.items:
if not item.is_valid or item.is_deleted:
continue
if item.value <= threshold:
continue
result.append(transform_data(item.value))Guidelines
| Use Comprehensions | Use Loops |
|---|---|
| Single filter + map | Multiple conditions |
| One level of nesting | Side effects needed |
| Fits on one line (~80 chars) | Complex transformations |
| Pure data transformation | Exception handling required |
Walrus Operator in Comprehensions (Python 3.8+)
# Avoid repeated computation
results = [y for x in data if (y := expensive_transform(x)) is not None]Benefits
- More readable for simple cases
- Often 10-30% faster than equivalent loops
- Generator expressions avoid building intermediate lists
- Clearly signals "this is a data transformation"
Related skills
FAQ
What does code-simplifier do?
Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior. Use when simplifying code, reducing complexity, cleaning up recent changes, applying refactoring patterns
When should I use code-simplifier?
Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior. Use when simplifying code, reducing complexity, cleaning up recent changes, applying refactoring patterns
What are common prerequisites?
--- name: code-simplifier description: Code simplification skill for improving clarity, consistency, and maintainability while preserving exact behavior.
Is Code Simplifier safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.