diff --git a/EXAMPLES.md b/EXAMPLES.md new file mode 100644 index 0000000..6c42835 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,522 @@ +# Examples + +Real-world code examples demonstrating the four principles. Each example shows what LLMs commonly do wrong and how to fix it. + +--- + +## 1. Think Before Coding + +### Example 1: Hidden Assumptions + +**User Request:** "Add a feature to export user data" + +**❌ What LLMs Do (Wrong Assumptions)** + +```python +def export_users(format='json'): + """Export all users to a file.""" + users = User.query.all() + + if format == 'json': + with open('users.json', 'w') as f: + json.dump([u.to_dict() for u in users], f) + elif format == 'csv': + with open('users.csv', 'w') as f: + writer = csv.DictWriter(f, fieldnames=['id', 'email', 'name']) + writer.writeheader() + writer.writerows([u.to_dict() for u in users]) + + return f"Exported {len(users)} users" +``` + +**Problems:** +- Assumed it should export ALL users (what about pagination? privacy?) +- Assumed file location without asking +- Assumed which fields to include +- Assumed CSV fieldnames without checking actual data structure + +**✅ What Should Happen (Surface Assumptions)** + +``` +Before implementing, I need to clarify: + +1. **Scope**: Export all users or filtered subset? (privacy implications) +2. **Format**: You mentioned "export" - did you mean: + - Download file in browser? + - Background job with email notification? + - API endpoint returning data? +3. **Fields**: Which user fields? (some might be sensitive) +4. **Volume**: How many users typically? (affects approach) + +Simplest approach: Add an API endpoint that returns paginated JSON. +Would need more info for file-based exports. + +What's your preference? +``` + +### Example 2: Multiple Interpretations + +**User Request:** "Make the search faster" + +**❌ What LLMs Do (Pick Silently)** + +```python +# Adds caching, database indexes, and async processing +# without asking which "faster" matters +@lru_cache(maxsize=1000) +async def search(query: str) -> List[Result]: + # ... 200 lines of optimization code +``` + +**✅ What Should Happen** + +``` +"Make the search faster" could mean: + +1. **Faster response time** (< 100ms instead of 500ms) + - Add database indexes + - Cache frequent queries + - Estimated effort: 2 hours + +2. **Handle more concurrent searches** (throughput) + - Add async/await + - Connection pooling + - Estimated effort: 4 hours + +3. **Faster perceived speed** (UX) + - Show partial results immediately + - Progressive loading + - Estimated effort: 3 hours + +Current search takes ~500ms for typical queries. +Which aspect matters most for your use case? +``` + +--- + +## 2. Simplicity First + +### Example 1: Over-abstraction + +**User Request:** "Add a function to calculate discount" + +**❌ What LLMs Do (Overengineered)** + +```python +from abc import ABC, abstractmethod +from enum import Enum +from typing import Protocol, Union +from dataclasses import dataclass + +class DiscountStrategy(ABC): + @abstractmethod + def calculate(self, amount: float) -> float: + pass + +class PercentageDiscount(DiscountStrategy): + def __init__(self, percentage: float): + self.percentage = percentage + + def calculate(self, amount: float) -> float: + return amount * (self.percentage / 100) + +class FixedDiscount(DiscountStrategy): + def __init__(self, fixed_amount: float): + self.fixed_amount = fixed_amount + + def calculate(self, amount: float) -> float: + return min(self.fixed_amount, amount) + +@dataclass +class DiscountConfig: + strategy: DiscountStrategy + min_purchase: float = 0.0 + max_discount: float = float('inf') + +class DiscountCalculator: + def __init__(self, config: DiscountConfig): + self.config = config + + def apply_discount(self, amount: float) -> float: + if amount < self.config.min_purchase: + return 0 + discount = self.config.strategy.calculate(amount) + return min(discount, self.config.max_discount) + +# Usage requires 30+ lines of setup for a simple calculation +``` + +**✅ What Should Happen (Simple)** + +```python +def calculate_discount(amount: float, percent: float) -> float: + """Calculate discount amount. percent should be 0-100.""" + return amount * (percent / 100) + +# Usage +discount = calculate_discount(100.0, 10.0) # $10 off +``` + +**When to add complexity:** Only when you actually need multiple discount types. If that requirement comes later, refactor then. + +### Example 2: Speculative Features + +**User Request:** "Save user preferences to database" + +**❌ What LLMs Do (Unnecessary Features)** + +```python +class PreferenceManager: + def __init__(self, db, cache=None, validator=None): + self.db = db + self.cache = cache or InMemoryCache() + self.validator = validator or DefaultValidator() + + def save(self, user_id: int, prefs: dict, + merge: bool = True, + validate: bool = True, + notify: bool = False) -> bool: + """Save preferences with optional merging, validation, and notifications.""" + + if validate: + errors = self.validator.validate(prefs) + if errors: + raise ValidationError(errors) + + existing = self.get(user_id) if merge else {} + + if merge: + existing.update(prefs) + prefs = existing + + self.db.execute( + "UPDATE users SET preferences = ? WHERE id = ?", + (json.dumps(prefs), user_id) + ) + + if self.cache: + self.cache.set(f"prefs:{user_id}", prefs) + + if notify: + self.notify_preference_change(user_id, prefs) + + return True + + def notify_preference_change(self, user_id: int, prefs: dict): + # Another 30 lines for a feature nobody asked for + pass +``` + +**✅ What Should Happen (Just What Was Asked)** + +```python +def save_preferences(db, user_id: int, preferences: dict): + """Save user preferences to database.""" + db.execute( + "UPDATE users SET preferences = ? WHERE id = ?", + (json.dumps(preferences), user_id) + ) +``` + +**Add later if needed:** Caching (when performance matters), validation (when bad data appears), merging (when requirement emerges). + +--- + +## 3. Surgical Changes + +### Example 1: Drive-by Refactoring + +**User Request:** "Fix the bug where empty emails crash the validator" + +**❌ What LLMs Do (Too Much)** + +```diff + def validate_user(user_data): +- # Check email format +- if not user_data.get('email'): ++ """Validate user data.""" ++ email = user_data.get('email', '').strip() ++ ++ # Validate email ++ if not email: + raise ValueError("Email required") +- +- # Basic email validation +- if '@' not in user_data['email']: ++ if '@' not in email or '.' not in email.split('@')[1]: + raise ValueError("Invalid email") + +- # Check username +- if not user_data.get('username'): ++ # Validate username ++ username = user_data.get('username', '').strip() ++ if not username: + raise ValueError("Username required") ++ if len(username) < 3: ++ raise ValueError("Username too short") ++ if not username.isalnum(): ++ raise ValueError("Username must be alphanumeric") + + return True +``` + +**Problems:** +- "Improved" email validation beyond the bug fix +- Added username validation nobody asked for +- Changed comments +- Added docstring + +**✅ What Should Happen (Surgical)** + +```diff + def validate_user(user_data): + # Check email format +- if not user_data.get('email'): ++ email = user_data.get('email', '') ++ if not email or not email.strip(): + raise ValueError("Email required") + + # Basic email validation +- if '@' not in user_data['email']: ++ if '@' not in email: + raise ValueError("Invalid email") + + # Check username + if not user_data.get('username'): + raise ValueError("Username required") + + return True +``` + +**Only changed:** The specific lines that fix empty email handling. + +### Example 2: Style Drift + +**User Request:** "Add logging to the upload function" + +**❌ What LLMs Do (Reformat Everything)** + +```diff +- def upload_file(file_path, destination): ++ def upload_file(file_path: str, destination: str) -> bool: ++ """Upload file to destination with logging.""" ++ logger.info(f"Uploading {file_path} to {destination}") ++ + try: +- with open(file_path, 'rb') as f: +- data = f.read() +- +- response = requests.post(destination, files={'file': data}) +- +- if response.status_code == 200: +- return True +- else: +- return False ++ with open(file_path, "rb") as f: ++ data = f.read() ++ response = requests.post(destination, files={"file": data}) ++ success = response.status_code == 200 ++ ++ if success: ++ logger.info(f"Upload successful: {file_path}") ++ else: ++ logger.error(f"Upload failed: {file_path}, status={response.status_code}") ++ ++ return success + except Exception as e: +- print(f"Error: {e}") ++ logger.exception(f"Upload error for {file_path}: {e}") + return False +``` + +**Problems:** +- Changed quote style ('' to "") +- Added type hints nobody asked for +- Added docstring +- Reformatted whitespace +- Changed boolean return logic + +**✅ What Should Happen (Match Existing Style)** + +```diff ++ import logging ++ ++ logger = logging.getLogger(__name__) ++ + def upload_file(file_path, destination): ++ logger.info(f'Starting upload: {file_path}') + try: + with open(file_path, 'rb') as f: + data = f.read() + + response = requests.post(destination, files={'file': data}) + + if response.status_code == 200: ++ logger.info(f'Upload successful: {file_path}') + return True + else: ++ logger.error(f'Upload failed: {file_path}, status={response.status_code}') + return False + except Exception as e: +- print(f"Error: {e}") ++ logger.exception(f'Upload error: {file_path}') + return False +``` + +**Matched:** Single quotes, no type hints, existing boolean pattern, spacing style. + +--- + +## 4. Goal-Driven Execution + +### Example 1: Vague vs. Verifiable + +**User Request:** "Fix the authentication system" + +**❌ What LLMs Do (Vague Approach)** + +``` +I'll fix the authentication system by: +1. Reviewing the code +2. Identifying issues +3. Making improvements +4. Testing the changes + +[Proceeds to make changes without clear success criteria] +``` + +**✅ What Should Happen (Verifiable Goals)** + +``` +To "fix authentication," I need to define success criteria. +What specific issue are we solving? + +If the issue is "users stay logged in after password change": + +Plan: +1. Write test: Change password → verify old session invalidated + Verify: Test fails (reproduces bug) + +2. Implement: Invalidate sessions on password change + Verify: Test passes + +3. Check edge cases: Multiple active sessions, concurrent changes + Verify: Additional tests pass + +4. Verify no regression: Existing auth tests still pass + Verify: Full test suite green + +Current test coverage for auth: [shows coverage] +What's the specific auth issue you're seeing? +``` + +### Example 2: Multi-Step with Verification + +**User Request:** "Add rate limiting to the API" + +**❌ What LLMs Do (All at Once)** + +```python +# Implements full rate limiting with Redis, multiple strategies, +# configuration system, and monitoring in one 300-line commit +# No clear verification steps +``` + +**✅ What Should Happen (Incremental with Verification)** + +``` +Plan for rate limiting: + +1. Add basic in-memory rate limiting (single endpoint) + Verify: + - Test: 100 requests → first 10 succeed, rest get 429 + - Manual: curl endpoint 11 times, see rate limit error + +2. Extract to middleware (apply to all endpoints) + Verify: + - Test: Rate limits apply to /users and /posts + - Existing endpoint tests still pass + +3. Add Redis backend (for multi-server) + Verify: + - Test: Rate limit persists across app restarts + - Test: Two app instances share rate limit counter + +4. Add configuration (rates per endpoint) + Verify: + - Test: /search allows 10/min, /users allows 100/min + - Config file parsed correctly + +Each step is independently verifiable and deployable. +Start with step 1? +``` + +### Example 3: Test-First Verification + +**User Request:** "The sorting breaks when there are duplicate scores" + +**❌ What LLMs Do (Fix Without Reproducing)** + +```python +# Immediately changes sort logic without confirming the bug +def sort_scores(scores): + return sorted(scores, key=lambda x: (-x['score'], x['name'])) +``` + +**✅ What Should Happen (Reproduce First)** + +```python +# 1. First, write a test that reproduces the issue +def test_sort_with_duplicate_scores(): + """Test sorting when multiple items have same score.""" + scores = [ + {'name': 'Alice', 'score': 100}, + {'name': 'Bob', 'score': 100}, + {'name': 'Charlie', 'score': 90}, + ] + + result = sort_scores(scores) + + # The bug: order is non-deterministic for duplicates + # Run this test multiple times, it should be consistent + assert result[0]['score'] == 100 + assert result[1]['score'] == 100 + assert result[2]['score'] == 90 + +# Verify: Run test 10 times → fails with inconsistent ordering + +# 2. Now fix with stable sort +def sort_scores(scores): + """Sort by score descending, then name ascending for ties.""" + return sorted(scores, key=lambda x: (-x['score'], x['name'])) + +# Verify: Test passes consistently +``` + +--- + +## Anti-Patterns Summary + +| Principle | Anti-Pattern | Fix | +|-----------|-------------|-----| +| Think Before Coding | Silently assumes file format, fields, scope | List assumptions explicitly, ask for clarification | +| Simplicity First | Strategy pattern for single discount calculation | One function until complexity is actually needed | +| Surgical Changes | Reformats quotes, adds type hints while fixing bug | Only change lines that fix the reported issue | +| Goal-Driven | "I'll review and improve the code" | "Write test for bug X → make it pass → verify no regressions" | + +## Key Insight + +The "overcomplicated" examples aren't obviously wrong—they follow design patterns and best practices. The problem is **timing**: they add complexity before it's needed, which: + +- Makes code harder to understand +- Introduces more bugs +- Takes longer to implement +- Harder to test + +The "simple" versions are: +- Easier to understand +- Faster to implement +- Easier to test +- Can be refactored later when complexity is actually needed + +**Good code is code that solves today's problem simply, not tomorrow's problem prematurely.**