Hoofdstuk 9
Als de buitenwereld niet meewerkt
Reproduceer voordat je repareert
Nog iets voordat je begint: de kopie van het catalogusproject in dit hoofdstuk bevat een fout die we er bewust in hebben gezet. Je hebt niets verkeerd gedaan in hoofdstuk 8 en het is geen vergissing in de beginbestanden. Debuggen moet je oefenen op iets dat niet werkt, dus dat krijg je hier.
Jouw taak is de fout te vinden zoals je een echte fout zou vinden. Dat betekent dat je de neiging moet weerstaan om meteen te gaan bewerken.
Het cataloguscommando meldt verkeerd gevormde invoer al zonder traceback. Dat is nuttig, maar niet elke fout hoort er hetzelfde uit te zien. Leg vier feiten vast voordat je code verandert: de werkmap, de exacte argumentenlijst, de invoerbytes en het verschijnsel dat je zag.
Begin in het Bash-paneel vanuit de projecthoofdmap en voer het commando uit:
pwd
python catalog.py catalog.csv selected.json --minimum-quantity 4
cat selected.json
Het commando schrijft één item weg. Volgens het contract uit hoofdstuk 8 behoudt de optie items waarvan het aantal minstens de drempelwaarde is. Een item met aantal 4 en drempelwaarde 4 had dus moeten blijven. Je verwachtte twee items.
Los het nog niet op. Wat je nu hebt, is waardevoller dan een oplossing: een reproduceerbaar geval om te debuggen. Je kent de map (/workspace), het exacte commando, het exacte invoerbestand en het exacte onjuiste resultaat. Zodra een daarvan verandert, is het een ander geval.
Dat laatste slaan mensen vaak over. Een commando opnieuw uitvoeren telt alleen als bewijs wanneer alles eromheen gelijk is gebleven. Een andere map, een andere drempelwaarde, een bewerkt CSV-bestand: je kijkt dan naar een nieuw probleem terwijl je denkt dat het nog het oude is.
Drie verantwoordelijken voor fouten
Voer een commando uit waarin één pad ontbreekt:
python catalog.py catalog.csv
Argparse drukt de gebruiksinstructies en een foutmelding af op stderr en sluit af met status 2. De conversie begint niet. Dit is een interfacefout: het commando voldoet niet aan de publieke grammatica.
Geef nu een invoerbestand op dat niet bestaat:
python catalog.py missing.csv selected.json
Het commando voldoet aan de grammatica, maar de buitenwereld kan het gevraagde bestand niet leveren. Dit is een verwachte externe fout. Het programma begrijpt de mislukte bewerking voldoende om een korte foutmelding en status 1 te geven.
Een programmeerfout is anders. Start de interactieve Python-prompt:
python
Vraag de selectiefunctie daarna om een item te verwerken dat de interne invariant van de catalogus schendt:
>>> import catalog
>>> catalog.select_items([{}], 4)
Dit levert een traceback op die eindigt op KeyError: 'quantity'. De fout zit niet bij de toegang tot het bestand. Projectcode ontving een structuur die volgens haar na validatie niet kan voorkomen. Daar “kon de catalogus niet lezen” van maken zou het bewijs verbergen dat je nodig hebt om het programma te repareren.
Verlaat de interactieve prompt voordat je teruggaat naar Bash:
>>> exit()
Lees de traceback van het resultaat terug naar de oorzaak
Begin bij de laatste regel. Die geeft het exceptietype en de melding: KeyError: 'quantity'. Ga daarna omhoog naar het binnenste stackframe in de projectcode. Dat frame noemt catalog.py, de regel binnen select_items en de expressie die de ontbrekende sleutel probeerde te lezen.
Eerdere frames leggen uit hoe de uitvoering daar terechtkwam. Ze geven context en zijn niet automatisch de plek die je moet bewerken. De eerste nuttige vraag is: welke binnenste bewerking in het project schond een aanname?
python catalog.py source.csv drukt de gebruiksinstructies van argparse af en sluit af met status 2. Bij welke verantwoordelijke zoek je eerst?
Een traceback eindigt op KeyError: 'quantity' en het binnenste projectframe wijst naar select_items. Wat onderzoek je eerst?
Hierna pauzeer je de geslaagde uitvoering met het onjuiste resultaat vóór de vergelijking. Je onderzoekt de waarden die bepalen of het item op de grens behouden blijft.