Zum Hauptinhalt springen

Beispiele

Praxisnahe Code-Beispiele, die die vier Prinzipien demonstrieren. Jedes Beispiel zeigt, was LLMs typischerweise falsch machen und wie es besser geht.


1. Think Before Coding​

Beispiel 1: Versteckte Annahmen​

User Request: "Add a feature to export user data"

❌ Was LLMs tun (falsche Annahmen)

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"

Probleme:

  • Es wurde angenommen, dass alle Nutzer exportiert werden sollen
  • Der Dateipfad wurde ohne RĂĽckfrage angenommen
  • Die zu exportierenden Felder wurden stillschweigend gewählt
  • CSV-Feldnamen wurden festgelegt, ohne die echte Datenstruktur zu prĂĽfen

âś… Was passieren sollte (Annahmen offenlegen)

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?

Beispiel 2: Mehrere Interpretationen​

User Request: "Make the search faster"

❌ Was LLMs tun (stillschweigend auswählen)

# 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

âś… Was passieren sollte

"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​

Beispiel 1: Über-Abstraktion​

User Request: "Add a function to calculate discount"

❌ Was LLMs tun (overengineered)

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

âś… Was passieren sollte (einfach)

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

Wann Komplexität sinnvoll wird: Erst dann, wenn wirklich mehrere Discount-Typen gebraucht werden. Kommt diese Anforderung später, wird dann refactored.

Beispiel 2: Spekulative Features​

User Request: "Save user preferences to database"

❌ Was LLMs tun (unnötige Features)

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

âś… Was passieren sollte (genau das Angefragte)

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)
)

Später ergänzen, wenn nötig: Caching, Validierung und Merge-Logik erst dann ergänzen, wenn der Bedarf real ist.


3. Surgical Changes​

Beispiel 1: Drive-by-Refactoring​

User Request: "Fix the bug where empty emails crash the validator"

❌ Was LLMs tun (zu viel auf einmal)

  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

Probleme:

  • Die E-Mail-Validierung wurde ĂĽber den eigentlichen Bugfix hinaus verändert
  • Zusätzliche Username-Validierung wurde eingebaut, obwohl sie nicht angefragt war
  • Kommentare wurden verändert
  • Eine Docstring wurde ergänzt

âś… Was passieren sollte (chirurgisch)

  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

Geändert wurde nur: genau der Teil, der den Bug mit leeren E-Mails behebt.

Beispiel 2: Style Drift​

User Request: "Add logging to the upload function"

❌ Was LLMs tun (alles umformatieren)

- 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

Probleme:

  • Quote-Stil wurde geändert
  • Type Hints wurden hinzugefĂĽgt, obwohl sie nicht angefragt waren
  • Eine Docstring wurde ergänzt
  • Whitespace wurde umformatiert
  • Die Return-Logik wurde verändert

âś… Was passieren sollte (bestehenden Stil matchen)

+ 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

Beibehalten wurde: Single Quotes, keine Type Hints, bestehendes Boolean-Muster und bestehender Spacing-Stil.


4. Goal-Driven Execution​

Beispiel 1: Vage vs. überprüfbar​

User Request: "Fix the authentication system"

❌ Was LLMs tun (vager Ansatz)

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]

âś… Was passieren sollte (ĂĽberprĂĽfbare Ziele)

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?

Beispiel 2: Mehrere Schritte mit Verifikation​

User Request: "Add rate limiting to the API"

❌ Was LLMs tun (alles auf einmal)

# Implements full rate limiting with Redis, multiple strategies,
# configuration system, and monitoring in one 300-line commit
# No clear verification steps

âś… Was passieren sollte (inkrementell mit Verifikation)

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?

Beispiel 3: Test-First-Verifikation​

User Request: "The sorting breaks when there are duplicate scores"

❌ Was LLMs tun (fixen ohne Reproduktion)

# Immediately changes sort logic without confirming the bug
def sort_scores(scores):
return sorted(scores, key=lambda x: (-x['score'], x['name']))

âś… Was passieren sollte (erst reproduzieren)

# 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

Zusammenfassung der Anti-Patterns​

PrinzipAnti-PatternBesser
Think Before CodingDateiformat, Felder oder Scope stillschweigend annehmenAnnahmen explizit auflisten und RĂĽckfragen stellen
Simplicity FirstStrategy Pattern für eine einzelne RabattberechnungEine Funktion, bis echte Komplexität gebraucht wird
Surgical ChangesQuotes umformatieren und Type Hints ergänzen, während ein Bug gefixt wirdNur die Zeilen ändern, die das gemeldete Problem beheben
Goal-Driven"I'll review and improve the code""Write test for bug X → make it pass → verify no regressions"

Zentrale Erkenntnis​

Die "überkomplizierten" Beispiele sind nicht offensichtlich falsch - sie folgen Design Patterns und Best Practices. Das Problem ist das Timing: Sie führen Komplexität ein, bevor sie gebraucht wird. Das:

  • macht Code schwerer verständlich
  • fĂĽhrt zu mehr Bugs
  • kostet mehr Zeit bei der Umsetzung
  • erschwert Tests

Die einfachen Versionen sind:

  • leichter zu verstehen
  • schneller umzusetzen
  • leichter zu testen
  • später immer noch refactorbar, wenn echte Komplexität entsteht

Guter Code ist Code, der das heutige Problem einfach löst - nicht Code, der das morgige Problem vorschnell antizipiert.