Prompts om code uit te leggen: van regex tot stacktrace
Zestien kant-en-klare vragen voor wie code wil laten uitleggen. In elke vraag zit al een stuk code, een patroon, een commando of een stacktrace, en er staat bij voor wie de uitleg is en hoe diep die moet gaan. Zet er je eigen code in, klik op «Toepassen» en lees.
Code van iemand anders doorgronden
Code van iemand anders begrijpen als de auteur allang vertrokken is: een functie zonder commentaar regel voor regel laten uitleggen, code lezen in een taal die je zelf niet schrijft en horen waarom het zo gebouwd is.
Een functie zonder commentaar
Zet geërfde code om in één zin over wat er werkelijk wordt uitgerekend.
def recalc(items, rate): total = 0 for it in items: if it.status != "cancelled": total += it.price * it.qty if total > 10000: total = total * (1 - rate) return round(total, 2)# overgenomen van de vorige ontwikkelaar, geen enkele opmerking — leg uit wat deze functie uitrekent en onder welke voorwaarden
De functie rekent het orderbedrag inclusief korting uit.1. Ze vermenigvuldigt prijs met aantal over alle regels en slaat geannuleerde regels over: een regel met status "cancelled" komt nooit in het totaal terecht.2. Zodra het totaal boven 10000 uitkomt, wordt rate erop toegepast: 0.1 betekent tien procent eraf.3. Het resultaat wordt op twee decimalen afgerond, dus op centen.Het detail dat alleen de code prijsgeeft: de korting geldt over het hele bedrag en niet over het deel boven de drempel. Een order van 10001 is na korting dus goedkoper dan een order van 9999.
Code in een taal die je niet schrijft
Legt een onbekende constructie uit via de constructie die je in je eigen taal al gebruikt.
import functoolsdef retry(times): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): for attempt in range(times): try: return fn(*args, **kwargs) except TimeoutError: continue raise RuntimeError("all attempts failed") return wrapper return decorator# ik schrijf PHP en zie een decorator voor het eerst — leg het uit met begrippen die ik al ken
Dit is een decorator met een parameter. Wat je in PHP al kent en er het dichtst bij komt, is een functie die een closure teruggeeft die om een andere functie heen zit.Je leest het van binnen naar buiten:- retry(3) geeft decorator terug;- decorator krijgt jouw functie en geeft wrapper terug;- de regel @retry(3) boven een functie betekent precies fn = retry(3)(fn) — vanaf dat moment wijst de naam naar wrapper.wrapper roept het origineel tot drie keer aan en slikt TimeoutError zonder iets te zeggen; zijn de pogingen op, dan gooit hij RuntimeError. functools.wraps staat er zodat de omhulling de naam en de docstring van het origineel behoudt: zonder dat zouden de debugger en elke trace overal alleen nog wrapper laten zien.
Regel voor regel
Loopt het fragment regel voor regel door, voor als niet het geheel maar één bepaalde plek onduidelijk is.
def top_customers(orders, n): totals = dict() for order in orders: totals[order.customer_id] = totals.get(order.customer_id, 0) + order.total ranked = sorted(totals.items(), key=lambda pair: -pair[1]) return ranked[:n]# loop het regel voor regel na, vooral de regel met sorted en het minteken vóór pair
1. totals = dict() — een leeg woordenboek waarin de bedragen per klant worden verzameld.2. De lus loopt door de orders. totals.get(id, 0) geeft het opgetelde bedrag terug, of nul als een klant voor het eerst voorbijkomt: daarom is een aparte controle op de eerste order niet nodig.3. sorted over items() sorteert paren van klant en bedrag. Het minteken vóór pair[1] draait de volgorde om: aflopend in plaats van oplopend, zodat de grootste klanten bovenaan staan.4. ranked[:n] houdt alleen de eerste n paren over.Dat er paren en niet alleen identificatienummers teruggaan, is met opzet: de aanroeper heeft het bedrag nodig om het te tonen en hoeft het zo niet opnieuw op te tellen.
Waarom het zo geschreven is
Legt niet uit wat de code doet, maar waarom de auteur voor deze vorm koos.
_cache = None_lock = threading.Lock()def get_config(): global _cache if _cache is None: with _lock: if _cache is None: _cache = load_config_from_disk() return _cache# waarom staat de controle op None er twee keer, buiten de lock en erbinnen
Dit is het patroon double-checked locking, en beide controles staan er om een andere reden.De eerste is de snelle weg. Zodra de configuratie één keer geladen is, zien alle volgende aanroepen de klare waarde en keren ze meteen terug zonder de lock aan te raken. Een lock nemen kost bij elke aanroep tijd, terwijl hij echt maar één keer per procesleven nodig is.De tweede gaat over de race. Terwijl de eerste thread de lock vasthoudt en het bestand leest, is een tweede de buitenste controle al gepasseerd en staat erachter te wachten. Zonder de binnenste controle zou die de configuratie een tweede keer laden en het resultaat van de ander overschrijven.De regel global zorgt ervoor dat de toekenning de modulevariabele verandert in plaats van een nieuwe lokale aan te maken.
Fouten en stacktraces
De rode tekst die je eerst moet lezen en pas daarna kunt repareren: een foutmelding begrijpen, een stacktrace lezen na een crash, zien waarom een installatie vastloopt en een regel uit het productielog ontcijferen.
De applicatie crasht
Leest de stacktrace: waar het afbrak, welke regel de schuldige is en wat de fout zelf betekent.
Traceback (most recent call last): File "app/orders.py", line 42, in create_order total = calc_total(items) File "app/pricing.py", line 17, in calc_total return sum(item.price * item.qty for item in items)AttributeError: 'dict' object has no attribute 'price'wat is hier gebeurd en in welke regel moet ik de oorzaak zoeken
AttributeError betekent dat het object geen attribuut price heeft.Een trace lees je van boven naar beneden voor de aanroeproute en van beneden naar boven voor de oorzaak:- create_order, in orders.py regel 42, riep calc_total aan;- calc_total, in pricing.py regel 17, vroeg om item.price en liep vast.Regel 17 loopt vast, maar het probleem zit daar niet. De melding zegt dat er in items woordenboeken zitten en geen objecten, en bij een woordenboek werkt toegang met een punt niet. De aanroeper heeft de gegevens dus rechtstreeks uit de JSON doorgegeven zonder er een model van te maken. Repareer het waar items wordt samengesteld, of lees het veld als item["price"].
De installatie loopt vast
Vertaalt het installatielog uit de taal van de pakketbeheerder naar gewone taal.
ERROR: Could not find a version that satisfies the requirement pandas==2.9.1 (from versions: 2.0.0, 2.1.4, 2.2.2)ERROR: No matching distribution found for pandas==2.9.1ik krijg de afhankelijkheden niet geïnstalleerd, wat betekent deze melding
Die versie van de bibliotheek bestaat niet.pip is naar de pakketindex gegaan, heeft pandas gevonden en de beschikbare versies opgesomd: de nieuwste daarvan is 2.2.2. Jij vraagt om 2.9.1, die niemand publiceert, vandaar «no matching distribution».In de praktijk is het bijna altijd een van drie dingen: een typefout in het versienummer, een requirements.txt die uit een nieuwer project is meegekomen, of een versie die alleen voor een andere Python-versie bestaat. De tweede regel is geen apart probleem, maar de uitkomst van de eerste.
Een regel uit het productielog
Legt uit wat de server werkelijk meldt als er een verlopen wachttijd en drie getallen in het log staan.
2026-09-12T03:14:07Z ERROR db pool: timeout acquiring connection after 30s, active=20 idle=0 waiting=1372026-09-12T03:14:07Z ERROR api POST /orders 503 in 30012ms request_id=8c1f2awat probeert de server mij met deze twee regels te vertellen
De databaseverbindingen zijn op.Eerste regel: de pool heeft 30 seconden op een vrije verbinding gewacht en het opgegeven. active=20 betekent dat alle twintig bezet zijn, idle=0 dat er geen enkele vrij is, waiting=137 dat er honderdzevenendertig verzoeken in de rij staan.De tweede regel is het gevolg: POST /orders hing dezelfde 30 seconden en gaf 503 terug.Samen gelezen: er komen verzoeken binnen sneller dan de database verbindingen loslaat. De pool is niet stuk, hij meldt eerlijk dat hij tegen zijn plafond zit. Kijk als volgende naar de lange query’s die een verbinding vasthouden en naar de poolgrootte ten opzichte van het aantal workers.
Reguliere expressies en commando’s
Regels die werken zolang niemand ze aanraakt: een reguliere expressie laten uitleggen, een regex uit een configuratiebestand ontleden en een shell-commando uit andermans handleiding nalopen voor je het plakt.
Wat de reguliere expressie controleert
Haalt het patroon uit elkaar en zegt welke adressen erdoor komen en welke onterecht worden geweigerd.
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z][A-Za-z]+$dit staat in onze formuliercontrole — wat controleert het precies en wat laat het ten onrechte door
Het patroon controleert de vorm van een adres, niet of het bestaat.- ^ en $ eisen dat de hele tekenreeks past en niet een stukje erbinnen.- Het deel vóór het apenstaartje: letters, cijfers, punt, liggend streepje, procent, plus en koppelteken, minstens één teken.- @ — precies één apenstaartje.- Het domein: letters, cijfers, punten en koppeltekens.- Aan het eind een punt en een extensie van minstens twee letters.Wat er ten onrechte doorheen glipt: twee punten achter elkaar, een punt aan het begin van de naam, een domein dat niet bestaat. Wat zonder goede reden wordt geweigerd: geïnternationaliseerde adressen en extensies in niet-Latijnse alfabetten. Voor een formulier is dit meestal genoeg, want of het adres bestaat blijkt toch pas uit de bevestigingsmail.
Een expressie uit een configuratie
Laat zien welke delen van de logregel in welke groep terechtkomen, en in welke volgorde.
^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - - \[([^\]]+)\] "([A-Z]+) ([^ ]+) [^"]+" ([0-9]+) ([0-9]+)$hiermee wordt een regel uit access.log opgesplitst — wat komt er in welke groep
De expressie splitst een logregel van de webserver in zes groepen.1. Het IP-adres: vier getallen met punten ertussen. De punt met een schuine streep ervoor betekent echt een punt en niet «een willekeurig teken».2. Het tijdstip tussen blokhaken: daarbinnen is elk teken toegestaan behalve de sluithaak, en juist dat weerhoudt de groep ervan de rest van de regel op te slokken.3. De methode: hoofdletters.4. Het pad: alles tot de eerste spatie.5. De antwoordcode.6. De grootte van het antwoord in bytes.De volgorde van de groepen is de volgorde van de velden in het resultaat: groep één is het adres, groep vijf is de status. Een regel met een spatie in het pad past niet als geheel en wordt simpelweg overgeslagen.
Een commando uit andermans handleiding
Haalt de reeks uit de terminal uit elkaar voordat je op een productieserver op Enter drukt.
tar -czf - ./data | ssh backup@nas "cat > /backups/data-$(date +%F).tar.gz"# gevonden in onze back-uphandleiding — leg uit wat er gebeurt voordat ik dit uitvoer
Het commando zet een back-up van de map data op een andere machine, zonder hier iets op te slaan.- tar -czf - bouwt het archief en comprimeert het, maar schrijft het naar de standaarduitvoer in plaats van naar een bestand: daar staat het losse koppelteken voor.- De verticale streep geeft die stroom door aan het volgende commando.- ssh verbindt met de machine nas als gebruiker backup en start daar cat, dat de stroom in een bestand legt.- $(date +%F) zet de datum van vandaag in de bestandsnaam: er komt data-2026-09-13.tar.gz uit.Het archief landt nergens in zijn geheel: het stroomt al tijdens het inpakken over het netwerk, dus vrije schijfruimte op de bronmachine is niet nodig. Er is één scherpe rand: bestaat er al een bestand met die naam, dan wordt het zonder één woord overschreven.
SQL en databasequery’s
Een SELECT van een half scherm: een SQL-query laten uitleggen, begrijpen wat een vensterfunctie precies doet en uitzoeken waar een trage query in productie over struikelt ten opzichte van de testdatabase.
Een rapportagequery
Zet een lange SELECT om in één zin over welke regels in het rapport terechtkomen.
SELECT c.name, date_trunc('month', o.created_at) AS month, count(*) AS orders, sum(o.total) AS revenueFROM orders oJOIN customers c ON c.id = o.customer_idWHERE o.created_at >= now() - interval '1 year'GROUP BY 1, 2HAVING sum(o.total) > 1000ORDER BY revenue DESC-- leg uit wat deze query teruggeeft en waarom een deel van de klanten nooit in het rapport staat
De query rekent de omzet per klant uit, uitgesplitst per maand, over het afgelopen jaar.- JOIN koppelt de order aan de klant. Het is een gewone join en geen LEFT, dus klanten zonder ook maar één order komen er helemaal niet in voor.- WHERE snijdt alles weg wat ouder is dan een jaar.- date_trunc kort de datum in tot de eerste van de maand: zo vallen losse orders in maandgroepen uiteen.- GROUP BY 1, 2 groepeert op de eerste twee uitdrukkingen uit de lijst: klantnaam en maand.- HAVING gooit groepen onder 1000 weg. Anders dan WHERE grijpt dit filter pas in nadat de som is berekend.Eén regel van het resultaat is één klant in één maand, niet een klant als geheel.
Vensterfunctie
Legt uit wat OVER berekent en waarin een venster verschilt van een gewone groepering.
SELECT customer_id, created_at, total, sum(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total, row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnFROM orders-- leg uit wat deze twee OVER berekenen en waarom de regels niet worden samengevouwen
Een vensterfunctie rekent over een groep zonder de regels samen te vouwen: er komen er evenveel uit als erin gingen, alleen krijgt elke regel er twee kolommen bij.- PARTITION BY customer_id verdeelt de tabel in stapels per klant. Zo’n stapel is het venster.- In de eerste kolom loopt de som op: ORDER BY created_at bepaalt de volgorde binnen het venster, en standaard tellen de regels vanaf het begin van het venster tot en met de huidige mee. Bij de derde order van een klant is dat de som van de eerste drie.- Het tweede OVER sorteert anders, aflopend op bedrag, en row_number deelt de plaatsen uit: de 1 gaat naar de grootste order van die klant.Juist dat onderscheidt een venster van GROUP BY: groeperen zou één regel per klant hebben overgehouden en de losse orders hebben weggegooid.
Waar de query over struikelt
Legt de query uit en wijst meteen aan waar hij bij echte gegevens tegenaan loopt.
SELECT *FROM orders oLEFT JOIN order_items i ON i.order_id = o.idWHERE DATE(o.created_at) = '2026-09-01' AND o.status != 'cancelled'ORDER BY o.created_at DESC-- op de testdatabase meteen klaar, in productie blijft hij hangen — leg de query uit en waar hij hier tegenaan loopt
De query haalt de orders van één september op, samen met al hun regels. LEFT JOIN betekent dat een order zonder regels er ook uit komt, met lege kolommen uit order_items.Waar hij bij echte gegevens over struikelt:- DATE(o.created_at) is een functie over de kolom heen, en daardoor is de index op created_at niet meer bruikbaar: de database moet voor elke rij van de tabel een datum uitrekenen. Bij honderd testrijen valt dat niet op, bij miljoenen is het een volledige scan.- SELECT * over een een-op-veelkoppeling geeft elke order net zo vaak terug als hij regels heeft. Er komen flink meer rijen uit dan er orders zijn, en de applicatie moet ze zelf weer samenvouwen.- Beide tabellen hebben kolommen met dezelfde naam (id, created_at); in het resultaat staan ze door elkaar en aan de naam zie je niet meer uit welke tabel een veld komt.
Uitleggen aan een beginner en in een sollicitatiegesprek
Wanneer de uitleg zwaarder weegt dan de code: programmeren uitleggen aan een beginner, een opgave uit een sollicitatiegesprek stap voor stap doornemen en een script toelichten zonder vaktaal.
Het eerste programma
Uitleg voor wie twee weken code schrijft: wat elke regel doet en waarom hij er zo staat.
nums = [4, 8, 15, 16, 23, 42]total = 0for n in nums: if n % 2 == 0: total += nprint(total / len(nums))# ik ben net met Python begonnen — leg stap voor stap uit wat hier gebeurt
Het programma telt de even getallen uit de lijst bij elkaar op en deelt het resultaat door het aantal van ALLE getallen.1. nums is een lijst van zes getallen.2. total = 0 is de doos waarin we gaan optellen. De nul staat er om een beginpunt te hebben.3. for n in nums is de lus: n wordt om de beurt elk getal uit de lijst.4. n % 2 == 0 betekent dat de rest bij deling door twee nul is, oftewel dat het getal even is.5. total += n telt het gevondene op bij wat er al stond.6. Delen door len(nums) is delen door de lengte van de lijst, dus door zes.Dit ziet eruit als een denkfout: in de lijst staan vier even getallen die samen 70 zijn, maar er wordt door zes gedeeld. Wil je het gemiddelde van de even getallen, dan moet je door het aantal even getallen delen.
Een opgave uit een sollicitatiegesprek
Het algoritme zo uit elkaar gehaald dat je het hardop bij een whiteboard kunt uitleggen.
def search(nums, target): lo, hi = 0, len(nums) - 1 while hi >= lo: mid = (lo + hi) // 2 if nums[mid] == target: return mid if nums[mid] > target: hi = mid - 1 else: lo = mid + 1 return -1# kreeg dit in een sollicitatiegesprek — leg het idee achter het algoritme uit en waarom het snel is
Dit is binair zoeken in een gesorteerde array. Er zit één idee in: bij elke stap valt de helft van wat over is af.- lo en hi zijn de grenzen van het stuk waarin het antwoord nog kan liggen.- mid is het midden. De dubbele schuine streep is gehele deling, dus je krijgt een index en geen breuk.- Staat het gezochte in het midden, dan ben je klaar.- Is het midden groter, dan ligt het antwoord links en schuift hi op.- Anders ligt het rechts en schuift lo op.- De lus stopt als de grenzen elkaar passeren: de waarde staat er niet in en er komt -1 terug.De kosten zijn logaritmisch: een miljoen elementen betekent een stuk of twintig stappen in plaats van een miljoen. Harde voorwaarde is dat de array al gesorteerd is, anders geeft het algoritme zwijgend een verkeerd antwoord.De gebruikelijke vervolgvraag in een gesprek: in talen met een vaste getalbreedte kan lo + hi overlopen, en daarom schrijft men daar lo + (hi - lo) // 2.
Het aan je leidinggevende uitleggen
Het script naverteld zonder één vakterm, voor een gesprek met iemand die geen code leest.
import csv, smtplibrows = list(csv.DictReader(open("orders.csv")))late = [r for r in rows if r["shipped_at"] == ""]body = "Nog niet verzonden bestellingen: " + str(len(late))smtplib.SMTP("mail.local").sendmail("bot@shop.local", "ops@shop.local", body)# ik moet aan een niet-technische leidinggevende uitleggen wat dit script doet
Het script kijkt welke bestellingen nog niet verzonden zijn en meldt dat aantal per e-mail.Stap voor stap, zonder vaktermen:1. Het opent de bestellingentabel: een gewoon bestand dat Excel ook zou openen.2. Het houdt de regels over waarin de verzenddatum leeg is: wel besteld, niet vertrokken.3. Het telt hoeveel van zulke regels er zijn.4. Het stuurt een e-mail naar het adres van de afdeling met dat ene getal erin.Wat een leidinggevende moet weten: het script verandert niets en zit niemand in de weg, het leest alleen en meldt — je kunt het desnoods elk uur laten draaien. Zijn enige zwakke plek: het leest een bestand en laat dus het beeld van de laatste export zien, niet dat van dit moment.