Programmierpraktikum SoSe 2024, Bachelor Informatik, FU Berlin
ProPra2024 > Debugging > Häufige-Defektarten > Anordnungsdefekte

Defekte durch falsche Anordnung von Anweisungen

Idea

Ziel

Ich verstehe, in welcher Form Lokalisierungsdefekte meinen Code mangelhaft werden lassen, und habe einen solchen Defekt in fremdem Code erfolgreich gefunden.

Detailed

Arbeitsschritte

Warnung:

Der in dieser Aufgabe zu bearbeitende Code gehört zum Spiel "Go Fish". Ein erster Code hierzu wird in der Aufgabe Defekte Ausdrücke besprochen. Es ist nicht nötig diese Aufgabe vorher bearbeitet zu haben, da die beiden Probleme keinen Code teilen. Allerdings liefert die Bearbeitung der ersten Aufgabe etwas mehr Kontext über das ganze Programm.

Eine Heranführung an Lokalisierungsdefekte

Als Lokalisierungsdefekt werden Defekte bezeichnet, bei dem sich Code in der falschen Reihenfolge innerhalb des Programms befindet. Ein Beispiel ist es, wenn die Initialisierung einer Variable innerhalb einer Schleife anstatt außerhalb stattfindet:

1
2
3
while count < maxcount:
    count = 0
    # code that updates count

Ein weiteres Beispiel für einen Lokalisierungsdefekt ist es, wenn die Reihenfolge der Anweisungen nicht mit der gewünschten Reihenfolge der Operationen übereinstimmt. Häufig werden versehentlich zwei Instruktionen vertauscht. Der folgende Code versucht ein Element aus einer Liste auf 0 zu setzen, nachdem es den Wert zu einer Summe hinzugefügt hat. Leider sind die beiden Instruktionen vertauscht:

1
2
some_list[f] = 0;
total += some_list[f];

Eine arithmetische Operation kann auch zwei vertauschte Ausdrücke besitzen, wie man an diesem Beispiel sieht, bei dem versucht wird die Hypotenuse eines Dreiecks zu berechnen:

1
2
3
import math
result = math.sqrt(c)
c = math.pow(a,2) + math.pow(b,2)

Die bisherigen Beispiele erkennt man noch relativ leicht als falsch. Schwieriger wird es, wenn verschachtelte Blöcke im Spiel sind und Anweisungen zwar in der optisch richtigen Reihenfolge, aber leider im falschen Block stehen:

1
2
3
4
5
6
7
if found_blank:
    if string_done:
        clean_up()
        if not buffer.empty():
            free(buffer)

    return

In diesem Fall sollte der return-Ausdruck wahrscheinlich eine Ebene tiefer liegen (die Funktion soll also nur zurückkehren, wenn string_done wahr ist, aber nicht nur, weil found_blank wahr ist). Natürlich könnte dieser Code auch ebenso richtig sein. Es ist nur wichtig zu begreifen, dass solche Defekte sehr leicht zu übersehen sind.

Lokalisierungsdefekte können auch so aussehen, dass ganz fremde Code-Zeilen hinzugefügt worden sind:

1
2
3
4
# sort the list
list_head = sort(list_head)
list_head = None  # what is this thing doing here?
print(list_head)

Diese überflüssige Anweisung könnte ein Überbleibsel eines früheren Algorithmus sein, ein Fehler beim Kopieren und Einfügen oder einfach ein Fehler des Programmierers.

Ihre Aufgabe

In dieser Aufgabe sollen Sie einen Code debuggen, der einen Lokalisierungsdefekt beinhaltet. Die Funktion beinhaltet einen weiteren Teil des Spiels "Go Fish", das in der Aufgabe Defekte Ausdrücke eingeführt worden ist. Sie prüft, ob er Gegenspieler Karten von einem bestimmten Rang besitzt. Wenn dem so ist, werden diese Karten in die Hand des Spielers transferiert. Wenn dies dazu führt, dass der Spieler vier Karten desselben Rangs auf der Hand hält, werden diese Karten von der Hand des Spielers abgeworfen.

Bemerkung:

Falls Sie die Aufgabe Defekte Ausdrücke nicht bearbeitet haben, ist hier eine kurze Erinnerung über die Datenstrukturen des Spiels "Go Fish", die auch in dieser Aufgabe benutzt werden:

  • Karten werden anhand ihres Rangs und ihrer Farbe identifiziert.
    Dabei ist der Rang ein Element aus der Liste ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"] (auf Englisch: 2 to 10, Jack, Queen, King, Ace; auf Deutsch: 2 bis 10, Bube, Dame, König, Ass) und die Farbe ein Element aus der Liste ["spades", "hearts", "diamonds", "clubs"] (auf Deutsch: Pik, Herz, Karo, Kreuz).
  • Eine Hand ist ein Wörterbuch.
    In jedem Element des Wörterbuchs ist der Schlüssel ein Rang und sein Wert eine Liste von dazugehörigen Farben, die der Spieler in seiner Hand hält. Wenn also z. B. ein Spieler die "Pik 3" und "Herz 3" in seiner Hand hält, aber keine weiteren 3er-Karten, dann lautet der Wörterbucheintrag {"3": ["spades", "hearts"]}. Ein Schlüssel sollte keine leeren Listen beinhalten; wenn keine Karte des gegebenen Rangs existiert, dann existiert kein Wert für diesen Schlüssel.

Die Funktion check_card() nimmt vier Parameter:

  • den Namen des Spielers als String (wird nur genutzt, um beim Ablegen sagen zu können, wer denn überhaupt ablegt),
  • die Hand des Spielers,
  • den Rang, der in der Hand des Gegenspielers geprüft werden soll und
  • die Hand des Gegenspielers.
 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
def check_card(
    name_of_hand: str, hand_of_player: dict, rank_of_card: str, hand_of_opponent: dict
) -> bool:
    """Check if opponent_hand contains any cards of the
    specified rank, if it does, transfer them to player_hand.

    hand_name: A string with the name of player_hand
    player_hand: A hand dictionary, as described above
    card_rank: A string with the name of a
               card rank ("2" through "10", "J", "Q", "K, or "A")
    opponent_hand: A hand dictionary, as described above

    Returns: True if a card is transferred, False otherwise
    """

    if rank_of_card in hand_of_opponent:
        transfer_cards: list = hand_of_opponent[rank_of_card]
        # transfer_cards is a list!
        del hand_of_opponent[rank_of_card]
        if rank_of_card in hand_of_player:
            hand_of_player[rank_of_card].extend(transfer_cards)
        else:  # shouldn't happen, but handle it
            hand_of_player[rank_of_card] = transfer_cards

        if len(hand_of_player[rank_of_card]) == 4:
            print(f"{name_of_hand} lays down {rank_of_card}")
            del hand_of_player[rank_of_card]

            return True
        else:
            return False


hand_name = "HAND"
player_hand = {"5": ["spades", "hearts"]}
card_rank = "5"
opponent_hand = {"6": ["diamonds"], "10": ["clubs"]}

print(f"hand -> {player_hand}")
print(check_card(hand_name, player_hand, card_rank, opponent_hand))
print(f"hand -> {player_hand}")

Hier sind einige Vorschläge, um an den Code heranzutreten:

  1. Besitzt der Code implizierte else-Ausdrücke?
    Welche Eingaben würden dazu führen, dass diese ausgeführt werden?
  2. Der Kommentar in Zeile 18 ist einer der wenigen Kommentare im Hauptalgorithmus, der auf einen nicht offensichtlichen Aspekt des Codes hinweist.
    Ist der Kommentar richtig?
  3. Da die Anweisung in Zeile 23 in einem echten "Go Fish"-Spiel normalerweise nicht ausgeführt würde (man kann nicht nach einem bestimmten Rang fragen, wenn man nicht bereits eine Karte dieses Rangs auf der Hand hat), ist es ein riskanter Code. Er wurde vielleicht nie getestet, aber jemand, der diese Funktion von irgendwo anders aufruft, könnte annehmen, dass sie funktioniert.
    Es ist also ein guter Bereich, um nach einem Defekt zu suchen.
Hinweis (nur bei Bedarf): Lösungshinweis

Durchlaufen Sie den Code mit den folgenden Parametern:

Erste Eingabe

Der Gegenspieler hat eine Karte des gefragten Rangs:

1
2
3
4
5
hand_name = "HAND"
player_hand = {"5": ["spades", "hearts"]}
card_rank = "5"
opponent_hand = {"6": ["diamonds"], 
                 "10": ["clubs"]}
Hinweis (nur bei Bedarf): Zweite Eingabe

Der Gegenspieler besitzt keine Karte des gefragten Rangs:

1
2
3
4
hand_name = "HAND"
player_hand = {"A": ["clubs"]}
card_rank = "A"
opponent_hand = {"2": ["hearts"]}
Hinweis (nur bei Bedarf): Dritte Eingabe

Der Gegenspieler besitzt zwei Karten des Rangs und der Spieler hält nach dem Transfer alle vier Karten des Rangs.

1
2
3
4
5
hand_name = "HAND"
player_hand = {"6": ["spades", "hearts"],
               "Q": ["spades", "hearts"]}
card_rank = "6"
opponent_hand = {"6": ["diamonds", "clubs"]}
Hinweis (nur bei Bedarf): Vierte Eingabe

Der Gegenspieler besitzt Karten vom gefragten Rang, aber der Spieler besitzt keine Karten von diesem Rang (das ist ein Regelverstoß und sollte in einem echten Spiel von "Go Fish" nicht passieren)

1
2
3
4
hand_name = "HAND"
player_hand = {"J": ["hearts"]}
card_rank = "2"
opponent_hand = {"2": ["clubs", "spades"]}
  • Defekt gefunden? Prima. Dann jetzt bitte in Anordnungsdefekte.py korrigieren.
  • Machen sie einen Commit Anordnungsdefekte.py corrected, der nur genau diese modifizierte Datei enthält.
  • 1 git -P show HEAD
Snippet

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.

Die Zeilen 29 bis 31 sind falsch indentiert. return True muss in allen Fällen ausgeführt werden, in denen das if von Zeile 16 wahr ist. Die Lösung ist, dass die drei Zeilen eine Ebene vorgezogen werden müssen.

Erklärung: Eine Karte des angegebenen Ranges befindet sich in der Hand des Gegners, was dazu führt, dass sie auf die Hand des Spielers übertragen wird; dies ist die definierte Situation, in der die Funktion True zurückgeben sollte.