Prompts voor code review: 16 kant-en-klare checks

Zestien fragmenten met elk een echte fout, plus de vraag die een reviewer erbij zou stellen. Elke kaart zet taal, focus en omgeving alvast goed voor een code review — vervang het voorbeeld door je eigen code en klik op Toepassen.

Bugs en logica

Code die compileert en toch liegt: een bug in code opsporen, zien waarom een mutable default-argument in Python misgaat, tijden zonder zone vergelijken en een regressie in een pull request herkennen.

Mutable default-argument

De klassieke Python-val: de lijst in de signatuur overleeft de aanroep en verzamelt gegevens van anderen.

Prompt

Bekijk deze winkelwagenfunctie: klanten vinden in hun bestelling artikelen die ze nooit hebben toegevoegd.def add_item(item, cart=[]): cart.append(item) return cart

Resultaat

Regel 1 · kritiek · bugcart=[] wordt één keer aangemaakt, bij het importeren van de module, en niet bij elke aanroep: de tweede klant vult de wagen van de eerste verder, en zo door tot het proces herstart.Fix: cart: list | None = None en als eerste regel van de body if cart is None: cart = [].

Een nul die voor niets doorgaat

De waarheidscheck verbergt een legitieme nul, en de klant met een leeg saldo krijgt een placeholder te zien.

Prompt

Kijk naar deze check: onderscheidt hij een ontbrekende waarde echt van een nul?def render_balance(user): balance = user.get("balance") if not balance: return "geen gegevens" return format_money(balance)

Resultaat

Regel 3 · ernstig · bugif not balance vangt None, 0 en 0.0 op precies dezelfde manier: wie tot de laatste cent heeft uitgegeven leest "geen gegevens" in plaats van een eerlijke nul.Fix: if balance is None — het ontbreken check je expliciet, en de nul gaat gewoon door de formatter zoals elk ander getal.

Tijd vergelijken zonder zone

datetime.now() geeft de lokale servertijd terwijl de geldigheid van het token in UTC is opgeslagen.

Prompt

Controleer de berekening van de tokengeldigheid. De dienst draait op servers in verschillende tijdzones.def is_expired(token): return datetime.now() > token.expires_at

Resultaat

Regel 2 · kritiek · bugdatetime.now() is naïeve lokale tijd en expires_at komt in UTC uit de database: op een server in Amsterdam leven tokens twee uur te lang, en tegen een aware waarde klapt de vergelijking meteen met een TypeError.Fix: datetime.now(timezone.utc) en één regel voor het hele project — opslaan doe je aware en in UTC.

Regressie in een pull request

Een review van de wijziging in plaats van het bestand: wat er met deze commit meegaat naar productie.

Prompt

Review deze diff uit een pull request: wat gaat er precies mee naar productie?@@ -12,8 +12,7 @@ def apply_discount(order, percent): if order.paid: raise ValueError("order already paid")- if percent > 90:- raise ValueError("discount too large") order.total = order.total * (100 - percent) / 100+ order.discount_percent = percent

Resultaat

Regels 15-16 · kritiek · bugDe verwijderde check was de enige grens op percent: bij 150 wordt het ordertotaal negatief, en de kassa maakt daar een terugbetaling van.Fix: de check terugzetten of naar de validatie van het verzoek verplaatsen, en percent=150 aan de tests toevoegen.

Beveiliging

De plekken waar invoer van buiten bij de database en het bestandssysteem komt: code controleren op kwetsbaarheden, SQL-injectie in een zoekfunctie voorkomen en een token uit een deploy-script halen.

SQL-injectie in het zoeken

Het adres uit het formulier wordt in de SQL geplakt: wie het formulier invult, bestuurt vanaf dat moment de database.

Prompt

Review deze gebruikerszoekfunctie. Het e-mailadres komt rechtstreeks uit een formulier op de site.def find_user(conn, email): query = "SELECT * FROM users WHERE email = '" + email + "'" return conn.execute(query).fetchone()

Resultaat

Regel 2 · kritiek · beveiligingemail wordt in de SQL-tekst geplakt: een waarde met een quote sluit de conditie af en alles daarna voert de database uit als jouw eigen query, DROP TABLE incluis.Fix: een parameter in plaats van plakwerk — conn.execute("SELECT id, email FROM users WHERE email = %s", [email]); meteen verdwijnt ook de SELECT * die nu de wachtwoordhash meeneemt.

Token in het script

Een productiesleutel staat in de repository en rolt onderweg ook nog het deploy-log in.

Prompt

Review dit deployscript: zit hier iets gevaarlijks op het gebied van beveiliging?#!/usr/bin/env bashAPI_TOKEN="sk_live_9f3c1ad84b22"echo "deploying with $API_TOKEN"curl -H "Authorization: Bearer $API_TOKEN" -X POST https://api.example.com/deploy

Resultaat

Regels 2-3 · kritiek · beveiligingEen productietoken staat in een bestand dat in git zit: iedereen die de repo ooit heeft gekloond heeft hem in zijn historie, en echo kopieert hem ook nog naar het deploy-log, dat een veel bredere groep leest.Fix: uit de omgeving lezen ($API_TOKEN zonder default), de echo weghalen en deze sleutel als gelekt beschouwen — dus roteren.

Buiten de uploadmap komen

De bestandsnaam komt ongewijzigd uit het verzoek, en twee puntjes met een slash leiden naar elk bestand op de server.

Prompt

Review dit downloadendpoint: de bestandsnaam komt uit de querystring.def download(request): name = request.args.get("file") path = os.path.join("/var/app/uploads", name) return send_file(path)

Resultaat

Regel 3 · kritiek · beveiligingos.path.join stapt zonder morren uit /var/app/uploads zodra de naam ../../etc/passwd is, en een absoluut pad gooit het eerste argument zelfs helemaal weg: elk bestand dat het proces mag lezen is te downloaden.Fix: os.path.basename(name), daarna de os.path.realpath van het resultaat vergelijken met de uploadmap en alleen serveren als het daarbinnen valt.

Prestaties

Trage code sneller maken op de plekken waar het altijd misgaat: queries in een lus, een query die de index misloopt herkennen en een groot logbestand regel voor regel inlezen in plaats van in één keer.

Queries in de lus

Het rapport gaat per gebruiker twee keer naar de database: duizend rijen worden tweeduizend queries.

Prompt

Kijk naar dit rapport: bij duizend gebruikers doet het er een minuut over. Waar gaat de tijd heen?def orders_report(user_ids): rows = [] for user_id in user_ids: user = db.query("SELECT name FROM users WHERE id = %s", user_id) orders = db.query("SELECT total FROM orders WHERE user_id = %s", user_id) rows.append((user.name, sum(o.total for o in orders))) return rows

Resultaat

Regels 3-5 · ernstig · prestatiesTwee queries per gebruiker: duizend gebruikers zijn tweeduizend round trips, en de tijd gaat het netwerk in, niet het rekenwerk.Fix: één query met een JOIN en GROUP BY users.id, of twee queries met IN en het samenvoegen in het geheugen.

Query die de index misloopt

Een functie op de kolom en een procentteken vooraan in LIKE zetten de indexen uit: de database leest de hele tabel.

Prompt

Review deze query: op een tabel van tien miljoen rijen duurt hij twintig seconden.SELECT *FROM ordersWHERE date_trunc('day', created_at) = '2026-09-01' AND lower(email) LIKE '%@example.com'ORDER BY created_at DESC

Resultaat

Regels 3-4 · ernstig · prestatiesdate_trunc op created_at maakt de index nutteloos, en een LIKE die met een procentteken begint kan er sowieso niets mee: wat overblijft is een seq scan over de hele tabel, en SELECT * sleept er ook nog kolommen bij die niemand leest.Fix: created_at tegen een bereik vergelijken (vanaf middernacht op 1 september en kleiner dan middernacht op de 2e), het domein apart indexeren of in een eigen kolom zetten, en alleen de velden selecteren die je gebruikt.

Het hele log in het geheugen

Het bestand wordt in één keer gelezen, waardoor de grootte van het log de grootte van het proces wordt.

Prompt

Review deze foutenteller voor een logbestand. De bestanden lopen op tot enkele gigabytes.def count_errors(path): lines = open(path).read().split("\n") return len([line for line in lines if "ERROR" in line])

Resultaat

Regel 2 · ernstig · prestatiesread() tilt het hele bestand het geheugen in, split verdubbelt dat en de lijst binnen len() houdt een derde kopie vast: bij een log van acht gigabyte is de OOM killer er eerder dan het antwoord. Het bestand wordt bovendien nooit gesloten.Fix: with open(path) as f en sum(1 for line in f if "ERROR" in line) — regel voor regel, met constant geheugen.

Leesbaarheid en standaarden

Code leesbaarder maken voor wie er na jou in kijkt: geneste if-statements opschonen, namen die tegen de stijlgids ingaan aanpakken en een btw-tarief dat op twee plekken in de code staat.

Vier geneste condities

De regel voor het versturen van de mail zit op het vijfde inspringniveau en is alleen in zijn geheel te lezen.

Prompt

Beoordeel de leesbaarheid van deze functie: je moet hem tot het eind lezen om de conditie te kennen.def notify(user): if user is not None: if user.email is not None: if user.subscribed: if not user.banned: send_email(user.email) return True return False

Resultaat

Regels 2-5 · klein · leesbaarheidVier geneste ifs zijn één regel, uitgevouwen als een trap: om te weten wie de mail krijgt moet je alle vier de condities tegelijk vasthouden, terwijl PEP 8 om de platte vorm vraagt.Fix: vroege returns — if user is None: return False enzovoort — dan blijft de body op één inspringniveau.

Namen tegen de stijlgids in

Een methode met een hoofdletter en variabelen van één letter: RuboCop klaagt, en de volgende lezer ook.

Prompt

Review deze methode tegen RuboCop: wat gaat hier in tegen de gebruikelijke Ruby-stijl?def CalcTotal(o) t = 0 o.each do |i| t = t + i.price * i.qty end tend

Resultaat

Regels 1-4 · klein · standaardenNaming/MethodName: methodenamen zijn in Ruby snake_case, en CamelCase leest hier als een constante. De namen o, t en i zeggen niets over hun inhoud, en handmatig optellen is precies het werk van sum.Fix: def calc_total(items) en items.sum do |item| item.price * item.qty end — vier regels worden er één.

Btw-tarief op twee plekken

Eén regel in twee functies gekopieerd en al uit elkaar gelopen: 20 procent op de factuur, 19 op de bon.

Prompt

Review deze twee functies: rekenen ze hetzelfde uit?def invoice_total(order): return round(order.subtotal * 1.2, 2)def receipt_total(order): return round(order.subtotal * 1.19, 2)

Resultaat

Regels 2 en 5 · ernstig · architectuurEén bedrijfsregel staat twee keer in de code en is al uit elkaar gelopen: de factuur rekent 20 procent, de bon 19, en de klant ziet twee verschillende totalen voor één bestelling.Fix: één constante VAT_RATE en één functie die ze allebei aanroepen, dan verandert het tarief op precies één plek.

Tests en betrouwbaarheid

Wat er gebeurt als iets misgaat: tests beoordelen in een review als ze alleen het gelukkige pad dekken, een ingeslikte exception opsporen en een back-up controleren die nooit is teruggezet.

Test alleen voor het gelukkige pad

Eén groene case wekt de indruk van dekking die er niet is.

Prompt

Beoordeel deze tests voor apply_discount: wat ontbreekt eraan?def test_apply_discount(): order = Order(total=100) apply_discount(order, 10) assert order.total == 90

Resultaat

Regel 1 · ernstig · testsEén geval is gedekt: een gewone korting op een onbetaalde bestelling. Niets over nul, over honderd procent, over een negatieve waarde, over een al betaalde bestelling of over de afronding bij 33,33 — elk van die takken kan ongemerkt breken.Fix: parametrize op de grenswaarden en een aparte test met pytest.raises voor percent=150 en voor de al betaalde bestelling.

Ingeslikte exception

except Exception: pass maakt van een crash een "ok" en veegt het spoor uit de logs.

Prompt

Review dit opslaan van het profiel: gebruikers zeggen dat hun wijzigingen soms verdwijnen.def save_profile(user, data): try: db.update(user.id, data) search.reindex(user.id) except Exception: pass return "ok"

Resultaat

Regels 5-6 · kritiek · bugexcept Exception: pass slikt alles in en geeft "ok" terug, ook als er niets is weggeschreven: de gebruiker ziet succes, de gegevens zijn weg en het log is leeg. Twee operaties onder één try maken een mislukte herindexering bovendien ononderscheidbaar van een mislukte database.Fix: de specifieke excepties vangen, loggen met logger.exception en een eerlijke status teruggeven; haal de herindexering eruit, zodat haar falen het opslaan niet ongedaan maakt.

Back-up zonder controle

Het script kijkt niet naar exitcodes en verwijdert de oude kopieën voordat iemand merkt dat de nieuwe leeg is.

Prompt

Review dit nachtelijke back-upscript: herstellen vanaf de laatste kopie lukte niet.#!/usr/bin/env bashpg_dump "$DATABASE_URL" > /backup/db.sqlgzip -f /backup/db.sqlfind /backup -name "db.sql.gz" -mtime +7 -delete

Resultaat

Regels 2-4 · kritiek · bugEr staat geen set -euo pipefail: als pg_dump faalt wordt het bestand toch aangemaakt, leeg, gzip comprimeert het zonder morren en find gooit de werkende kopieën van ouder dan een week weg — na zeven dagen is er geen heel back-up meer over. Een niet-gezette $DATABASE_URL valt net zomin op.Fix: set -euo pipefail op de eerste regel, na pg_dump de grootte van de dump controleren en oude kopieën pas verwijderen als de nieuwe gelukt is.

Prompts voor code review: 16 kant-en-klare checks | iBro