6D: Design, Debugging und Donuts

In dieser Lektion gibt es einige Tipps, um das Entwerfen und Debuggen Deiner Programme einfacher zu machen. Je länger Du programmierst, um so besser wirst Du darin werden, Fehler zu vermeiden und zu beheben, aber hier werden wir versuchen, Dir ein paar sehr allgemeine und nützliche Ratschläge zu geben. Wir werden die folgenden Techniken erläutern:

  • erstelle jeden Teil einzeln
  • löse einige Beispiele von Hand
  • plane deinen Code bevor Du ihn schreibst
  • schreibe den Code
  • teste die Beispiele, die Du von Hand gelöst hast
  • teste zusätzliche Beispiele inklusive Grenzfälle und zufällig gewählte Fälle

Diese Zauberformel wird nicht alles automatisch lösen, aber sie kann Dir einiges an Sorgen ersparen und die Probleme reduzieren, die später ausgebügelt werden müssen.

Wenn ein Problem auftaucht, sind einige Strategien, um Dir das Debuggen zu erleichtern Verwenden von diagnostischen Ausdrücken (das wird später in dieser Lektion noch erläutert), Benutzen des Visualizers, den Code sorgfältig lesen, und gute Tests schreiben. Falls Du Python zu Hause benutzt, kannst Du breakpoint und step Tools verwenden, um dahinter zu kommen, was falsch läuft.

Ein Algorithmus ist eine Abfolge von Anweisungen. Während ein Computerprogramm, um zu funktionieren, in einer bestimmten Sprache wie Python oder JavaScript geschrieben sein muss, bedeutet das Wort Algorithmus lediglich eine Schritt-für-Schritt Handlungsanweisung, die auch auf Deutsch geschrieben oder in einem Diagramm ausgedrückt werden kann. Ein Rezept für Donuts ist zum Beispiel ein Algorithmus, mit "nehme drei Tassen Mehl" als ein Schritt, und "lass den Teig für zwei Stunden gehen" als ein weiterer. Wir werden in dieser Lektion einen einfachen Algorithmus entwickeln. Dann werden wir den Algorithmus implementieren, das heißt, wir werden ihn zu einem benutzbaren Computerprogramm machen.

Das Problem: Der Donut Rechner

Tim Horton's, eine Café-Kette in Kanada, verkauft Timbits, das sind leckere Donut Kugeln:

Photo: looking_and_learning, flickr

Du musst in den nächsten Wochen mehrere Parties organisieren, und Du wirst für jede Party eine andere Anzahl an Timbits bestellen. Also willst Du ein Programm schreiben, um den Preis einer beliebigen Anzahl von Timbits zu errechnen, um Dein Budget zu planen. In der Filiale in Deiner Nähe gelten die folgenden Preise:

Anzahl Preis
1 $0.20
10 (small box) $1.99
20 (medium box) $3.39
40 (large box) $6.19

Erstelle jeden Teil einzeln

Letztendlich möchtest Du ein Programm schreiben, dessen Input die Anzahl P der Leute auf der Party und dessen Output die Kosten, so viele Leute zu verpflegen inklusive Steuern ist. Auf der höchsten Ebene hat dieses Programm drei Komponenten:

  1. berechne für P Leute, was die Anzahl T von Timbits ist, die Du kaufen solltest?
  2. berechne den Preis, um genau T Timbits zu kaufen
  3. berechne die Steuern und Gesamtkosten.

Aber Du kannst jeden dieser Teile für sich bearbeiten. Wir empfehlen sogar, dass Du jeden Teil einzeln erstellst und ihn testest und in Ordnung bringst, bevor Du anfängst, am nächsten Teil zu arbeiten, denn je größer dein Programm ist, um so schwieriger ist es, jeden Bug zu finden und zu beheben. Es ist einfacher, die drei Teile einzeln zu entwickeln und zu testen, bevor man sie zu einem Programm zusammensetzt. Für den Rest dieser Lektion werden wir uns auf Schritt 2 konzentrieren: berechne den Preis, um genau T Timbits zu kaufen.

Löse einige Beispiele per Hand

Um dem Computer sagen zu können, was er tun soll, musst Du selbst in der Lage sein, es zu tun. Sehen wir uns ein Beispiel genauer an: was ist der Preis, um genau 45 Timbits zu kaufen? Wenn wir uns die Liste von Größen anschauen, können wir eine große Box kaufen (40), die $6.19 kostet. Übrig bleiben 45 – 40 = 5 Timbits, die wir einzeln kaufen müssen, die zusätzlich 5 * $0.20 = $1.00 kosten. Die Gesamtkosten sind damit $6.19 + $1.00 = $7.19.

Plane deinen Code, bevor Du ihn schreibst

Was wäre, wenn Du 4 oder 456 Timbits anstatt 45 kaufen wolltest? Allgemein sollten wir so viele große Boxen kaufen wie möglich, dann eine mittlere Box und/oder eine kleine Box nehmen falls benötigt, und einzelne Timbits, um die richtige Gesamtzahl zu erreichen.

Ist dieser Algorithmus wirklich optimal? Ja! Du kannst sehen, dass zwei mittlere Boxen zu kaufen (2*$3.39 = $6.78) eine schlechte Idee ist, da wir 2*20=40 Timbits billiger in einer einzelnen großen Box bekommen können. Ähnliche Vergleiche zwischen einzeln/klein und klein/mittel können benutzt werden, um zu beweisen, dass der Algorithmus den günstigsten Preis liefert, um eine bestimmte Anzahl an Timbits zu kaufen. Wenn die Preise/Größen andere wären, könnte es sein, dass wir vorsichtiger sein müssten.

Wir haben gesagt, dass Du deinen Code planen solltest, bevor Du ihn schreibst. Du kannst:

  • eine kurze Schritt-für-Schritt Beschreibung des Algorithmus' auf Deutsch statt in Python geben
  • ein Diagramm oder Flowchart benutzen, um die Schritte zu zeigen
  • dich für einen Moment auf die wichtigsten Ideen statt auf die Details konzentrieren.

In unserem Beispiel geben wir eine Beschreibung auf Deutsch. Wir zeigen Dir kein Flowchart für dieses spezifische Programm, weil es nur in einer geraden Linie von Schritt 1 zu Schritt 2 und so weiter laufen würde. Für Programme mit Schleifen, wie wir später noch sehen werden, sind Flowcharts nützlicher.

Algorithmus, um den Preis einer beliebigen Anzahl von Timbits zu berechnen

  1. nimm die Input Anzahl von Timbits
  2. verfolge die Gesamtkosten, beginnend bei Null
  3. kaufe so viele große Boxen wie möglich
    1. berechne die Anzahl von Timbits, die noch benötigt wird
    2. passe die Gesamtkosten an
  4. kaufe eine mittlere Box, falls Du kannst und wiederhole Schritt A. und B.
  5. kaufe eine kleine Box, falls Du kannst und wiederhole Schritt A. und B.
  6. kaufe einzelne Timbits und wiederhole Schritt B.
  7. gib die Gesamtkosten aus

Wir nennen dies eine Pseudocode-Beschreibung des Algorithmus, weil es eine Reihe von Schritt-für-Schritt Anweisungen ist, aber nicht in einer echten Programmiersprache.

Schreibe den Code

Um den ersten Ratschlag noch einmal anzuwenden, erstelle jeden Teil einzeln, kannst Du auch auch ein separates Stück Code für jeden Schritt von 1 bis 7 schreiben. Zusätzlich gibt es eine Sache, die wir für alle Schritte beachten müssen: welche Variablen werden wir benutzen? Was werden ihre Namen, Typen und Ausgangswerte sein?

Wir benutzen timbitsLeft, um die Anzahl von Timbits zu repräsentieren, die wir noch kaufen müssen und wir benutzen totalCost, um die Gesamtkosten bis jetzt zu repräsentieren. Wir müssen Python die Typen nicht im Voraus sagen, aber wir können für uns planen, dass timbitsLeft ein int sein wird, während totalCost eine float-Variable, die die Gesamtkosten in Dollar misst, sein wird. (Also ist ein Cent 0.01). Wir werden einige zusätzliche temporäre Variablen innerhalb der einzelnen Schritte brauchen, die wir erst angehen werden, wenn wir sie brauchen.

Schritte 1 und 2 können mit jeweils einer Zeile Code geschrieben werden:

timbitsLeft = int(input()) # Schritt 1: nimm den Input
totalCost = 0              # Schritt 2: setze die Gesamtkosten auf ihren Ausgangswert
Für Schritt 3 müssen wir die maximale Anzahl an großen Boxen berechnen, die wir kaufen können. Wie genau können wir das machen? Da große Boxen 40 Timbits enthalten, sollte die Zahl bigBoxes der ganzzahlige Quotient von timbitsLeft geteilt durch 40 sein. Die Division, die Kosten und die Subtraktion einbeziehend, bekommen wir das folgende Codefragment:

# Schritt 3: kaufe so viele große Boxen, wie Du kannst
bigBoxes = timbitsLeft / 40
totalCost = totalCost + bigBoxes * 6.19    # passe die Gesamtkosten an
timbitsLeft = timbitsLeft - 40 * bigBoxes  # berechne die noch benötigten Timbits
Eigentlich, so stellt sich heraus, haben wir bereits einen Fehler gemacht. Die beste Methode, um Fehler zu entdecken, ist es, deinen Code frühzeitig und häufig zu testen! Lasst uns optimistischerweise sagen, dass wir den Codebestandteil an dieser Stelle testen. Wir werden einfach ein paar print statements einfügen, um zu beobachten, wie er sich bis jetzt verhält. (Anstatt print statements kannst Du auch den Visualizer benutzen, um jeden Schritt zu überprüfen.)

Beispiel
Testet den Code bis hierhin mit dem Input 45, unter Verwendung von print statements um das Debuggen zu erleichtern.

Das funktioniert nicht so, wie wir es uns vorgestellt hatten! Wo liegt das Problem? Welches ist die erste Zeile, in der etwas passiert ist, was wir so nicht geplant hatten? Versuche sie zu finden, bevor Du den nächsten Absatz öffnest. Denk daran, wir haben dieses Beispiel vorhin per Hand gelöst.

Klicke, um die Erklärung zu lesen

Schuld an dem Problem ist die Zeile bigBoxes = timbitsLeft / 40, denn bigBoxes wird berechnet als 1.125, während wir es mit einem Wert von 1 haben wollten. Man kann keinen Bruchteil einer Box kaufen. Ein möglicher Ausweg, den Du in Lektion 7A sehen wirst, ist // zu benutzen, um ganzzahlig zu dividieren anstatt /, das Dezimalstellen hinter dem Komma ausgibt. Eine andere Lösung, die auf dem aufbaut, was wir in Lektion 4 gesehen haben, ist es,timbitsLeft / 40 in einen int zu konvertieren, um den Bruchteil zu beseitigen. Wir werden diesen Ansatz benutzen: die Zeile wird zu bigBoxes = int(timbitsLeft / 40).

Schritte 4 und 5 sind ähnlich wie Schritt 3. Als eine Alternative, da du höchstens eine mittlere Box und höchstens eine kleine Box kaufen wirst, können wir auch ein if Statement benutzen:

if timbitsLeft >= 20: # Schritt 4, können wir eine mittlere Box kaufen?
  totalCost = totalCost + 3.39
  timbitsLeft = timbitsLeft - 20
if timbitsLeft >= 10: # Schritt 5, können wir eine kleine Box kaufen?
  totalCost = totalCost + 1.99
  timbitsLeft = timbitsLeft - 20
Zum Schluss müssen wir noch 20 Cent für jeden der letzten benötigten Timbits bezahlen und die Antwort auswerfen.

totalCost = totalCost + timbitsLeft * 20 # Schritt 6
print(totalCost)                         # Schritt 7

Teste die Beispiele, die Du von Hand gelöst hast

Hier ist das vollständige Programm. Das erste, was wir tun werden, ist, zu testen, ob es mit dem Input 45 funktioniert, den wir von Hand gelöst hatten.

Beispiel
Was ist der Preis für 45 Timbits?
Du kannst die Eingabe für das Programm in die Box unten eingeben.

Es muss einen Bug geben, da wir über 100 Dollar für nur 45 Timbits bezahlen sollen! Deine letzte Aufgabe ist es, diesen Bug und einen weiteren Bug, der sich im Code versteckt hat, zu finden und zu beseitigen. Das wird in der nächsten Lektion getestet.

Nur damit Du es weißt, es gibt etwas Seltsames bei Tim Hortons Preisen, das kein Bug ist: es kann billiger sein, genau T+1 Timbits zu kaufen als genau T Timbits zu kaufen. Zum Beispiel genau 19 Timbits zu kaufen kostet $3.79, während genau 20 Timbits nur $3.39 kosten. Trotzdem werden wir bei dem Problem bleiben, genau T Timbits so günstig wie möglich zu kaufen, für das der oben erläuterte Algorithmus korrekt ist. Also sollte Dein Programm tatsächlich 3.79 ausgeben, wenn der Input 19 ist.

Teste zusätzliche Beispiele

In Computer Science Circles versuchen wir Deinen Code intelligent und automatisiert zu testen, um sicherzustellen, dass Du das Problem korrekt gelöst hast. Aber in einer echten Programmiersituation ist es deine eigene Aufgabe, deinen Code sorgfältig zu testen. Hier sind einige nützliche Richtlinien für das Testen.

  • Du kannst überprüfen, dass jede Zeile Code deines Programms korrekt funktioniert. Bezogen auf das Timbit Problem wird der Input 10 testen, ob wir kleine Boxen richtig handhaben. So ähnlich werden die Inputs 20 und 40 überprüfen, dass mittlere und große Boxen korrekt gehandhabt werden. Zuletzt solltest Du überprüfen, ob wir einzelne Timbits richtig handhaben (z.B. mit dem Input 1).
    • Dies kann helfen, zu überprüfen, dass Werte wie $3.39 und $1.99 korrekt eingegeben wurden.
  • Überprüfe auf jeden Fall "Grenzfälle", die dein Programm an seine Grenzen bringen. Zum Beispiel ist der minimale mögliche Input für dieses Programm 0, da es keine negativen Timbits gibts. Unser Programm hat keinen maximalen Input, aber es wäre sinnvoll, Werte wie 39 und 41 zu überprüfen, die nahe an der Grenze zu einfach nur eine Box kaufen sind.
    • Dies kann helfen zu überprüfen, dass Du nicht aus Versehen > eingetippt hast, als Du stattdessen >= hättest eintippen sollen.
  • In einigen Fällen kannst du automatisch jeden Input überprüfen, oder eine große Anzahl von zufälligen Inputs überprüfen, um zu sehen, ob dein Programm wie gewollt funktioniert. Wir benutzen häufig zufällige Inputs in CS Circles' internen Tests. Benutze import random am Anfang deines Programms, dann wird zum Beispiel random.randint(10, 100) eine zufällige ganze Zahl zwischen 10 und 100 erzeugen.

Kannst Du beide Bugs finden und alle Tests bestehen? Probier es mal!

Manche Berechnungen führen zu kleinen Fehlern in Python:
Beispiel: Rundungsfehler
Es ist überraschend, dass der obenstehende Code nicht genau 0.35 gibt! Es hat etwas damit zu tun, wie Python Informationen speichert. Mach dir über solche winzigen Fehler keine Gedanken: der Grader ignoriert sie. Dazu kommt mehr in Lektion 7B.

Programmierübung: Timbits
Behebe die Bugs und bestehe alle Tests!
Du kannst die Eingabe für das Programm in die Box unten eingeben.

Schon hungrig? Du bist bereit mit der nächsten Lektion fortzufahren!

Während 10 Timbits eine Box ergeben, ergeben 8 Timbits ein Timbyte. Photo: thisisbrianfisher, flickr