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

Falsch benutzte Variable

Idea

Ziel

Ich verstehe, in welcher Form falsch benutzte Variablen als Defekte auftreten, 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". Der vorhergehende Code hierzu wird in den Aufgaben Defekte Ausdrücke und Defekte durch falsche Anordnung von Anweisungen besprochen. Wenn Sie den Code durch aufmerksames Lesen und händisches Durchgehen debuggen, ist es nicht nötig, die ersten beiden Aufgaben bearbeitet zu haben. Sollten Sie allerdings dem Defekt über Tools oder zusätzlichen Code auf die Schliche kommen wollen, benötigen Sie den (korrigierten) Code aus den anderen beiden Aufgaben.

Eine Heranführung an falsch benutzte Variablen

Ein einfacher und häufiger Defekt ist die Benutzung des falschen Variablennamens. Zum Beispiel wollte der Autor

1
i = 5

schreiben, hat aber stattdessen folgendes geschrieben:

1
j = 5

In Sprachen, in denen Variablen deklariert werden müssen, kann das alleine bereits zu einem Defekt führen, wenn j nicht deklariert wurde. Es kann aber auch sein, dass j auch deklariert wurde und denselben Typ wie i hat. Dies tritt häufiger auf, als man erwarten könnte. Es kann zum Beispiel schnell passieren, dass man mit dem Finger ausrutscht und benachbarte Tasten mitdrückt:

1
io = 5

Ob das schnell auffällt, kann davon abhängen, wie die Programmiersprache mit undeklarierten Variablen umgeht.

Eine Quelle von solchen Variablendefekten ist auch das Kopieren und Einfügen von ähnlichem Code. Wenn der Code zum Beispiel wie folgt aussieht

1
2
3
# adjust the endpoint of the line
x1 = transform(x1, x2, current_transform)
x2 = transform(y1, x2, current_transform)

wurde höchstwahrscheinlich die zweite Codezeile von der ersten kopiert und händisch das Auftreten von x geändert. Allerdings wurde eine Stelle übersehen, die zwar zu einem gültigen Ausdruck führt, aber nicht das intendierte Verhalten darstellt.

Überall, wo eine Variable benutzt wird, ist es möglich, die falsche Variable auf der linken oder rechten Seite der Zuweisung, als Argument für eine Funktion, als Rückgabewert usw. zu verwenden. Es kann beispielsweise dazu kommen, dass zwei Variablen als Parameter einer Funktion mitgegeben werden, aber vertauscht worden sind. Der Compiler wird diesen Defekt nicht entdecken, sofern beide Parameter von demselben Typ sind. Durch bloßes Draufgucken kann man ohne genaue Kenntnisse der Programmlogik im folgenden Beispiel nicht erkennen, welcher Funktionsaufruf der richtige ist:

1
2
draw_dot(y,x)  
draw_dot(x,y)

Wenn Ihr Code zwei Funktionen mit ähnlichem Namen verwendet, kann der Defekt auch darin liegen die beiden Funktionen zu vertauschen.

Ihre Aufgabe

Im Folgenden sollen Sie eine Funktion debuggen, in der ein Variablendefekt aufgetreten ist. Der Code spielt eine Runde des Spiels "Go Fish". Er benutzt die korrigierten Funktionen draw_code() aus der Aufgabe Defekte Ausdrücke und check_card() aus der Aufgabe Defekte durch falsche Anordnung von Anweisungen.

Eine Runde wird wie folgt durchgeführt: Es wird ein zufälliger Rang aus der Hand des Spielers ausgewählt und der Gegenspieler wird gefragt, ob er Karten dieses Rangs besitzt. Besitzt der Gegenspieler diese Karten, werden die Karten zum Spieler transferiert. Das wird solange durchgeführt, bis der Gegenspieler keine Karte des gefragten Ranges auf der Hand hat. In diesem Fall muss der Spieler "fischen" gehen und eine neue Karte vom Deck ziehen (was auch den Namen des Spiels erklärt).

Warnung:

Wenn Sie die Regeln des Spiels "Go Fish" auf Wikipedia gelesen haben, sollte Ihnen aufgefallen sein, dass der Spieler nach dem Ziehen der Karte immer noch am Zug ist, wenn die gezogene Karte den Rang der zuletzt gefragten Karte hat. Dieser Code prüft das nicht und das ist auch nicht der Defekt, der zu suchen ist!

Um ein vollständiges Spiel zu spielen, wird der Code so lange fortgesetzt, bis beide Spieler keine Karten mehr auf der Hand haben.

Bemerkung:

Falls Sie die Aufgaben Defekte Ausdrücke und Defekte durch falsche Anordnung von Anweisungen nicht bearbeitet haben, ist hier eine kurze Erinnerung über die Datenstrukturen des Spiels "Go Fish":

  • 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"] und die Farbe ein Element aus der Liste ["spades", "hearts", "diamonds", "clubs"].
  • Ein Deck ist eine Liste mit 52 Elementen. Jedes Element im Deck ist ein Tupel der Form (Rang, Farbe).
  • 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.
 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import random


def draw_card(name: str, deck: list, player_hand: dict) -> None:
    """Draw a new card from the deck and add it to
    hand. If the hand now holds the rank in all four
    suits, then remove them from the hand.

    name: A string with the name of player_hand, used
          only for display purposes.
    deck: A deck as described above.
    hand: A hand dictionary as described above.

    Returns: None.
    """


def check_card(
    hand_name: str, player_hand: dict, card_rank: str, opponent_hand: 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
    """


def do_turn(
    hand_name: str, deck: list[tuple], player_hand: dict, opponent_hand: dict
) -> None:
    """Play one turn of 'Go Fish'. A rank in player_hand is chosen,
    and if any cards of that rank exist in opponent_hand, they are transferred.
    This continues until no cards are transferred,
    at which point a new card is drawn from the deck into player_hand.

    hand_name: A string with the name of player_hand.
    deck: The current deck, a list of two-element tuples of the form (rank, suit).
    player_hand: A hand dictionary.
    opponent_hand: A hand dictionary.

    Returns: None.
    """

    """ Loop unless the player_hand is empty. Normally this loop exits via the break statement,
        when check_card() returns false meaning a card was not transferred.
    """

    while len(player_hand):
        """Pick a random index within the current hand..."""
        index = int(len(player_hand) * random.random())

        """ ...and use the rank of the card at that index as the one to ask for.
        """
        rank_to_check = list(player_hand.keys())[index]
        found = check_card(hand_name, opponent_hand, rank_to_check, player_hand)

        if not found:
            break

    # no transfer, so "go fish"
    draw_card(hand_name, deck, player_hand)


ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"]
suits = ["spades", "hearts", "diamonds", "clubs"]


def play_go_fish() -> None:
    """Initialization and loop of the game 'Go Fish'."""

    deck: list[tuple] = []
    hand1 = {}
    hand2 = {}

    for i in range(52):
        deck.append((ranks[i % 13], suits[i % 4]))

    for i in range(7):
        draw_card("HAND1", deck, hand1)
        draw_card("HAND2", deck, hand2)

    while True:
        do_turn("HAND1", deck, hand1, hand2)
        do_turn("HAND2", deck, hand2, hand1)

        if len(hand1) == 0 and len(hand2) == 0:
            break

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

  1. Es ist eine gute Idee, von unten nach oben vorzugehen:
    Überprüfen Sie, ob die do_turn()-Funktion korrekt ist, bevor Sie mit der play_go_fish()-Funktion weitermachen.
    Überlegen Sie sich eine Reihe von Parametern, mit denen Sie do_turn() testen können.
  2. Ist die Abfrage in Zeile 92 richtig?
    Wird das Spiel immer enden?
    Wird das Spiel zur richtigen Zeit enden?
  3. Ist die Initialisierung des Decks in den Zeilen 81 und 82 korrekt?
  4. Sehen Sie sich die vier Parameter der Funktion do_turn() an.
    Welche werden modifiziert und welche werden nur benutzt?
Hinweis (nur bei Bedarf): Lösungshinweise

Durchlaufen Sie die do_turn()-Funktion mit den folgenden Parametern: Das Deck hat nur eine Karte, damit die Zufälligkeit beim Kartenziehen eliminiert wird (auch wenn in einem echten Spiel das Deck 52 Karten hat, müssen Sie ein Versagen reproduzieren können, um es zu untersuchen).

Erste Eingabe

Der Spieler fragt seinen Gegenspieler nach einem Rang und als Resultat hat der Spieler alle vier Karten des Ranges auf seiner Hand:

1
2
3
4
hand_name = "HAND1"
deck = [("3", "hearts")]
player_hand = {"7": ["clubs", "spades"]}
opponent_hand = {"7": ["hearts", "diamonds"]}
Hinweis (nur bei Bedarf): Zweite Eingabe

Der Spieler fragt seinen Gegenspieler nach einem Rang, den der Gegenspieler nicht besitzt.

1
2
3
4
5
hand_name = "HAND1"
deck = [("5", "spades")]
player_hand = {"10": ["diamonds"],
               "K": ["spades"]}
opponent_hand = {"Q": ["clubs"]}
Hinweis (nur bei Bedarf): Dritte Eingabe

Betrachten Sie die folgende Situation kurz vor dem Ende des Spiels. Die Variablen sind wie folgt belegt:

1
2
3
hand1 = {}
hand2 = {"4": ["diamonds", "clubs"]} 
deck = [("4", "hearts"), ("4", "spades")]

Das Programm befindet sich genau vor Zeile 88 und wird als Nächstes die while-Schleife iterieren. Wird das Programm ordnungsgemäß beendet?

  • Defekt gefunden? Prima. Dann jetzt bitte in Defekte-bei-Variablen.py korrigieren.
  • Machen sie einen Commit Defekte-bei-Variablen.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.

Im Aufruf found = check_card(hand_name, OPPONENT_HAND, rank_to_check, PLAYER_HAND) in Zeile 61 wurden die zwei hier zur Verdeutlichung groß geschriebenen Parameter vertauscht. Der Fix ist einfach, man muss nur die beiden Parameter tauschen: found = check_card(hand_name, PLAYER_HAND, rank_to_check, OPPONENT_HAND).