Code Standards
What production-quality code looks like. Use this as a quality bar for writing and evaluating code.
Core principle: Code is read far more often than it is written. Optimize for the reader.
When to Use
- Evaluating your own code before submitting a PR
- Establishing standards for a codebase or team
- Identifying quality problems in existing code
- Answering "is this good enough?"
When NOT to Use
- Conducting a full code review (use code-review skill)
- Refactoring existing code (use refactoring skill)
Naming Standards
| Element |
Rule |
Example |
| Functions/methods |
Verb + noun, reveals what it does |
fetchUserById, calculateTax, validateEmail |
| Boolean variables |
is, has, can, should prefix |
isValid, hasPermission, canEdit |
| Classes |
Noun, PascalCase |
UserRepository, PaymentService |
| Constants |
SCREAMING_SNAKE_CASE |
MAX_RETRY_ATTEMPTS, DEFAULT_TIMEOUT_MS |
| Generic params |
T for type, K/V for key/value |
function find<T>(items: T[]): T |
| Test names |
it("does X when Y") |
it("returns null when user not found") |
Banned patterns:
- Single-letter variables outside loop indices (
i, j, k in loops only)
- Abbreviations that aren't industry-standard (
usr, mgr, tmp)
- Generic names:
data, info, stuff, helper, util, manager
- Misleading names: a function that does Y but is called
doX
Complexity Thresholds
| Element |
Threshold |
Action if Exceeded |
| Function length |
50 lines |
Extract to sub-functions |
| File length |
300 lines |
Split into modules |
| Parameters |
4 |
Use options object |
| Cyclomatic complexity |
10 |
Simplify logic |
| Nesting depth |
3 levels |
Early returns, extract functions |
| Class methods |
10 |
Single Responsibility violation — split |
Anti-Pattern Reference
Code Smells to Eliminate
| Smell |
Example |
Fix |
| Magic numbers |
if (status === 3) |
if (status === OrderStatus.CANCELLED) |
| Boolean parameters |
render(true, false, true) |
Named options object or separate functions |
| Commented-out code |
// old code here |
Delete it (git has history) |
| Deep nesting |
4+ levels of if/for |
Early returns, extract functions |
| God function |
200 lines doing everything |
Single Responsibility — extract |
| Shotgun surgery |
One change requires edits in 7 files |
Wrong abstraction boundary |
| Inappropriate intimacy |
Class A accesses Class B's private data |
Encapsulation violation — add methods |
| Primitive obsession |
string used for userId, email, and slug interchangeably |
Value objects |
Two Hats Rule
Never mix refactoring and optimization in the same session.
- Hat 1: Refactoring — change structure, NOT behavior. Tests must pass unchanged.
- Hat 2: Optimization — improve performance, NOT structure. Benchmarks required.
When switching hats: commit first, then switch context.
The Worst Offenders
// BANNED: Error silencing
try { doThing(); } catch (e) { /* ignore */ }
// FIX: At minimum, log. At most, handle.
// BANNED: Non-null assertion without justification
const user = getUser()!;
// FIX: const user = getUser() ?? throw new Error('...')
// BANNED: any type
function process(data: any): any { ... }
// FIX: explicit types or generics
// BANNED: Mutable global state
let currentUser = null; // module-level
// FIX: pass through function parameters or use proper context
// BANNED: Function with side effects and a name implying pure query
function getUserName(id: string): string {
logger.audit(`Name lookup: ${id}`); // side effect!
return db.get(id).name;
}
// FIX: separate concerns or document the side effect
Performance Anti-Patterns
| Pattern |
Fix |
| N+1 queries (DB call in a loop) |
Batch fetch before loop; use eager loading |
Blocking I/O in async handler (readFileSync, execSync) |
Use async equivalents |
No pagination (SELECT * returning all rows) |
Add LIMIT / cursor pagination |
| O(n²) algorithm (nested loops over same data) |
HashMap or sort + single pass |
Function Quality Standards
A good function:
- Does one thing — can be described in a single sentence without "and"
- Has a clear name — caller doesn't need to read the body
- Has 4 or fewer parameters — more → use options object
- Handles its error cases explicitly — no silent failures
- Has no unexpected side effects — if it has side effects, name makes it clear
// Bad: does multiple things, unclear, silent failure
function process(d: any, f: boolean) {
try {
db.save(d);
if (f) sendEmail(d.email, 'done');
} catch {}
}
// Good: single responsibility, clear name, typed, explicit errors
async function saveTaskAndNotify(task: Task, notify: boolean): Promise<void> {
await taskRepository.save(task);
if (notify) {
await emailService.sendTaskCreated(task.assignee.email, task);
}
}
Error Handling Standards
// Every async function either returns a result or throws a typed error
// Bad: returns null on failure (caller must remember to check)
async function getUser(id: string): Promise<User | null> {
try { return await db.findUser(id); }
catch { return null; }
}
// Good: throws on not-found, caller can rely on non-null return
async function getUser(id: string): Promise<User> {
const user = await db.findUser(id);
if (!user) throw new NotFoundError(`User ${id} not found`);
return user;
}
// Bad: catch-all silence
try { await riskyOperation(); } catch (e) { /* ignore */ }
// Good: explicit handling
try {
await riskyOperation();
} catch (err) {
if (err instanceof NetworkError) {
logger.warn('Network error, retrying', { err });
await retry(riskyOperation);
} else {
logger.error('Unexpected error', { err });
throw err; // re-throw unknown errors
}
}
Testing Standards
Every non-trivial function needs tests covering:
| Case |
Why |
| Happy path |
Proves basic functionality |
| Empty/null input |
Most common source of bugs |
| Boundary values |
Off-by-one errors |
| Error conditions |
Verifies graceful failure |
| Concurrent execution |
For async/shared-state code |
describe('calculateTax', () => {
it('returns 0 for zero subtotal', () => { ... });
it('applies rate to positive subtotal', () => { ... });
it('throws for negative subtotal', () => { ... });
it('handles floating point precision correctly', () => { ... });
});
Common Rationalizations to Reject
| Rationalization |
Reality |
| "It's obvious what this does" |
Future you at 2am disagrees |
| "I'll clean it up later" |
Later is never scheduled |
| "It's just a quick fix" |
Quick fixes compound into legacy debt |
| "The tests are too hard to write" |
The code is too hard to test — simplify it |
| "It works, don't touch it" |
Working ≠ correct; correct ≠ maintainable |
Automated Quality Gates (by phase)
| Phase |
Checks |
| Pre-commit |
Lint + format + type check + secret scan |
| CI pipeline |
Lint + secret scan + vulnerability scan + tests |
| Continuous |
Dependency updates + security advisories |
Verification Checklist
1---2name: code-standards3description: Use when evaluating whether code meets quality standards, checking naming conventions, assessing complexity thresholds, identifying anti-patterns, understanding what good code looks like, or applying quality standards to your own code before submitting. Triggers: "is this code good", "code quality", "does this follow best practices", "naming conventions", "is this too complex", "code standards", "what makes good code", "anti-patterns".4---56# Code Standards78What production-quality code looks like. Use this as a quality bar for writing and evaluating code.910**Core principle:** Code is read far more often than it is written. Optimize for the reader.1112## When to Use1314- Evaluating your own code before submitting a PR15- Establishing standards for a codebase or team16- Identifying quality problems in existing code17- Answering "is this good enough?"1819## When NOT to Use2021- Conducting a full code review (use code-review skill)22- Refactoring existing code (use refactoring skill)2324---2526## Naming Standards2728| Element | Rule | Example |29|---------|------|---------|30| Functions/methods | Verb + noun, reveals what it does | `fetchUserById`, `calculateTax`, `validateEmail` |31| Boolean variables | `is`, `has`, `can`, `should` prefix | `isValid`, `hasPermission`, `canEdit` |32| Classes | Noun, PascalCase | `UserRepository`, `PaymentService` |33| Constants | SCREAMING_SNAKE_CASE | `MAX_RETRY_ATTEMPTS`, `DEFAULT_TIMEOUT_MS` |34| Generic params | T for type, K/V for key/value | `function find<T>(items: T[]): T` |35| Test names | `it("does X when Y")` | `it("returns null when user not found")` |3637**Banned patterns:**38- Single-letter variables outside loop indices (`i`, `j`, `k` in loops only)39- Abbreviations that aren't industry-standard (`usr`, `mgr`, `tmp`)40- Generic names: `data`, `info`, `stuff`, `helper`, `util`, `manager`41- Misleading names: a function that does Y but is called `doX`4243---4445## Complexity Thresholds4647| Element | Threshold | Action if Exceeded |48|---------|-----------|-------------------|49| Function length | 50 lines | Extract to sub-functions |50| File length | 300 lines | Split into modules |51| Parameters | 4 | Use options object |52| Cyclomatic complexity | 10 | Simplify logic |53| Nesting depth | 3 levels | Early returns, extract functions |54| Class methods | 10 | Single Responsibility violation — split |5556---5758## Anti-Pattern Reference5960### Code Smells to Eliminate6162| Smell | Example | Fix |63|-------|---------|-----|64| **Magic numbers** | `if (status === 3)` | `if (status === OrderStatus.CANCELLED)` |65| **Boolean parameters** | `render(true, false, true)` | Named options object or separate functions |66| **Commented-out code** | `// old code here` | Delete it (git has history) |67| **Deep nesting** | 4+ levels of if/for | Early returns, extract functions |68| **God function** | 200 lines doing everything | Single Responsibility — extract |69| **Shotgun surgery** | One change requires edits in 7 files | Wrong abstraction boundary |70| **Inappropriate intimacy** | Class A accesses Class B's private data | Encapsulation violation — add methods |71| **Primitive obsession** | `string` used for userId, email, and slug interchangeably | Value objects |7273### Two Hats Rule7475Never mix refactoring and optimization in the same session.76- **Hat 1: Refactoring** — change structure, NOT behavior. Tests must pass unchanged.77- **Hat 2: Optimization** — improve performance, NOT structure. Benchmarks required.7879When switching hats: commit first, then switch context.8081### The Worst Offenders8283```typescript84// BANNED: Error silencing85try { doThing(); } catch (e) { /* ignore */ }86// FIX: At minimum, log. At most, handle.8788// BANNED: Non-null assertion without justification89const user = getUser()!;90// FIX: const user = getUser() ?? throw new Error('...')9192// BANNED: any type93function process(data: any): any { ... }94// FIX: explicit types or generics9596// BANNED: Mutable global state97let currentUser = null; // module-level98// FIX: pass through function parameters or use proper context99100// BANNED: Function with side effects and a name implying pure query101function getUserName(id: string): string {102 logger.audit(`Name lookup: ${id}`); // side effect!103 return db.get(id).name;104}105// FIX: separate concerns or document the side effect106```107108### Performance Anti-Patterns109110| Pattern | Fix |111|---------|-----|112| N+1 queries (DB call in a loop) | Batch fetch before loop; use eager loading |113| Blocking I/O in async handler (`readFileSync`, `execSync`) | Use async equivalents |114| No pagination (`SELECT *` returning all rows) | Add `LIMIT` / cursor pagination |115| O(n²) algorithm (nested loops over same data) | HashMap or sort + single pass |116117---118119## Function Quality Standards120121A good function:122- Does **one thing** — can be described in a single sentence without "and"123- Has a **clear name** — caller doesn't need to read the body124- Has **4 or fewer parameters** — more → use options object125- Handles its **error cases explicitly** — no silent failures126- Has **no unexpected side effects** — if it has side effects, name makes it clear127128```typescript129// Bad: does multiple things, unclear, silent failure130function process(d: any, f: boolean) {131 try {132 db.save(d);133 if (f) sendEmail(d.email, 'done');134 } catch {}135}136137// Good: single responsibility, clear name, typed, explicit errors138async function saveTaskAndNotify(task: Task, notify: boolean): Promise<void> {139 await taskRepository.save(task);140 if (notify) {141 await emailService.sendTaskCreated(task.assignee.email, task);142 }143}144```145146---147148## Error Handling Standards149150```typescript151// Every async function either returns a result or throws a typed error152153// Bad: returns null on failure (caller must remember to check)154async function getUser(id: string): Promise<User | null> {155 try { return await db.findUser(id); }156 catch { return null; }157}158159// Good: throws on not-found, caller can rely on non-null return160async function getUser(id: string): Promise<User> {161 const user = await db.findUser(id);162 if (!user) throw new NotFoundError(`User ${id} not found`);163 return user;164}165166// Bad: catch-all silence167try { await riskyOperation(); } catch (e) { /* ignore */ }168169// Good: explicit handling170try {171 await riskyOperation();172} catch (err) {173 if (err instanceof NetworkError) {174 logger.warn('Network error, retrying', { err });175 await retry(riskyOperation);176 } else {177 logger.error('Unexpected error', { err });178 throw err; // re-throw unknown errors179 }180}181```182183---184185## Testing Standards186187Every non-trivial function needs tests covering:188189| Case | Why |190|------|-----|191| Happy path | Proves basic functionality |192| Empty/null input | Most common source of bugs |193| Boundary values | Off-by-one errors |194| Error conditions | Verifies graceful failure |195| Concurrent execution | For async/shared-state code |196197```typescript198describe('calculateTax', () => {199 it('returns 0 for zero subtotal', () => { ... });200 it('applies rate to positive subtotal', () => { ... });201 it('throws for negative subtotal', () => { ... });202 it('handles floating point precision correctly', () => { ... });203});204```205206---207208## Common Rationalizations to Reject209210| Rationalization | Reality |211|----------------|---------|212| "It's obvious what this does" | Future you at 2am disagrees |213| "I'll clean it up later" | Later is never scheduled |214| "It's just a quick fix" | Quick fixes compound into legacy debt |215| "The tests are too hard to write" | The code is too hard to test — simplify it |216| "It works, don't touch it" | Working ≠ correct; correct ≠ maintainable |217218### Automated Quality Gates (by phase)219220| Phase | Checks |221|-------|--------|222| Pre-commit | Lint + format + type check + secret scan |223| CI pipeline | Lint + secret scan + vulnerability scan + tests |224| Continuous | Dependency updates + security advisories |225226## Verification Checklist227228- [ ] All names reveal intent (no abbrevations, no generic names)229- [ ] No function longer than 50 lines230- [ ] No file longer than 300 lines231- [ ] No magic numbers (use named constants)232- [ ] No boolean function parameters233- [ ] No error silencing (every catch either handles or re-throws)234- [ ] No commented-out code235- [ ] Every function does one thing236- [ ] Parameters ≤ 4 (or options object used)237- [ ] Tests cover happy path, empty/null, error conditions