Kaizen skill
Guide for continuous improvement, error proofing, and standardization.
by davila7·MIT license·★ 32,299 Stars on the repo·GitHub ↗
npx degit davila7/claude-code-templates/cli-tool/components/skills/productivity/kaizen#main ~/.claude/skills/kaizenChecked ·commit main
Files of Kaizen
Show the full text731 lines
Kaizen: Continuous Improvement
Overview
Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed.
Core principle: Many small improvements beat one big change. Prevent errors at design time, not with fixes.
When to Use
Always applied for:
- Code implementation and refactoring
- Architecture and design decisions
- Process and workflow improvements
- Error handling and validation
Philosophy: Quality through incremental progress and prevention, not perfection through massive effort.
The Four Pillars
1. Continuous Improvement (Kaizen)
Small, frequent improvements compound into major gains.
Principles
Incremental over revolutionary:
- Make smallest viable change that improves quality
- One improvement at a time
- Verify each change before next
- Build momentum through small wins
Always leave code better:
- Fix small issues as you encounter them
- Refactor while you work (within scope)
- Update outdated comments
- Remove dead code when you see it
Iterative refinement:
- First version: make it work
- Second pass: make it clear
- Third pass: make it efficient
- Don't try all three at once
// Iteration 2: Make it clear (refactor) const calculateTotal = (items: Item[]): number => { return items.reduce((total, item) => { return total + (item.price * item.quantity); }, 0); };
// Iteration 3: Make it robust (add validation) const calculateTotal = (items: Item[]): number => { if (!items?.length) return 0;
return items.reduce((total, item) => { if (item.price < 0 || item.quantity < 0) { throw new Error('Price and quantity must be non-negative'); } return total + (item.price * item.quantity); }, 0); };
Each step is complete, tested, and working
</Good>
<Bad>
```typescript
// Trying to do everything at once
const calculateTotal = (items: Item[]): number => {
// Validate, optimize, add features, handle edge cases all together
if (!items?.length) return 0;
const validItems = items.filter(item => {
if (item.price < 0) throw new Error('Negative price');
if (item.quantity < 0) throw new Error('Negative quantity');
return item.quantity > 0; // Also filtering zero quantities
});
// Plus caching, plus logging, plus currency conversion...
return validItems.reduce(...); // Too many concerns at once
};
Overwhelming, error-prone, hard to verify </Bad>
In Practice
When implementing features:
- Start with simplest version that works
- Add one improvement (error handling, validation, etc.)
- Test and verify
- Repeat if time permits
- Don't try to make it perfect immediately
When refactoring:
- Fix one smell at a time
- Commit after each improvement
- Keep tests passing throughout
- Stop when "good enough" (diminishing returns)
When reviewing code:
- Suggest incremental improvements (not rewrites)
- Prioritize: critical → important → nice-to-have
- Focus on highest-impact changes first
- Accept "better than before" even if not perfect
2. Poka-Yoke (Error Proofing)
Design systems that prevent errors at compile/design time, not runtime.
Principles
Make errors impossible:
- Type system catches mistakes
- Compiler enforces contracts
- Invalid states unrepresentable
- Errors caught early (left of production)
Design for safety:
- Fail fast and loudly
- Provide helpful error messages
- Make correct path obvious
- Make incorrect path difficult
Defense in layers:
- Type system (compile time)
- Validation (runtime, early)
- Guards (preconditions)
- Error boundaries (graceful degradation)
Type System Error Proofing
<Good> ```typescript // Error: string status can be any value type OrderBad = { status: string; // Can be "pending", "PENDING", "pnding", anything! total: number; };// Good: Only valid states possible type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered'; type Order = { status: OrderStatus; total: number; };
// Better: States with associated data type Order = | { status: 'pending'; createdAt: Date } | { status: 'processing'; startedAt: Date; estimatedCompletion: Date } | { status: 'shipped'; trackingNumber: string; shippedAt: Date } | { status: 'delivered'; deliveredAt: Date; signature: string };
// Now impossible to have shipped without trackingNumber
Type system prevents entire classes of errors
</Good>
<Good>
```typescript
// Make invalid states unrepresentable
type NonEmptyArray<T> = [T, ...T[]];
const firstItem = <T>(items: NonEmptyArray<T>): T => {
return items[0]; // Always safe, never undefined!
};
// Caller must prove array is non-empty
const items: number[] = [1, 2, 3];
if (items.length > 0) {
firstItem(items as NonEmptyArray<number>); // Safe
}
Function signature guarantees safety </Good>
Validation Error Proofing
<Good> ```typescript // Error: Validation after use const processPayment = (amount: number) => { const fee = amount * 0.03; // Used before validation! if (amount <= 0) throw new Error('Invalid amount'); // ... };// Good: Validate immediately const processPayment = (amount: number) => { if (amount <= 0) { throw new Error('Payment amount must be positive'); } if (amount > 10000) { throw new Error('Payment exceeds maximum allowed'); }
const fee = amount * 0.03; // ... now safe to use };
// Better: Validation at boundary with branded type type PositiveNumber = number & { readonly __brand: 'PositiveNumber' };
const validatePositive = (n: number): PositiveNumber => { if (n <= 0) throw new Error('Must be positive'); return n as PositiveNumber; };
const processPayment = (amount: PositiveNumber) => { // amount is guaranteed positive, no need to check const fee = amount * 0.03; };
// Validate at system boundary const handlePaymentRequest = (req: Request) => { const amount = validatePositive(req.body.amount); // Validate once processPayment(amount); // Use everywhere safely };
Validate once at boundary, safe everywhere else
</Good>
#### Guards and Preconditions
<Good>
```typescript
// Early returns prevent deeply nested code
const processUser = (user: User | null) => {
if (!user) {
logger.error('User not found');
return;
}
if (!user.email) {
logger.error('User email missing');
return;
}
if (!user.isActive) {
logger.info('User inactive, skipping');
return;
}
// Main logic here, guaranteed user is valid and active
sendEmail(user.email, 'Welcome!');
};
Guards make assumptions explicit and enforced </Good>
Configuration Error Proofing
<Good> ```typescript // Error: Optional config with unsafe defaults type ConfigBad = { apiKey?: string; timeout?: number; };const client = new APIClient({ timeout: 5000 }); // apiKey missing!
// Good: Required config, fails early type Config = { apiKey: string; timeout: number; };
const loadConfig = (): Config => { const apiKey = process.env.API_KEY; if (!apiKey) { throw new Error('API_KEY environment variable required'); }
return { apiKey, timeout: 5000, }; };
// App fails at startup if config invalid, not during request const config = loadConfig(); const client = new APIClient(config);
Fail at startup, not in production
</Good>
#### In Practice
**When designing APIs:**
- Use types to constrain inputs
- Make invalid states unrepresentable
- Return Result<T, E> instead of throwing
- Document preconditions in types
**When handling errors:**
- Validate at system boundaries
- Use guards for preconditions
- Fail fast with clear messages
- Log context for debugging
**When configuring:**
- Required over optional with defaults
- Validate all config at startup
- Fail deployment if config invalid
- Don't allow partial configurations
### 3. Standardized Work
Follow established patterns. Document what works. Make good practices easy to follow.
#### Principles
**Consistency over cleverness:**
- Follow existing codebase patterns
- Don't reinvent solved problems
- New pattern only if significantly better
- Team agreement on new patterns
**Documentation lives with code:**
- README for setup and architecture
- CLAUDE.md for AI coding conventions
- Comments for "why", not "what"
- Examples for complex patterns
**Automate standards:**
- Linters enforce style
- Type checks enforce contracts
- Tests verify behavior
- CI/CD enforces quality gates
#### Following Patterns
<Good>
```typescript
// Existing codebase pattern for API clients
class UserAPIClient {
async getUser(id: string): Promise<User> {
return this.fetch(`/users/${id}`);
}
}
// New code follows the same pattern
class OrderAPIClient {
async getOrder(id: string): Promise<Order> {
return this.fetch(`/orders/${id}`);
}
}
Consistency makes codebase predictable </Good>
<Bad> ```typescript // Existing pattern uses classes class UserAPIClient { /* ... */ }// New code introduces different pattern without discussion const getOrder = async (id: string): Promise<Order> => { // Breaking consistency "because I prefer functions" };
Inconsistency creates confusion
</Bad>
#### Error Handling Patterns
<Good>
```typescript
// Project standard: Result type for recoverable errors
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
// All services follow this pattern
const fetchUser = async (id: string): Promise<Result<User, Error>> => {
try {
const user = await db.users.findById(id);
if (!user) {
return { ok: false, error: new Error('User not found') };
}
return { ok: true, value: user };
} catch (err) {
return { ok: false, error: err as Error };
}
};
// Callers use consistent pattern
const result = await fetchUser('123');
if (!result.ok) {
logger.error('Failed to fetch user', result.error);
return;
}
const user = result.value; // Type-safe!
Standard pattern across codebase </Good>
Documentation Standards
<Good> ```typescript /** * Retries an async operation with exponential backoff. * * Why: Network requests fail temporarily; retrying improves reliability * When to use: External API calls, database operations * When not to use: User input validation, internal function calls * * @example * const result = await retry( * () => fetch('https://api.example.com/data'), * { maxAttempts: 3, baseDelay: 1000 } * ); */ const retry = async <T>( operation: () => Promise<T>, options: RetryOptions ): Promise<T> => { // Implementation... }; ``` Documents why, when, and how </Good>In Practice
Before adding new patterns:
- Search codebase for similar problems solved
- Check CLAUDE.md for project conventions
- Discuss with team if breaking from pattern
- Update docs when introducing new pattern
When writing code:
- Match existing file structure
- Use same naming conventions
- Follow same error handling approach
- Import from same locations
When reviewing:
- Check consistency with existing code
- Point to examples in codebase
- Suggest aligning with standards
- Update CLAUDE.md if new standard emerges
4. Just-In-Time (JIT)
Build what's needed now. No more, no less. Avoid premature optimization and over-engineering.
Principles
YAGNI (You Aren't Gonna Need It):
- Implement only current requirements
- No "just in case" features
- No "we might need this later" code
- Delete speculation
Simplest thing that works:
- Start with straightforward solution
- Add complexity only when needed
- Refactor when requirements change
- Don't anticipate future needs
Optimize when measured:
- No premature optimization
- Profile before optimizing
- Measure impact of changes
- Accept "good enough" performance
YAGNI in Action
<Good> ```typescript // Current requirement: Log errors to console const logError = (error: Error) => { console.error(error.message); }; ``` Simple, meets current need </Good><Bad> ```typescript // Over-engineered for "future needs" interface LogTransport { write(level: LogLevel, message: string, meta?: LogMetadata): Promise<void>; }class ConsoleTransport implements LogTransport { /... / } class FileTransport implements LogTransport { / ... / } class RemoteTransport implements LogTransport { / .../ }
class Logger { private transports: LogTransport[] = []; private queue: LogEntry[] = []; private rateLimiter: RateLimiter; private formatter: LogFormatter;
// 200 lines of code for "maybe we'll need it" }
const logError = (error: Error) => { Logger.getInstance().log('error', error.message); };
Building for imaginary future requirements
</Bad>
**When to add complexity:**
- Current requirement demands it
- Pain points identified through use
- Measured performance issues
- Multiple use cases emerged
<Good>
```typescript
// Start simple
const formatCurrency = (amount: number): string => {
return `$${amount.toFixed(2)}`;
};
// Requirement evolves: support multiple currencies
const formatCurrency = (amount: number, currency: string): string => {
const symbols = { USD: '$', EUR: '€', GBP: '£' };
return `${symbols[currency]}${amount.toFixed(2)}`;
};
// Requirement evolves: support localization
const formatCurrency = (amount: number, locale: string): string => {
return new Intl.NumberFormat(locale, {\n style: 'currency',
currency: locale === 'en-US' ? 'USD' : 'EUR',
}).format(amount);
};
Complexity added only when needed </Good>
Premature Abstraction
<Bad> ```typescript // One use case, but building generic framework abstract class BaseCRUDService<T> { abstract getAll(): Promise<T[]>; abstract getById(id: string): Promise<T>; abstract create(data: Partial<T>): Promise<T>; abstract update(id: string, data: Partial<T>): Promise<T>; abstract delete(id: string): Promise<void>; }class GenericRepository<T> { /300 lines / } class QueryBuilder<T> { / 200 lines/ } // ... building entire ORM for single table
Massive abstraction for uncertain future
</Bad>
<Good>
```typescript
// Simple functions for current needs
const getUsers = async (): Promise<User[]> => {
return db.query('SELECT * FROM users');
};
const getUserById = async (id: string): Promise<User | null> => {
return db.query('SELECT * FROM users WHERE id = $1', [id]);
};
// When pattern emerges across multiple entities, then abstract
Abstract only when pattern proven across 3+ cases </Good>
Performance Optimization
<Good> ```typescript // Current: Simple approach const filterActiveUsers = (users: User[]): User[] => { return users.filter(user => user.isActive); };// Benchmark shows: 50ms for 1000 users (acceptable) // ✓ Ship it, no optimization needed
// Later: After profiling shows this is bottleneck // Then optimize with indexed lookup or caching
Optimize based on measurement, not assumptions
</Good>
<Bad>
```typescript
// Premature optimization
const filterActiveUsers = (users: User[]): User[] => {
// "This might be slow, so let's cache and index"
const cache = new WeakMap();
const indexed = buildBTreeIndex(users, 'isActive');
// 100 lines of optimization code
// Adds complexity, harder to maintain
// No evidence it was needed
};\
Complex solution for unmeasured problem </Bad>
In Practice
When implementing:
- Solve the immediate problem
- Use straightforward approach
- Resist "what if" thinking
- Delete speculative code
When optimizing:
- Profile first, optimize second
- Measure before and after
- Document why optimization needed
- Keep simple version in tests
When abstracting:
- Wait for 3+ similar cases (Rule of Three)
- Make abstraction as simple as possible
- Prefer duplication over wrong abstraction
- Refactor when pattern clear
Integration with Commands
The Kaizen skill guides how you work. The commands provide structured analysis:
/why: Root cause analysis (5 Whys)/cause-and-effect: Multi-factor analysis (Fishbone)/plan-do-check-act: Iterative improvement cycles/analyse-problem: Comprehensive documentation (A3)/analyse: Smart method selection (Gemba/VSM/Muda)
Use commands for structured problem-solving. Apply skill for day-to-day development.
Red Flags
Violating Continuous Improvement:
- "I'll refactor it later" (never happens)
- Leaving code worse than you found it
- Big bang rewrites instead of incremental
Violating Poka-Yoke:
- "Users should just be careful"
- Validation after use instead of before
- Optional config with no validation
Violating Standardized Work:
- "I prefer to do it my way"
- Not checking existing patterns
- Ignoring project conventions
Violating Just-In-Time:
- "We might need this someday"
- Building frameworks before using them
- Optimizing without measuring
Remember
Kaizen is about:
- Small improvements continuously
- Preventing errors by design
- Following proven patterns
- Building only what's needed
Not about:
- Perfection on first try
- Massive refactoring projects
- Clever abstractions
- Premature optimization
Mindset: Good enough today, better tomorrow. Repeat.
| 1 | |
| 2 | name kaizen |
| 3 | description Guide for continuous improvement, error proofing, and standardization. Use this skill when the user wants to improve code quality, refactor, or discuss process improvements. |
| 4 | |
| 5 | |
| 6 | # Kaizen: Continuous Improvement |
| 7 | |
| 8 | ## Overview |
| 9 | |
| 10 | Small improvements, continuously. Error-proof by design. Follow what works. Build only what's needed. |
| 11 | |
| 12 | **Core principle:** Many small improvements beat one big change. Prevent errors at design time, not with fixes. |
| 13 | |
| 14 | ## When to Use |
| 15 | |
| 16 | **Always applied for:** |
| 17 | |
| 18 | Code implementation and refactoring |
| 19 | Architecture and design decisions |
| 20 | Process and workflow improvements |
| 21 | Error handling and validation |
| 22 | |
| 23 | **Philosophy:** Quality through incremental progress and prevention, not perfection through massive effort. |
| 24 | |
| 25 | ## The Four Pillars |
| 26 | |
| 27 | ### 1. Continuous Improvement (Kaizen) |
| 28 | |
| 29 | Small, frequent improvements compound into major gains. |
| 30 | |
| 31 | #### Principles |
| 32 | |
| 33 | **Incremental over revolutionary:** |
| 34 | |
| 35 | Make smallest viable change that improves quality |
| 36 | One improvement at a time |
| 37 | Verify each change before next |
| 38 | Build momentum through small wins |
| 39 | |
| 40 | **Always leave code better:** |
| 41 | |
| 42 | Fix small issues as you encounter them |
| 43 | Refactor while you work (within scope) |
| 44 | Update outdated comments |
| 45 | Remove dead code when you see it |
| 46 | |
| 47 | **Iterative refinement:** |
| 48 | |
| 49 | First version: make it work |
| 50 | Second pass: make it clear |
| 51 | Third pass: make it efficient |
| 52 | Don't try all three at once |
| 53 | |
| 54 | <Good> |
| 55 | |
| 56 | // Iteration 1: Make it work |
| 57 | const calculateTotal = (items: Item[]) => { |
| 58 | let total = 0; |
| 59 | for (let i = 0; i < items.length; i++) { |
| 60 | total += items[i].price * items[i].quantity; |
| 61 | } |
| 62 | return total; |
| 63 | }; |
| 64 | |
| 65 | // Iteration 2: Make it clear (refactor) |
| 66 | const calculateTotal = (items: Item[]): number => { |
| 67 | return items.reduce((total, item) => { |
| 68 | return total + (item.price \* item.quantity); |
| 69 | }, 0); |
| 70 | }; |
| 71 | |
| 72 | // Iteration 3: Make it robust (add validation) |
| 73 | const calculateTotal = (items: Item[]): number => { |
| 74 | if (!items?.length) return 0; |
| 75 | |
| 76 | return items.reduce((total, item) => { |
| 77 | if (item.price < 0 || item.quantity < 0) { |
| 78 | throw new Error('Price and quantity must be non-negative'); |
| 79 | } |
| 80 | return total + (item.price \* item.quantity); |
| 81 | }, 0); |
| 82 | }; |
| 83 | |
| 84 | |
| 85 | Each step is complete, tested, and working |
| 86 | </Good> |
| 87 | |
| 88 | <Bad> |
| 89 | |
| 90 | // Trying to do everything at once |
| 91 | const calculateTotal = (items: Item[]): number => { |
| 92 | // Validate, optimize, add features, handle edge cases all together |
| 93 | if (!items?.length) return 0; |
| 94 | const validItems = items.filter(item => { |
| 95 | if (item.price < 0) throw new Error('Negative price'); |
| 96 | if (item.quantity < 0) throw new Error('Negative quantity'); |
| 97 | return item.quantity > 0; // Also filtering zero quantities |
| 98 | }); |
| 99 | // Plus caching, plus logging, plus currency conversion... |
| 100 | return validItems.reduce(...); // Too many concerns at once |
| 101 | }; |
| 102 | |
| 103 | |
| 104 | Overwhelming, error-prone, hard to verify |
| 105 | </Bad> |
| 106 | |
| 107 | #### In Practice |
| 108 | |
| 109 | **When implementing features:** |
| 110 | |
| 111 | Start with simplest version that works |
| 112 | Add one improvement (error handling, validation, etc.) |
| 113 | Test and verify |
| 114 | Repeat if time permits |
| 115 | Don't try to make it perfect immediately |
| 116 | |
| 117 | **When refactoring:** |
| 118 | |
| 119 | Fix one smell at a time |
| 120 | Commit after each improvement |
| 121 | Keep tests passing throughout |
| 122 | Stop when "good enough" (diminishing returns) |
| 123 | |
| 124 | **When reviewing code:** |
| 125 | |
| 126 | Suggest incremental improvements (not rewrites) |
| 127 | Prioritize: critical → important → nice-to-have |
| 128 | Focus on highest-impact changes first |
| 129 | Accept "better than before" even if not perfect |
| 130 | |
| 131 | ### 2. Poka-Yoke (Error Proofing) |
| 132 | |
| 133 | Design systems that prevent errors at compile/design time, not runtime. |
| 134 | |
| 135 | #### Principles |
| 136 | |
| 137 | **Make errors impossible:** |
| 138 | |
| 139 | Type system catches mistakes |
| 140 | Compiler enforces contracts |
| 141 | Invalid states unrepresentable |
| 142 | Errors caught early (left of production) |
| 143 | |
| 144 | **Design for safety:** |
| 145 | |
| 146 | Fail fast and loudly |
| 147 | Provide helpful error messages |
| 148 | Make correct path obvious |
| 149 | Make incorrect path difficult |
| 150 | |
| 151 | **Defense in layers:** |
| 152 | |
| 153 | Type system (compile time) |
| 154 | Validation (runtime, early) |
| 155 | Guards (preconditions) |
| 156 | Error boundaries (graceful degradation) |
| 157 | |
| 158 | #### Type System Error Proofing |
| 159 | |
| 160 | <Good> |
| 161 | |
| 162 | // Error: string status can be any value |
| 163 | type OrderBad = { |
| 164 | status: string; // Can be "pending", "PENDING", "pnding", anything! |
| 165 | total: number; |
| 166 | }; |
| 167 | |
| 168 | // Good: Only valid states possible |
| 169 | type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered'; |
| 170 | type Order = { |
| 171 | status: OrderStatus; |
| 172 | total: number; |
| 173 | }; |
| 174 | |
| 175 | // Better: States with associated data |
| 176 | type Order = |
| 177 | | { status: 'pending'; createdAt: Date } |
| 178 | | { status: 'processing'; startedAt: Date; estimatedCompletion: Date } |
| 179 | | { status: 'shipped'; trackingNumber: string; shippedAt: Date } |
| 180 | | { status: 'delivered'; deliveredAt: Date; signature: string }; |
| 181 | |
| 182 | // Now impossible to have shipped without trackingNumber |
| 183 | |
| 184 | |
| 185 | Type system prevents entire classes of errors |
| 186 | </Good> |
| 187 | |
| 188 | <Good> |
| 189 | |
| 190 | // Make invalid states unrepresentable |
| 191 | type NonEmptyArray<T> = [T, ...T[]]; |
| 192 | |
| 193 | const firstItem = <T>(items: NonEmptyArray<T>): T => { |
| 194 | return items[0]; // Always safe, never undefined! |
| 195 | }; |
| 196 | |
| 197 | // Caller must prove array is non-empty |
| 198 | const items: number[] = [1, 2, 3]; |
| 199 | if (items.length > 0) { |
| 200 | firstItem(items as NonEmptyArray<number>); // Safe |
| 201 | } |
| 202 | |
| 203 | |
| 204 | Function signature guarantees safety |
| 205 | </Good> |
| 206 | |
| 207 | #### Validation Error Proofing |
| 208 | |
| 209 | <Good> |
| 210 | |
| 211 | // Error: Validation after use |
| 212 | const processPayment = (amount: number) => { |
| 213 | const fee = amount * 0.03; // Used before validation! |
| 214 | if (amount <= 0) throw new Error('Invalid amount'); |
| 215 | // ... |
| 216 | }; |
| 217 | |
| 218 | // Good: Validate immediately |
| 219 | const processPayment = (amount: number) => { |
| 220 | if (amount <= 0) { |
| 221 | throw new Error('Payment amount must be positive'); |
| 222 | } |
| 223 | if (amount > 10000) { |
| 224 | throw new Error('Payment exceeds maximum allowed'); |
| 225 | } |
| 226 | |
| 227 | const fee = amount \* 0.03; |
| 228 | // ... now safe to use |
| 229 | }; |
| 230 | |
| 231 | // Better: Validation at boundary with branded type |
| 232 | type PositiveNumber = number & { readonly \_\_brand: 'PositiveNumber' }; |
| 233 | |
| 234 | const validatePositive = (n: number): PositiveNumber => { |
| 235 | if (n <= 0) throw new Error('Must be positive'); |
| 236 | return n as PositiveNumber; |
| 237 | }; |
| 238 | |
| 239 | const processPayment = (amount: PositiveNumber) => { |
| 240 | // amount is guaranteed positive, no need to check |
| 241 | const fee = amount \* 0.03; |
| 242 | }; |
| 243 | |
| 244 | // Validate at system boundary |
| 245 | const handlePaymentRequest = (req: Request) => { |
| 246 | const amount = validatePositive(req.body.amount); // Validate once |
| 247 | processPayment(amount); // Use everywhere safely |
| 248 | }; |
| 249 | |
| 250 | |
| 251 | Validate once at boundary, safe everywhere else |
| 252 | </Good> |
| 253 | |
| 254 | #### Guards and Preconditions |
| 255 | |
| 256 | <Good> |
| 257 | |
| 258 | // Early returns prevent deeply nested code |
| 259 | const processUser = (user: User | null) => { |
| 260 | if (!user) { |
| 261 | logger.error('User not found'); |
| 262 | return; |
| 263 | } |
| 264 | |
| 265 | if (!user.email) { |
| 266 | logger.error('User email missing'); |
| 267 | return; |
| 268 | } |
| 269 | |
| 270 | if (!user.isActive) { |
| 271 | logger.info('User inactive, skipping'); |
| 272 | return; |
| 273 | } |
| 274 | |
| 275 | // Main logic here, guaranteed user is valid and active |
| 276 | sendEmail(user.email, 'Welcome!'); |
| 277 | }; |
| 278 | |
| 279 | |
| 280 | Guards make assumptions explicit and enforced |
| 281 | </Good> |
| 282 | |
| 283 | #### Configuration Error Proofing |
| 284 | |
| 285 | <Good> |
| 286 | |
| 287 | // Error: Optional config with unsafe defaults |
| 288 | type ConfigBad = { |
| 289 | apiKey?: string; |
| 290 | timeout?: number; |
| 291 | }; |
| 292 | |
| 293 | const client = new APIClient({ timeout: 5000 }); // apiKey missing! |
| 294 | |
| 295 | // Good: Required config, fails early |
| 296 | type Config = { |
| 297 | apiKey: string; |
| 298 | timeout: number; |
| 299 | }; |
| 300 | |
| 301 | const loadConfig = (): Config => { |
| 302 | const apiKey = process.env.API_KEY; |
| 303 | if (!apiKey) { |
| 304 | throw new Error('API_KEY environment variable required'); |
| 305 | } |
| 306 | |
| 307 | return { |
| 308 | apiKey, |
| 309 | timeout: 5000, |
| 310 | }; |
| 311 | }; |
| 312 | |
| 313 | // App fails at startup if config invalid, not during request |
| 314 | const config = loadConfig(); |
| 315 | const client = new APIClient(config); |
| 316 | |
| 317 | |
| 318 | Fail at startup, not in production |
| 319 | </Good> |
| 320 | |
| 321 | #### In Practice |
| 322 | |
| 323 | **When designing APIs:** |
| 324 | Use types to constrain inputs |
| 325 | Make invalid states unrepresentable |
| 326 | Return Result<T, E> instead of throwing |
| 327 | Document preconditions in types |
| 328 | |
| 329 | **When handling errors:** |
| 330 | Validate at system boundaries |
| 331 | |
| 332 | Use guards for preconditions |
| 333 | Fail fast with clear messages |
| 334 | Log context for debugging |
| 335 | |
| 336 | **When configuring:** |
| 337 | Required over optional with defaults |
| 338 | Validate all config at startup |
| 339 | Fail deployment if config invalid |
| 340 | Don't allow partial configurations |
| 341 | |
| 342 | ### 3. Standardized Work |
| 343 | Follow established patterns. Document what works. Make good practices easy to follow. |
| 344 | |
| 345 | #### Principles |
| 346 | |
| 347 | **Consistency over cleverness:** |
| 348 | Follow existing codebase patterns |
| 349 | Don't reinvent solved problems |
| 350 | New pattern only if significantly better |
| 351 | Team agreement on new patterns |
| 352 | |
| 353 | **Documentation lives with code:** |
| 354 | README for setup and architecture |
| 355 | CLAUDE.md for AI coding conventions |
| 356 | Comments for "why", not "what" |
| 357 | Examples for complex patterns |
| 358 | |
| 359 | **Automate standards:** |
| 360 | Linters enforce style |
| 361 | Type checks enforce contracts |
| 362 | Tests verify behavior |
| 363 | CI/CD enforces quality gates |
| 364 | |
| 365 | #### Following Patterns |
| 366 | |
| 367 | <Good> |
| 368 | |
| 369 | // Existing codebase pattern for API clients |
| 370 | class UserAPIClient { |
| 371 | async getUser(id: string): Promise<User> { |
| 372 | return this.fetch(`/users/${id}`); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | // New code follows the same pattern |
| 377 | class OrderAPIClient { |
| 378 | async getOrder(id: string): Promise<Order> { |
| 379 | return this.fetch(`/orders/${id}`); |
| 380 | } |
| 381 | } |
| 382 | |
| 383 | |
| 384 | Consistency makes codebase predictable |
| 385 | </Good> |
| 386 | |
| 387 | <Bad> |
| 388 | |
| 389 | // Existing pattern uses classes |
| 390 | class UserAPIClient { /* ... */ } |
| 391 | |
| 392 | // New code introduces different pattern without discussion |
| 393 | const getOrder = async (id: string): Promise<Order> => { |
| 394 | // Breaking consistency "because I prefer functions" |
| 395 | }; |
| 396 | |
| 397 | |
| 398 | Inconsistency creates confusion |
| 399 | </Bad> |
| 400 | |
| 401 | #### Error Handling Patterns |
| 402 | |
| 403 | <Good> |
| 404 | |
| 405 | // Project standard: Result type for recoverable errors |
| 406 | type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; |
| 407 | |
| 408 | // All services follow this pattern |
| 409 | const fetchUser = async (id: string): Promise<Result<User, Error>> => { |
| 410 | try { |
| 411 | const user = await db.users.findById(id); |
| 412 | if (!user) { |
| 413 | return { ok: false, error: new Error('User not found') }; |
| 414 | } |
| 415 | return { ok: true, value: user }; |
| 416 | } catch (err) { |
| 417 | return { ok: false, error: err as Error }; |
| 418 | } |
| 419 | }; |
| 420 | |
| 421 | // Callers use consistent pattern |
| 422 | const result = await fetchUser('123'); |
| 423 | if (!result.ok) { |
| 424 | logger.error('Failed to fetch user', result.error); |
| 425 | return; |
| 426 | } |
| 427 | const user = result.value; // Type-safe! |
| 428 | |
| 429 | |
| 430 | Standard pattern across codebase |
| 431 | </Good> |
| 432 | |
| 433 | #### Documentation Standards |
| 434 | |
| 435 | <Good> |
| 436 | |
| 437 | /** |
| 438 | * Retries an async operation with exponential backoff. |
| 439 | * |
| 440 | * Why: Network requests fail temporarily; retrying improves reliability |
| 441 | * When to use: External API calls, database operations |
| 442 | * When not to use: User input validation, internal function calls |
| 443 | * |
| 444 | * @example |
| 445 | * const result = await retry( |
| 446 | * () => fetch('https://api.example.com/data'), |
| 447 | * { maxAttempts: 3, baseDelay: 1000 } |
| 448 | * ); |
| 449 | */ |
| 450 | const retry = async <T>( |
| 451 | operation: () => Promise<T>, |
| 452 | options: RetryOptions |
| 453 | ): Promise<T> => { |
| 454 | // Implementation... |
| 455 | }; |
| 456 | |
| 457 | Documents why, when, and how |
| 458 | </Good> |
| 459 | |
| 460 | #### In Practice |
| 461 | |
| 462 | **Before adding new patterns:** |
| 463 | |
| 464 | Search codebase for similar problems solved |
| 465 | Check CLAUDE.md for project conventions |
| 466 | Discuss with team if breaking from pattern |
| 467 | Update docs when introducing new pattern |
| 468 | |
| 469 | **When writing code:** |
| 470 | |
| 471 | Match existing file structure |
| 472 | Use same naming conventions |
| 473 | Follow same error handling approach |
| 474 | Import from same locations |
| 475 | |
| 476 | **When reviewing:** |
| 477 | |
| 478 | Check consistency with existing code |
| 479 | Point to examples in codebase |
| 480 | Suggest aligning with standards |
| 481 | Update CLAUDE.md if new standard emerges |
| 482 | |
| 483 | ### 4. Just-In-Time (JIT) |
| 484 | |
| 485 | Build what's needed now. No more, no less. Avoid premature optimization and over-engineering. |
| 486 | |
| 487 | #### Principles |
| 488 | |
| 489 | **YAGNI (You Aren't Gonna Need It):** |
| 490 | |
| 491 | Implement only current requirements |
| 492 | No "just in case" features |
| 493 | No "we might need this later" code |
| 494 | Delete speculation |
| 495 | |
| 496 | **Simplest thing that works:** |
| 497 | |
| 498 | Start with straightforward solution |
| 499 | Add complexity only when needed |
| 500 | Refactor when requirements change |
| 501 | Don't anticipate future needs |
| 502 | |
| 503 | **Optimize when measured:** |
| 504 | |
| 505 | No premature optimization |
| 506 | Profile before optimizing |
| 507 | Measure impact of changes |
| 508 | Accept "good enough" performance |
| 509 | |
| 510 | #### YAGNI in Action |
| 511 | |
| 512 | <Good> |
| 513 | |
| 514 | // Current requirement: Log errors to console |
| 515 | const logError = (error: Error) => { |
| 516 | console.error(error.message); |
| 517 | }; |
| 518 | |
| 519 | Simple, meets current need |
| 520 | </Good> |
| 521 | |
| 522 | <Bad> |
| 523 | |
| 524 | // Over-engineered for "future needs" |
| 525 | interface LogTransport { |
| 526 | write(level: LogLevel, message: string, meta?: LogMetadata): Promise<void>; |
| 527 | } |
| 528 | |
| 529 | class ConsoleTransport implements LogTransport { /_... _/ } |
| 530 | class FileTransport implements LogTransport { /_ ... _/ } |
| 531 | class RemoteTransport implements LogTransport { /_ ..._/ } |
| 532 | |
| 533 | class Logger { |
| 534 | private transports: LogTransport[] = []; |
| 535 | private queue: LogEntry[] = []; |
| 536 | private rateLimiter: RateLimiter; |
| 537 | private formatter: LogFormatter; |
| 538 | |
| 539 | // 200 lines of code for "maybe we'll need it" |
| 540 | } |
| 541 | |
| 542 | const logError = (error: Error) => { |
| 543 | Logger.getInstance().log('error', error.message); |
| 544 | }; |
| 545 | |
| 546 | |
| 547 | Building for imaginary future requirements |
| 548 | </Bad> |
| 549 | |
| 550 | **When to add complexity:** |
| 551 | Current requirement demands it |
| 552 | Pain points identified through use |
| 553 | Measured performance issues |
| 554 | Multiple use cases emerged |
| 555 | |
| 556 | <Good> |
| 557 | |
| 558 | // Start simple |
| 559 | const formatCurrency = (amount: number): string => { |
| 560 | return `$${amount.toFixed(2)}`; |
| 561 | }; |
| 562 | |
| 563 | // Requirement evolves: support multiple currencies |
| 564 | const formatCurrency = (amount: number, currency: string): string => { |
| 565 | const symbols = { USD: '$', EUR: '€', GBP: '£' }; |
| 566 | return `${symbols[currency]}${amount.toFixed(2)}`; |
| 567 | }; |
| 568 | |
| 569 | // Requirement evolves: support localization |
| 570 | const formatCurrency = (amount: number, locale: string): string => { |
| 571 | return new Intl.NumberFormat(locale, {\n style: 'currency', |
| 572 | currency: locale === 'en-US' ? 'USD' : 'EUR', |
| 573 | }).format(amount); |
| 574 | }; |
| 575 | |
| 576 | |
| 577 | Complexity added only when needed |
| 578 | </Good> |
| 579 | |
| 580 | #### Premature Abstraction |
| 581 | |
| 582 | <Bad> |
| 583 | |
| 584 | // One use case, but building generic framework |
| 585 | abstract class BaseCRUDService<T> { |
| 586 | abstract getAll(): Promise<T[]>; |
| 587 | abstract getById(id: string): Promise<T>; |
| 588 | abstract create(data: Partial<T>): Promise<T>; |
| 589 | abstract update(id: string, data: Partial<T>): Promise<T>; |
| 590 | abstract delete(id: string): Promise<void>; |
| 591 | } |
| 592 | |
| 593 | class GenericRepository<T> { /_300 lines _/ } |
| 594 | class QueryBuilder<T> { /_ 200 lines_/ } |
| 595 | // ... building entire ORM for single table |
| 596 | |
| 597 | |
| 598 | Massive abstraction for uncertain future |
| 599 | </Bad> |
| 600 | |
| 601 | <Good> |
| 602 | |
| 603 | // Simple functions for current needs |
| 604 | const getUsers = async (): Promise<User[]> => { |
| 605 | return db.query('SELECT * FROM users'); |
| 606 | }; |
| 607 | |
| 608 | const getUserById = async (id: string): Promise<User | null> => { |
| 609 | return db.query('SELECT * FROM users WHERE id = $1', [id]); |
| 610 | }; |
| 611 | |
| 612 | // When pattern emerges across multiple entities, then abstract |
| 613 | |
| 614 | |
| 615 | Abstract only when pattern proven across 3+ cases |
| 616 | </Good> |
| 617 | |
| 618 | #### Performance Optimization |
| 619 | |
| 620 | <Good> |
| 621 | |
| 622 | // Current: Simple approach |
| 623 | const filterActiveUsers = (users: User[]): User[] => { |
| 624 | return users.filter(user => user.isActive); |
| 625 | }; |
| 626 | |
| 627 | // Benchmark shows: 50ms for 1000 users (acceptable) |
| 628 | // ✓ Ship it, no optimization needed |
| 629 | |
| 630 | // Later: After profiling shows this is bottleneck |
| 631 | // Then optimize with indexed lookup or caching |
| 632 | |
| 633 | |
| 634 | Optimize based on measurement, not assumptions |
| 635 | </Good> |
| 636 | |
| 637 | <Bad> |
| 638 | |
| 639 | // Premature optimization |
| 640 | const filterActiveUsers = (users: User[]): User[] => { |
| 641 | // "This might be slow, so let's cache and index" |
| 642 | const cache = new WeakMap(); |
| 643 | const indexed = buildBTreeIndex(users, 'isActive'); |
| 644 | // 100 lines of optimization code |
| 645 | // Adds complexity, harder to maintain |
| 646 | // No evidence it was needed |
| 647 | };\ |
| 648 | |
| 649 | |
| 650 | Complex solution for unmeasured problem |
| 651 | </Bad> |
| 652 | |
| 653 | #### In Practice |
| 654 | |
| 655 | **When implementing:** |
| 656 | |
| 657 | Solve the immediate problem |
| 658 | Use straightforward approach |
| 659 | Resist "what if" thinking |
| 660 | Delete speculative code |
| 661 | |
| 662 | **When optimizing:** |
| 663 | |
| 664 | Profile first, optimize second |
| 665 | Measure before and after |
| 666 | Document why optimization needed |
| 667 | Keep simple version in tests |
| 668 | |
| 669 | **When abstracting:** |
| 670 | |
| 671 | Wait for 3+ similar cases (Rule of Three) |
| 672 | Make abstraction as simple as possible |
| 673 | Prefer duplication over wrong abstraction |
| 674 | Refactor when pattern clear |
| 675 | |
| 676 | ## Integration with Commands |
| 677 | |
| 678 | The Kaizen skill guides how you work. The commands provide structured analysis: |
| 679 | |
| 680 | **`/why`**: Root cause analysis (5 Whys) |
| 681 | **`/cause-and-effect`**: Multi-factor analysis (Fishbone) |
| 682 | **`/plan-do-check-act`**: Iterative improvement cycles |
| 683 | **`/analyse-problem`**: Comprehensive documentation (A3) |
| 684 | **`/analyse`**: Smart method selection (Gemba/VSM/Muda) |
| 685 | |
| 686 | Use commands for structured problem-solving. Apply skill for day-to-day development. |
| 687 | |
| 688 | ## Red Flags |
| 689 | |
| 690 | **Violating Continuous Improvement:** |
| 691 | |
| 692 | "I'll refactor it later" (never happens) |
| 693 | Leaving code worse than you found it |
| 694 | Big bang rewrites instead of incremental |
| 695 | |
| 696 | **Violating Poka-Yoke:** |
| 697 | |
| 698 | "Users should just be careful" |
| 699 | Validation after use instead of before |
| 700 | Optional config with no validation |
| 701 | |
| 702 | **Violating Standardized Work:** |
| 703 | |
| 704 | "I prefer to do it my way" |
| 705 | Not checking existing patterns |
| 706 | Ignoring project conventions |
| 707 | |
| 708 | **Violating Just-In-Time:** |
| 709 | |
| 710 | "We might need this someday" |
| 711 | Building frameworks before using them |
| 712 | Optimizing without measuring |
| 713 | |
| 714 | ## Remember |
| 715 | |
| 716 | **Kaizen is about:** |
| 717 | |
| 718 | Small improvements continuously |
| 719 | Preventing errors by design |
| 720 | Following proven patterns |
| 721 | Building only what's needed |
| 722 | |
| 723 | **Not about:** |
| 724 | |
| 725 | Perfection on first try |
| 726 | Massive refactoring projects |
| 727 | Clever abstractions |
| 728 | Premature optimization |
| 729 | |
| 730 | **Mindset:** Good enough today, better tomorrow. Repeat. |
| 731 |
Discussion
Browse more free Claude skills.