Het gebruik van Large Language Models (LLM's) voor softwareontwikkeling is in rap tempo geëvolueerd van een experimentele gimmick naar een onmisbaar onderdeel van de workflow van een developer. Modellen zoals GPT-4, Claude 3.5 Sonnet en Gemini 1.5 Pro zijn in staat om complexe architecturen uit te schrijven, hardnekkige bugs te vinden en unit tests te genereren. Echter, de kwaliteit van de gegenereerde code is volledig afhankelijk van de kwaliteit van je prompt. Een LLM is geen gedachtenlezer; het is een krachtige, maar uiterst letterlijke assistent.
In dit artikel duiken we in de techniek van het schrijven van hoogwaardige prompts specifiek gericht op codegeneratie. We behandelen het meegeven van de juiste context, het afdwingen van harde eisen omtrent taal en stijl, en de kunst van het itereren. Of je nu een beginner bent of een ervaren prompt engineer, deze richtlijnen tillen je codegeneratie naar een professioneel niveau.
Waarom codegeneratie een andere aanpak vereist
Wanneer je een LLM vraagt om een blogpost te schrijven, is er veel ruimte voor interpretatie en creativiteit. Bij het schrijven van code is die ruimte er niet. Code werkt, of het werkt niet. Bovendien bestaat code zelden in een vacuüm. Een enkele functie moet passen binnen een bredere architectuur, communiceren met specifieke databases, voldoen aan interne security-richtlijnen en geschreven zijn in een specifieke versie van een framework.
Een veelgemaakte fout is om de LLM te behandelen als een zoekmachine. Developers schrijven vaak: hoe maak ik een login formulier in React?. Het resultaat is dan een generieke tutorial met verouderde class components en ongevalideerde invoervelden. Om bruikbare, productieklare code te krijgen, moet je overschakelen naar een declaratieve, contextrijke manier van prompten.
De fundamenten van een goede code-prompt
Een succesvolle prompt voor codegeneratie rust op drie pijlers: context, expliciete eisen en een afgebakende taak. Laten we deze pijlers één voor één ontleden.
1. Context is koning: Bestanden en omgeving
Een LLM kent jouw codebase niet. Het weet niet of je een monolithische applicatie bouwt of microservices gebruikt. Goed context management is cruciaal. Als je wil dat de LLM een bestaande functie aanpast of een nieuwe functie schrijft die integreert met bestaande code, moet je die bestaande code delen.
Gebruik hiervoor duidelijke scheidingstekens, zoals Markdown codeblokken of XML-tags. Dit helpt het model om instructies te scheiden van de referentiecode. Bijvoorbeeld:
Hier is de huidige implementatie van de user controller in `controllers/userController.js`:
<file name="userController.js">
[Plak hier je code]
</file>
Schrijf op basis van bovenstaande code een nieuwe middleware functie die...
2. Versiebeheer van frameworks en libraries
De kennis van een LLM heeft een zogenaamde knowledge cutoff, maar is ook getraind op miljarden regels legacy code. Als je niet specificeert welke versie je gebruikt, is de kans groot dat het model verouderde syntax gebruikt. Vermeld altijd expliciet de versies van je belangrijkste afhankelijkheden.
- Slecht: "Schrijf een React component voor een navigatiebalk."
- Goed: "Schrijf een React 18 component voor een navigatiebalk. Gebruik functionele componenten, hooks (useState, useEffect) en Tailwind CSS v3 voor de styling."
3. Foutmeldingen effectief delen (Debugging)
Wanneer je een LLM gebruikt voor debugging, is "het werkt niet" de slechtst mogelijke prompt. Modellen blinken uit in patroonherkenning. Hoe meer patronen je ze voert, hoe beter het antwoord. Deel daarom altijd de volledige stack trace, de relevante code én de verwachte uitkomst.
Eisen expliciet maken voor bruikbare code
Naast context, moet je de kaders waarbinnen de LLM mag opereren strak dicteren. Als je deze eisen openlaat, maakt het model aannames, wat vrijwel altijd leidt tot extra werk (refactoren) achteraf.
Programmeertaal en stijlrichtlijnen
Specificeer niet alleen de taal, maar ook het paradigma (bijv. object-georiënteerd of functioneel) en eventuele type-systemen. Als je TypeScript gebruikt, dwing dan strict mode af in je prompt. Vraag om specifieke naamgevingsconventies (camelCase, snake_case) en documentatiestandaarden (JSDoc, PEP 257 voor Python).
Tests, documentatie en edge-cases
Professionele code is geteste code. Een uitstekende techniek is om de LLM te vragen om Test-Driven Development (TDD) toe te passen, of om in ieder geval direct unit tests te genereren. Je kunt ook gestructureerde uitvoer eisen, wat handig is als je de LLM via een API aanroept in een geautomatiseerde pipeline. Meer hierover lees je in onze gids over output formaten afdwingen.
Let op: Aannames over bedrijfslogica
Een LLM kent je bedrijfsregels niet. Geef expliciet aan hoe edge-cases afgehandeld moeten worden. Bijvoorbeeld: "Als de gebruiker jonger is dan 18 jaar, moet de functie een specifieke UnderageException opwerpen met de HTTP status code 403. Val niet terug op generieke errors."
Concrete voor- en na-voorbeelden
Om de theorie in de praktijk te brengen, bekijken we drie veelvoorkomende scenario's. We vergelijken de 'luie' prompt met de professionele, uitgebreide prompt.
Voorbeeld 1: Een nieuwe functie schrijven (Python)
De vage prompt (Niet doen):
Schrijf een Python script dat CSV bestanden leest en de data in een database zet.
Deze prompt zal resulteren in een chaotisch script dat waarschijnlijk sqlite3 of pandas gebruikt, zonder foutafhandeling, hardcoded inloggegevens bevat en crasht bij een lege CSV-regel.
De professionele prompt (Wel doen):
Je bent een senior Python developer. Schrijf een Python 3.11 script dat voldoet aan de volgende eisen:
- Doel: Lees een CSV-bestand (gescheiden door puntkomma's) en voer de rijen in een PostgreSQL-database in.
- Libraries: Gebruik `psycopg2` voor de databaseverbinding en de standaard `csv` module.
- Kwaliteitseisen: Gebruik type hints (PEP 484), voeg Google-style docstrings toe aan alle functies.
- Foutafhandeling: Vang `FileNotFoundError` op en log een waarschuwing via de `logging` module. Sla corrupte CSV-rijen (minder dan 5 kolommen) over en log deze.
- Beveiliging: Haal database-credentials uit environment variables (gebruik `os.environ`), gebruik nooit hardcoded wachtwoorden in de code. Voorkom SQL-injectie door parameterized queries te gebruiken.
- Output format: Geef uitsluitend de Python code terug, zonder Markdown of extra uitleg.
Waarom dit werkt: Je dwingt veiligheid (environment variables, geen injecties), robuustheid (foutafhandeling van corrupte rijen) en stijl (type hints, docstrings) af in de eerste generatie.
Voorbeeld 2: Debuggen van een React-component
Stel, je hebt een React-applicatie die crasht tijdens het renderen.
De vage prompt:
Mijn React knop werkt niet, hij geeft een error over 'undefined is not a function' als ik erop klik. Los dit op.
De professionele prompt:
Ik krijg een TypeError: `undefined is not a function` bij het klikken op een knop in mijn React 18 component.
Hier is de stack trace uit de console:
[Plak stack trace hier]
Hier is de relevante component code:
<code>
import React, { useState } from 'react';
export default function UserProfile({ onSave }) {
const [name, setName] = useState('');
const handleClick = () => {
onSave(name);
}
return (
<div>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={handleClick}>Opslaan</button>
</div>
);
}
</code>
Analyseer de code en de foutmelding. Leg eerst in één zin uit wat het probleem is en geef daarna de gecorrigeerde code. Neem in de oplossing ook een verdedigende check (defensive programming) op om dit in de toekomst te voorkomen.
In dit geval zal het model direct zien dat de onSave prop mogelijk niet wordt meegegeven door de parent-component, en zal het een oplossing suggereren zoals if (typeof onSave === 'function') onSave(name); of het toevoegen van PropTypes/TypeScript interfaces.
Voorbeeld 3: Refactoren en patronen toepassen
Soms wil je bestaande, lelijke code opschonen. Maak gebruik van few-shot prompting door het model voorbeelden te geven van hoe de refactor eruit moet zien.
Refactor de onderstaande geneste if/else logica naar een meer leesbaar 'early return' patroon en gebruik een switch-statement of een mapping object (dictionary) waar dat logischer is.
Code om te refactoren:
[Plak code]
Behoud exact dezelfde functionaliteit. Voeg inline commentaar toe als een complexe regel code vereenvoudigd is, om aan te geven wat de nieuwe logica doet.
Itereren op gegenereerde code (De Copilot-aanpak)
Zelfs met de perfecte prompt zal een LLM zelden in één keer een foutloos script van 1000 regels schrijven. Succesvolle developers gebruiken LLM's iteratief. Dit betekent dat je een complexe taak opbreekt in kleinere stappen.
- Stap 1 (Scaffolding): Vraag om de mappenstructuur of de interfaces/types. "Genereer de TypeScript interfaces voor een E-commerce winkelwagen."
- Stap 2 (Kernlogica): "Schrijf nu de functie om een item toe te voegen aan de winkelwagen, gebaseerd op de interfaces uit je vorige antwoord."
- Stap 3 (Validatie): "Controleer de zojuist geschreven functie op edge-cases, zoals het toevoegen van een negatief aantal producten. Pas de code aan."
Dit proces staat bekend als multi-turn prompting. Als je merkt dat het model halverwege het gesprek de draad kwijtraakt of gaat hallucineren (dingen verzint), begin dan een nieuwe sessie. Om te begrijpen waarom modellen soms de draad kwijtraken bij lange gesprekken, raden we aan om meer te lezen over de fundamentele basis van hoe LLM's werken en context windows.
Verdiep je ook in het effectief voeren van multi-turn gesprekken om te voorkomen dat je telkens alle context opnieuw moet invoeren.
Veelgemaakte fouten en valkuilen
Naast de al genoemde punten, zijn hier enkele valkuilen die je ten koste van alles moet vermijden bij het schrijven van prompts voor code:
- Te grote scope: "Schrijf een compleet ERP-systeem in Django." Het model zal falen door restricties in output-lengte en gebrek aan specificaties.
- Blind kopiëren en plakken: LLM's kunnen kwetsbaarheden introduceren. Vraag het model om uit te leggen wat een specifiek blok code doet als je het niet 100% begrijpt.
- Bedrijfsgeheimen delen: Plak nooit gevoelige API-sleutels, wachtwoorden, of propriëtaire, sterk beveiligde bedrijfsalgoritmen in commerciële LLM-interfaces (tenzij je een veilige enterprise-omgeving hebt). Vervang gevoelige data door dummy-variabelen (bijv.
<API_KEY_HERE>). - Security blindspots negeren: Voeg standaard een zin toe zoals "Houd rekening met de OWASP Top 10 veiligheidsrisico's tijdens de implementatie" wanneer je web-gerelateerde code genereert.
Voor een uitgebreidere lijst met blunders, bekijk ons overzicht van veelgemaakte prompt-fouten.
Conclusie
Het schrijven van prompts voor codegeneratie is een vaardigheid die oefening vereist. Het dwingt je om vooraf beter na te denken over de architectuur, de vereisten en de randvoorwaarden van de software die je bouwt. Door expliciete context te bieden, harde eisen te stellen aan stijl en kwaliteit, en iteratief te werken, transformeer je een LLM van een onvoorspelbare chatbot naar een betrouwbare, onvermoeibare pair-programmer.
Neem de tijd om je eigen "prompt-templates" op te bouwen voor taken die je vaak uitvoert, zoals het schrijven van unit tests of het opzetten van boilerplate-code. Zo bespaar je in de toekomst aanzienlijk veel tijd.
