Zum Inhalt springen

Prompt Engineering für die Entwicklerproduktivität

Praktische Prompt-Engineering-Techniken, die LLMs zu zuverlässigen Coding-Assistenten machen: Kontext, Chain-of-Thought und strukturierte Ausgaben.

5 Min. Lesezeit
Terminal, das einen gut strukturierten Prompt zeigt, der präzisen Code erzeugt

Große Sprachmodelle (LLMs) sind bei Programmieraufgaben nützlich, aber die Qualität ihrer Ausgaben schwankt stark, je nachdem, wie man sie fragt. Vage Prompts liefern vage Antworten. Präzise Prompts mit Kontext, Einschränkungen und Beispielen liefern Code, den man tatsächlich verwenden kann. Der Unterschied liegt nicht im Modell, sondern im Prompt.

Dabei geht es nicht um Tricks oder Jailbreaks, sondern darum, Anfragen so zu strukturieren, dass das Modell genügend Informationen hat, um schon beim ersten Versuch brauchbare Antworten zu liefern.

Die Anatomie eines guten Prompts

Jeder wirksame Coding-Prompt besteht aus vier Bestandteilen: Kontext, Aufgabe, Einschränkungen und Format.

markdownmarkdown
# ❌ Vague prompt — produces generic, possibly wrong code
"Write a function to validate emails"
 
# ✅ Structured prompt — produces usable, specific code
Context: TypeScript project using Zod for validation.
The function is part of a user registration form handler.
 
Task: Write a function that validates an email address.
 
Constraints:
- Must use Zod schema validation
- Must check for disposable email domains (mailinator, tempmail, guerrillamail)
- Must return a typed result object, not throw errors
- Must handle null/undefined input
 
Format: TypeScript function with JSDoc, followed by unit test examples.

Die strukturierte Version sagt dem Modell genau, welchen Stack es verwenden soll, welche Grenzfälle zu behandeln sind und wie die Ausgabe aussehen soll. Die vage Version zwingt das Modell dazu, bei jeder Entscheidung zu raten.

Verwaltung des Kontextfensters

Modelle haben einen begrenzten Kontext. Wenn man die gesamte Codebasis in den Prompt kippt, verschwendet man Tokens für irrelevanten Code. Gib stattdessen gezielten Kontext an — die konkreten Typen, Schnittstellen und Muster, in die sich der generierte Code einfügen muss.

tstypescript
// Include only the interfaces the generated code needs to implement
// Instead of pasting 500 lines of code, paste 30 lines of types
 
/*
Given these existing types and the database schema:
 
interface User {
  id: string;
  email: string;
  role: 'admin' | 'user' | 'moderator';
  createdAt: Date;
}
 
interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  pageSize: number;
  hasMore: boolean;
}
 
Database: PostgreSQL with Prisma ORM
Table: users (id UUID PK, email VARCHAR UNIQUE, role VARCHAR, created_at TIMESTAMP)
 
Write a function that queries users with pagination, filtering by role,
and returns a PaginatedResponse<User>.
*/
tstypescript
// The model can now produce code that matches your exact types
async function getUsers(
  params: { role?: User['role']; page: number; pageSize: number }
): Promise<PaginatedResponse<User>> {
  const { role, page, pageSize } = params;
  const skip = (page - 1) * pageSize;
 
  const where = role ? { role } : {};
 
  const [data, total] = await Promise.all([
    prisma.user.findMany({ where, skip, take: pageSize, orderBy: { createdAt: 'desc' } }),
    prisma.user.count({ where }),
  ]);
 
  return {
    data,
    total,
    page,
    pageSize,
    hasMore: skip + data.length < total,
  };
}

Chain-of-Thought für komplexe Logik

Bitte das Modell bei mehrstufigen Algorithmen oder Architekturentscheidungen, das Problem zu durchdenken, bevor es Code schreibt.

markdownmarkdown
# ❌ Direct request — model jumps to code, may miss edge cases
"Implement a rate limiter using the sliding window algorithm"
 
# ✅ Chain-of-thought prompt
Task: Implement a sliding window rate limiter.
 
Before writing code, explain:
1. How the sliding window algorithm differs from fixed window
2. What data structure you'll use to track request timestamps
3. How you'll handle the window boundary calculation
4. What happens when the limit is exceeded
 
Then implement in TypeScript with:
- Redis for storage (using ioredis)
- Configurable window size and request limit
- Return type that includes remaining requests and reset time
tstypescript
// The model's reasoning leads to more robust implementations
// because it explicitly considers edge cases before coding
 
interface RateLimitResult {
  allowed: boolean;
  remaining: number;
  resetAt: Date;
  retryAfter?: number;  // seconds until next allowed request
}
 
async function checkRateLimit(
  redis: Redis,
  key: string,
  limit: number,
  windowMs: number
): Promise<RateLimitResult> {
  const now = Date.now();
  const windowStart = now - windowMs;
 
  // Remove entries outside the current window
  // Count entries within the window
  // Add current request if under limit
  const pipeline = redis.pipeline();
  pipeline.zremrangebyscore(key, 0, windowStart);
  pipeline.zcard(key);
  pipeline.zadd(key, now.toString(), `${now}-${Math.random()}`);
  pipeline.pexpire(key, windowMs);
 
  const results = await pipeline.exec();
  const currentCount = results?.[1]?.[1] as number;
 
  if (currentCount >= limit) {
    // Over limit — remove the entry we just added
    await redis.zremrangebyscore(key, now, now);
    const oldestInWindow = await redis.zrange(key, 0, 0, 'WITHSCORES');
    const resetAt = oldestInWindow.length > 1
      ? parseInt(oldestInWindow[1]) + windowMs
      : now + windowMs;
 
    return {
      allowed: false,
      remaining: 0,
      resetAt: new Date(resetAt),
      retryAfter: Math.ceil((resetAt - now) / 1000),
    };
  }
 
  return {
    allowed: true,
    remaining: limit - currentCount - 1,
    resetAt: new Date(now + windowMs),
  };
}

Few-Shot-Beispiele

Wenn das Modell einem bestimmten Muster folgen soll, gib Beispiele für die Zuordnung von Eingabe zu Ausgabe an. Zwei oder drei Beispiele etablieren das Muster zuverlässiger als lange Beschreibungen.

markdownmarkdown
Task: Generate TypeScript API route handlers following this project's pattern.
 
Example 1:
Input: GET /api/users — list all users with pagination
Output:
tstypescript
// app/api/users/route.ts
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const page = parseInt(searchParams.get('page') || '1');
  const limit = parseInt(searchParams.get('limit') || '20');
 
  const users = await db.user.findMany({
    skip: (page - 1) * limit,
    take: limit,
  });
 
  return NextResponse.json({ data: users, page, limit });
}
markdownmarkdown
Example 2:
Input: POST /api/users — create a new user
Output:
tstypescript
// app/api/users/route.ts
export async function POST(request: NextRequest) {
  const body = await request.json();
  const parsed = createUserSchema.safeParse(body);
 
  if (!parsed.success) {
    return NextResponse.json(
      { error: parsed.error.flatten() },
      { status: 400 }
    );
  }
 
  const user = await db.user.create({ data: parsed.data });
  return NextResponse.json({ data: user }, { status: 201 });
}
markdownmarkdown
Now generate:
Input: DELETE /api/users/[id] — delete a user by ID

Das Modell versteht jetzt die Konventionen deines Projekts: Next.js-Route-Handler, Zod-Validierung, das Prisma-ORM, ein einheitliches Antwortformat. Es folgt dem Muster, ohne dass du jede Entscheidung einzeln vorgeben musst.

Strukturierte Ausgabeanfragen

Wenn das Modell Daten in einem bestimmten Format ausgeben soll — nicht nur Code —, sei bei der Struktur explizit.

markdownmarkdown
# ❌ Ambiguous output format
"What are the security issues in this code?"
 
# ✅ Structured output format
Analyze this code for security vulnerabilities.
 
Output format (JSON):
{
  "vulnerabilities": [
    {
      "severity": "critical" | "high" | "medium" | "low",
      "line": number,
      "issue": "brief description",
      "fix": "code snippet showing the fix",
      "cwe": "CWE-XXX reference"
    }
  ],
  "summary": "one paragraph overall assessment"
}
jsonjson
// Model output follows the exact schema
{
  "vulnerabilities": [
    {
      "severity": "critical",
      "line": 15,
      "issue": "SQL query built with string concatenation allows injection",
      "fix": "db.query('SELECT * FROM users WHERE id = $1', [userId])",
      "cwe": "CWE-89"
    },
    {
      "severity": "high",
      "line": 28,
      "issue": "User input rendered without sanitization enables XSS",
      "fix": "import DOMPurify from 'dompurify'; element.innerHTML = DOMPurify.sanitize(input);",
      "cwe": "CWE-79"
    }
  ],
  "summary": "Two vulnerabilities found: a critical SQL injection and a high-severity XSS. Both stem from unsanitized user input being used directly in queries and DOM manipulation."
}

Iterative Verfeinerung

Einmalige Prompts liefern selten produktionsreifen Code. Behandle das Modell wie einen Pair-Programming-Partner — gib Feedback, fordere Änderungen an und nähere dich schrittweise der richtigen Lösung.

markdownmarkdown
# Round 1: Get the basic structure
"Implement a retry mechanism with exponential backoff for HTTP requests"
 
# Round 2: Add constraints based on the output
"Good. Now modify it to:
- Use a jitter factor to prevent thundering herd
- Accept a custom shouldRetry predicate
- Log each retry attempt with the attempt number and delay
- Respect a maximum total timeout, not just max retries"
 
# Round 3: Integration
"Now wrap this in a class that can be used as middleware
for our existing HttpClient. Show usage examples."

Jede Runde baut auf der vorherigen Ausgabe auf. Das Modell hat den vollständigen Gesprächskontext und verfeinert die Lösung deshalb, statt von vorne zu beginnen. Das ist schneller, als zu versuchen, alles auf Anhieb perfekt zu spezifizieren.

Prompt-Vorlagen für wiederkehrende Aufgaben

Speichere für Aufgaben, die du wiederholt ausführst, Prompt-Vorlagen, die die Konventionen deines Projekts enthalten.

markdownmarkdown
## Bug Fix Template
 
Context: [paste the error message and stack trace]
 
Relevant code: [paste the function/file where the error occurs]
 
Expected behavior: [what should happen]
Actual behavior: [what actually happens]
 
Constraints:
- Fix must not change the public API
- Must include a test case that would have caught this bug
- Explain why the bug occurred (root cause)
 
## Code Review Template
 
Review this code for:
1. Logic errors or edge cases
2. Performance issues (N+1 queries, unnecessary re-renders)
3. Security vulnerabilities
4. Deviation from project conventions
 
For each issue found, provide:
- Line number
- Issue description
- Suggested fix with code

Diese Vorlagen verwandeln LLMs von unberechenbaren Chat-Tools in verlässliche Entwicklungsassistenten. Die Konsistenz entsteht durch die Struktur des Prompts, nicht durch das Training des Modells.

Die wichtigsten Erkenntnisse

  1. Strukturiere Prompts mit Kontext, Aufgabe, Einschränkungen und Format — vage Prompts erzeugen vagen Code
  2. Gib gezielten Kontext an, keine ganzen Dateien — Typen und Schnittstellen geben dem Modell das, was es braucht, um sich in deine Codebasis einzufügen
  3. Nutze Chain-of-Thought für komplexe Logik — Nachdenken vor dem Programmieren deckt Grenzfälle auf
  4. Few-Shot-Beispiele etablieren Muster — zwei Beispiele sind mehr wert als ein Absatz Beschreibung
  5. Arbeite in iterativen Runden — behandle das Modell wie einen Pair-Programming-Partner, nicht wie ein Orakel
  6. Speichere Prompt-Vorlagen — wiederkehrende Aufgaben liefern dank wiederverwendbarer Strukturen konsistente Ergebnisse
Wilfredo Rujel

Wilfredo Rujel

Full-Stack-Softwareentwickler

Diesen Beitrag teilenX