Logikdefekte
Ziel
Ich verstehe, welche Form logische Defekte in Code annehmen können und habe einen solchen Defekt in fremdem Code erfolgreich gefunden.
Arbeitsschritte
Eine Heranführung an logische Defekte
Computer sind präzise in der Ausführung von Anweisungen, aber unfähig, Absichten zu
antizipieren, was zu logischen Defekten führt, oft basierend auf schlechten Annahmen über Daten.
Sehen Sie sich hierfür das folgende Code-Beispiel in Python an.
Hier wird versucht, mittels Wissen über die Repräsentation von ASCII-Zeichen
einen String in eine kleingeschriebene Variante umzuwandeln.
ord()
wandelt dabei ein Zeichen in seinen numerischen ASCII-Wert um, während chr()
das
Gegenteil macht.
1 2 3 4 |
|
Versuchen Sie für sich den Code mittels einer
ASCII-Tabelle nachzuvollziehen.
Der Code funktioniert, wenn s[k]
ein Großbuchstabe ist.
Bei Kleinbuchstaben, Satzzeichen und Leerzeichen liefert er aber nicht das gewünschte Resultat.
Der Grund hierfür ist, dass man die Logik der ASCII-Tabelle ignoriert hat.
Hier haben Klein- und Großbuchstaben verschiedene Codierungen.
Ein weiteres Beispiel findet man bei der Initialisierung von Schleifen.
Pythons for
-Schleife ist hier sehr robust, deshalb behelfen wir uns der Syntax einer
for
-Schleife in C.
Vor allem bei der Überlegung, wie man die Schleife beendet, können Denkfehler auftreten.
In C-Syntax besteht die Initialisierung einer for
-Schleife aus drei Teilen:
- Zuerst wird eine Laufvariable, auch Index genannt, initialisiert.
- Danach wird eine Abbruchbedingung gestellt und die
for
-Schleife läuft, solange diese Bedingung nicht erfüllt ist. In der Regel benutzt man den Index zur Definition der Abbruchbedingung. - Letztlich wird noch definiert wie der Index innerhalb der Schleife inkrementiert wird.
Sehen Sie schon das Problem?
1 |
|
Hinweis (nur bei Bedarf): Der Index der Schleife…
…fängt bei 1 an, wird bei jeder Iteration um 2 erhöht (ist also immer ungerade)
und die Schleife läuft, solange der Index den Wert 100 nicht annimmt.
Das Abbruchkriterium kann also allenfalls erfüllt werden, wenn der Schleifenrumpf weitere
Änderungen an j
macht;
andernfalls wird die Schleife niemals anhalten.
Hier wäre vermutlich die Bedingung j < 100
sinnvoller. Um genau diese Art von Fehler
zu vermeiden, ist der idiomatische Weg in Python auch for j in range(1, 100, 2)
.
Genauso kann durch ein falsch gesetztes oder vergessenes break
der richtige Zeitpunkt zum Austritt aus der Schleife verpasst werden,
wie z. B. in diesem Python-Code.
1 2 3 4 5 6 7 8 9 10 11 |
|
Am Ende der Zeile (end_of_line()
) wird eine Aufräum-Funktion (cleanup()
) aufgerufen.
Es wird aber verpasst, danach aus der while-Schleife auszubrechen, wodurch es folgend zum
Versagen kommen kann.
Die bisherigen Beispiele für Defekte sind durch kleine Änderungen behebbar gewesen.
Dass das nicht immer der Fall sein muss, soll das folgende Beispiel zeigen.
Im folgenden Python-Code wird versucht den Abstand zwischen den zwei Zahlen in der Liste a
zu
finden, die am weitesten voneinander entfernt sind.
In der Liste a = [2, 1, 10, 100]
sollte also das Ergebnis 99 lauten, denn die beiden am weitesten
voneinander entfernten Zahlen lauten 1 und 100.
1 2 3 4 5 6 7 8 |
|
Dieser Code tut genau das, was der Programmierer vorhat und berechnet den Abstand zwischen zwei
Zahlen aus der Liste a
.
Der Algorithmus ist aber falsch, da er davon ausgeht, dass die zwei am weitesten voneinander
entfernten Zahlen nebeneinander liegen.
Eine kleine Änderung wird bei diesem logischen Defekt nicht helfen; der gesamte Algorithmus muss
überarbeitet werden.
Ihre Aufgabe
Im Folgenden sollen Sie eine Funktion debuggen, in der ein logischer Defekt vorliegt. Diese Funktion teilt Personen fürs Weihnachtswichteln ihren Partnern zu. Hierfür erhält die Funktion eine Liste von Namen und gibt ein Dictionary zurück, in dem die Schlüssel die Schenker und die Werte die Beschenkten darstellen. Das ist gar nicht so trivial: Die Funktion muss die Situation verhindern, in der die ersten N-1 Personen unter sich selbst Geschenke verteilen und die letzte Person nur noch sich selbst beschenken könnte. Im folgenden Python-Code kann in einigen Iterationen bei der Handhabung dieses Falls ein Defekt auftreten, durch den eine Person doppelt beschenkt wird:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
Hier sind einige Vorschläge, um an den Code heranzutreten:
- Es werden zwei Listen (
input_list
undreceivers_list
) und ein Dictionary (return_dict
) benutzt.
Benennen Sie, was das Ziel dieser Datenstrukturen in diesem Code ist und notieren Sie, wo und wie diese verändert werden. - In Zeile 43 wird die Funktion
index()
benutzt, um eine Person inreceivers_list
zu finden. Das impliziert, dass diese Person inreceivers_list
sein muss.
Gibt es eine Garantie, dass das der Fall sein muss? - Es existiert eine Invariante zwischen
return_dict
undreceivers_list
, in welcher ein Element in einer der Listen ist, aber nicht in der anderen. Prüfen Sie, die entsprechenden Zeilen Code, die sicherstellen, dass diese Invariante immer wahr ist. - Was ist das Ziel des Codes in den Zeilen 41 bis 46?
Über wie viele Pfade kann man in diesen Code durchlaufen?
Hinweis (nur bei Bedarf): Lösungshinweis 1
Durchlaufen Sie eine Iteration des Codes mit der unter test_input
angegebenen Liste.
Hinweis (nur bei Bedarf): Lösungshinweis 2
Nehmen Sie noch einmal die Eingabe aus test_input
.
Stellen Sie sich vor, dass die zweite Iteration läuft, also person
gleich Joe
ist und
die receivers_list
aus ["Tom", "Donna", "Susan", "Paul"]
besteht.
Hinweis (nur bei Bedarf): Lösungshinweis 3
Bedenken Sie eine vierte Iteration, in der person
aus "Susan"
besteht und
nehmen Sie an, dass die receivers_list
jetzt aus ["Donna, "Paul"]
besteht.
Welche Zuteilungen sind hier noch möglich?
- Defekt gefunden? Prima. Dann jetzt bitte in
Logikdefekte.py
korrigieren. - Machen sie einen Commit
Logikdefekte.py corrected
, der nur genau diese modifizierte Datei enthält. - 1
git -P show HEAD
Abgabe
Geben Sie ein Kommandoprotokoll ab, das genau nur die Eingaben und Ausgaben der obigen Kommandos 1, 2, … enthält. Entfernen Sie vor Abgabe eventuelle Fehlversuche und sonstige zusätzliche Kommandos aus dem Protokoll.