Coding Practices Playbook
Core development principles for consistent, maintainable code. Give this to your AI agent to establish coding standards.
# agent_prompt
Copy this prompt and give it to your AI agent to establish these practices:
"Follow these coding practices for all code you write:
1. DRY - Don't repeat yourself. Extract common functionality.
2. KISS - Keep it simple. Prefer clarity over cleverness.
3. Single Responsibility - Each function/class does one thing well.
4. Fail fast - Validate inputs early, throw clear errors.
5. No magic numbers - Use named constants.
6. Guard clauses - Handle edge cases early and return.
7. Descriptive names - Variables and functions should be self-documenting."
# dry_principle
Don't Repeat Yourself. Every piece of knowledge should have a single, unambiguous representation in the system.
❌ Anti-pattern
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
function checkUserEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
} ✅ Better
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function isValidEmail(email) {
return EMAIL_REGEX.test(email);
}
// Reuse isValidEmail wherever needed # kiss_principle
Keep It Simple, Stupid. The simplest solution that works is usually the best. Avoid over-engineering.
❌ Over-engineered
class UserNameFormatter {
constructor(strategy) {
this.strategy = strategy;
}
format(user) {
return this.strategy.format(user);
}
}
// Plus 3 strategy classes... ✅ Simple
function formatUserName(user) {
return `${user.firstName} ${user.lastName}`;
}
// Add complexity only when needed # solid_principles
S — Single Responsibility
A class/function should have only one reason to change.
O — Open/Closed
Open for extension, closed for modification. Use composition.
L — Liskov Substitution
Subtypes must be substitutable for their base types.
I — Interface Segregation
Many specific interfaces beat one general-purpose interface.
D — Dependency Inversion
Depend on abstractions, not concretions. Inject dependencies.
# error_handling
Fail fast, fail explicitly. Validate early, provide context in errors, and never swallow exceptions silently.
async function processOrder(orderId) {
// Validate early (guard clause)
if (!orderId) {
throw new Error('Order ID is required');
}
const order = await getOrder(orderId);
if (!order) {
throw new Error(`Order not found: ${orderId}`);
}
// Happy path continues...
return processValidOrder(order);
} - • Guard clauses first: Handle invalid states at the top
- • Include context: What failed? What were the inputs?
- • Use custom errors: Create specific error types when needed
- • Log at boundaries: Log errors at API/system boundaries
# naming_conventions
Good names eliminate the need for comments. Code should read like prose.
❌ Unclear
const d = new Date();
const arr = users.filter(u => u.a);
function proc(x) { ... } ✅ Clear
const createdAt = new Date();
const activeUsers = users.filter(u => u.isActive);
function processPayment(order) { ... } • Booleans: Use is/has/can prefix (isActive, hasPermission)
• Functions: Use verbs (get, set, create, delete, validate)
• Arrays: Use plural nouns (users, orders, items)
• Constants: SCREAMING_SNAKE_CASE for true constants
# code_organization
File Structure
- • One component/class per file
- • Group related files in directories
- • Keep files under 300 lines (split if larger)
- • Co-locate tests with implementation
Function Guidelines
- • Functions under 30 lines (extract if larger)
- • Maximum 3-4 parameters (use objects for more)
- • Single level of abstraction per function
- • Pure functions where possible (no side effects)
Import Order
// 1. Built-in modules
import fs from 'fs';
// 2. External packages
import express from 'express';
// 3. Internal modules (absolute paths)
import { db } from '@/lib/database';
// 4. Relative imports
import { helper } from './helper'; # testing_philosophy
Tests are documentation. They show how code is meant to be used.
Testing Pyramid
70% Unit tests → 20% Integration tests → 10% E2E tests
- • Test behavior, not implementation: Tests should survive refactoring
- • Arrange-Act-Assert: Clear test structure
- • One assertion per test: Tests should fail for one reason
- • Descriptive test names: "should return empty array when no users exist"
- • Test edge cases: Empty inputs, null values, boundaries