De snoepautomaat van de kantine
Een schoolkantine heeft een snoepautomaat met een klein Python-programma. De voorraad staat in een dictionary; elk product kost 1,50 euro. De eigenaar test de automaat met de vijf aanroepen hieronder en ziet daarna vreemde dingen in de administratie: er verdwijnt voorraad die nooit is afgerekend en het programma stopt met een foutmelding zodra iemand een verkeerde code intypt. De automaat heeft geen toetsenbord met letters, alleen een numeriek display, maar de leerlingen hebben de productcodes snel door.
voorraad = {'chips': 4, 'snoep': 0, 'fris': 7, 'water': 2}
def koop(product, geld):
if voorraad[product] > 0:
voorraad[product] = voorraad[product] - 1
return geld - 1.50
return geld
print(koop('chips', 5.00))
print(koop('snoep', 5.00))
print(koop('fris', 1.00))
print(koop('water', 2.00))
print(koop('cola', 2.00))
De eigenaar wil dat de automaat blijft werken, ook als een leerling een productcode intypt die niet bestaat.
1. Bereken de uitvoer van de vijf print-opdrachten en de eindwaarde van voorraad. Leg per aanroep uit wat er gebeurt.
Read the worked answer
koop('chips', 5.00): voorraad['chips'] is 4, dus groter dan 0 → voorraad wordt 3; return 5.00 − 1.50 = 3.5 → print 3.5.koop('snoep', 5.00): voorraad is 0, dus niet groter dan 0 → return 5.0 → print 5.0. De leerling krijgt het geld terug, maar ziet geen melding; voor de automaat is dit onzichtbaar.koop('fris', 1.00): 7 > 0 → voorraad wordt 6; return 1.00 − 1.50 = −0.5 → print -0.5. Er is nergens gecontroleerd of er genoeg geld is: de automaat geeft het product weg en boekt een schuld van €0,50.koop('water', 2.00): 2 > 0 → voorraad wordt 1; return 0.5 → print 0.5.koop('cola', 2.00): 'cola' staat niet in de dictionary, dusvoorraad['cola']geeft een KeyError. Het programma stopt met een foutmelding en de print komt niet meer; in een echte automaat blijft het ingeworpen geld staan.
Eindvoorraad: {'chips': 3, 'snoep': 0, 'fris': 6, 'water': 1}. Er zijn drie fouten: geen controle of het product bestaat, geen controle of er genoeg geld is, en geen terugkoppeling bij uitverkocht.
2. Herschrijf de functie zodat alle drie de problemen zijn opgelost. Leg uit waarom de volgorde van de controles uitmaakt.
Read the worked answer
PRIJS = 1.50
def koop(product, geld):
if product not in voorraad:
return 'Onbekende code', geld
if voorraad[product] == 0:
return 'Uitverkocht', geld
if geld < PRIJS:
return 'Te weinig geld', geld
voorraad[product] = voorraad[product] - 1
return 'Bedankt', round(geld - PRIJS, 2)
Volgorde. Eerst lidmaatschap controleren: doe je dat niet, dan geeft voorraad[product] een KeyError vóór je iets kunt melden. Daarna voorraad, dan geld, en pas daarna de voorraad verlagen. Verlaag je eerst, dan moet je bij te weinig geld de verlaging weer ongedaan maken; dat vergeet iemand een keer en dan klopt de administratie niet meer. round voorkomt uitvoer als 3.4999999999; netter is om intern in centen te rekenen en pas bij het tonen om te zetten. De functie geeft nu naast de uitkomst ook een melding terug, zodat het display iets kan laten zien.
3. Een collega stelt voor om alle code in één grote try/except te zetten, zodat de automaat nooit meer stopt. Beoordeel dat voorstel.
Read the worked answer
Het goede deel: een try/except rond de aanroep vangt de KeyError op, dus bij een verkeerde code blijft de automaat draaien en blijft het geld van de leerling niet hangen. Dat is een echte verbetering ten opzichte van een crash.
Het zwakke deel: één grote try/except vangt ook fouten die je niet verwacht, zoals een typefout of een verkeerde variabelenaam. Die verberg je, en dan zie je ze nooit meer. De automaat lijkt te werken terwijl er stilletjes niets gebeurt. Bovendien kun je het verschil tussen 'onbekende code', 'uitverkocht' en 'interne fout' niet meer aan de leerling laten zien, en dat verschil bepaalt of hij zijn geld terugkrijgt.
Beter is twee lagen: expliciete controles met in voor de situaties die je kent en wilt afhandelen, en een smallere try/except alleen rond kwetsbare delen, bijvoorbeeld het aansturen van het uitgiftemechaniek dat kan weigeren. Vang op wat onvoorspelbaar is, controleer wat voorspelbaar is, en geef in beide gevallen een melding op het display en het geld terug.