Wie kann man manuelles Debuggen effektiv durchführen? [geschlossen]

8

Angenommen, Sie haben keinen Debugger zur Verfügung. Was wäre ein effektiver Ansatz zum Debuggen von Code, der (wie erwartet) nicht funktioniert?

Anto
quelle
20
"Das effektivste Debugging-Tool ist immer noch sorgfältiges Nachdenken, gepaart mit vernünftig platzierten Druckanweisungen." - Brian Kernighan
3
Stimme voll und ganz zu. Das leistungsstärkste Debugging-Tool sind weiterhin Druckanweisungen. Ein weiterer Rat ist, den eigentlichen Code und nicht die Kommentare zu debuggen.
Romeroqj
4
@jromero: Ich würde nicht sagen, dass Print-Anweisungen die "mächtigsten" sind. Am weitesten verbreitet und einfach zu bedienen, sicher.
Whatsisname
1
Dieses Kernighan-Zitat lässt mich wünschen, ich könnte Kommentare ablehnen. Das Debuggen von Druckanweisungen ist ein Werkzeug der letzten Instanz.
Mason Wheeler
2
@Mason: Bei der Frage wird davon ausgegangen, dass keine Debugger verfügbar sind (die "echte" Methode zum Verfolgen der Ausführung ist also weg). Was würden Sie also noch zum Verfolgen der Ausführung verwenden?

Antworten:

7

Es gibt eine Reihe von Techniken:

  1. Protokollierung. Wenn Sie keinen Dateizugriff haben, melden Sie sich bei einer seriellen Konsole oder einem anderen verfügbaren Ausgabegerät an. Es ist eine gute Idee, Ihren Code immer unter Berücksichtigung der Protokollierung zu schreiben, um eine bedingte Kompilierung für verschiedene Protokollierungsstufen zu ermöglichen, von "keine" bis "überfüllt".

  2. Schneiden Sie es ab. Schließen Sie die Teile Ihres Codes nacheinander um einen vermuteten Fehlerpunkt aus und analysieren Sie, was Sie getan haben, wenn der Fehler verschwindet.

  3. Behauptungen oder Verträge. Es ist eine gute Idee, Ihren Code von Anfang an mit Asserts zu füllen. Sie helfen nicht nur beim Debuggen, sondern dienen auch als zusätzliche Dokumentation für Ihren Code.

  4. Ähnlich wie bei 2. - Variieren Sie Ihre Eingabe und mischen Sie den Code neu, es sei denn, der Fehler verschwindet oder ändert sein Verhalten. Es ist oft eine gute Idee, mit verschiedenen Optimierungsstufen zu spielen (wenn Sie in C oder C ++ codieren), da zeigerbezogene Fehler in ihrem Verhalten äußerst volatil sind.

  5. Eine automatisierte Version von 3. - Verwenden Sie den instrumentierten Code, z. B. führen Sie die Binärdatei unter valgrind aus.

Und natürlich gibt es viel mehr Tools und Tricks, abhängig von der Art Ihrer Ausführungsumgebung und Ihrem Code.

SK-Logik
quelle
1
-1: Kann nicht zustimmen 4). Sie scheinen es als Lösung für den Fehler vorzuschlagen? Wenn ja, ist das böse und Sie haben ein Problem. Alles was es tut ist, die Symptome verschwinden zu lassen - es ist immer noch da, du hast es gerade versteckt ... für jetzt ...
mattnz
@ Mattnz, etwas dagegen zu erklären? Ich schlage diesen Ansatz als Alternative zu einem interaktiven Debugger vor. Was wiederum nur Symptome hervorhebt, nicht die tatsächlichen Ursachen. 4) ist ein Weg, um ein Problem zu identifizieren, keine Lösung. Tatsächlich ist mein Ansatz in den meisten Fällen viel besser als das Debuggen, da er eine bessere Abdeckung bietet. Es ist also wahrscheinlich, dass Sie nicht verstanden haben, was ich vorschlage. Versuchen Sie es erneut.
SK-Logik
Ich habe gesehen, dass Entwickler die in Schritt 4 beschriebenen Aktionen verwenden, um einen Fehler mithilfe von "Ich könnte ihn verursachen, jetzt kann ich nicht, ich habe ihn behoben - versenden" zu "beheben". Ihr Beitrag schlägt vor, dass diese Methode gültige Fehlerkorrekturen liefert.
Mattnz
@mattnz, nein, so etwas schlägt es nicht vor. Ich beschreibe einen Weg, einen Fehler zu untersuchen , nicht ihn zu beheben. Debugger beheben keine Fehler, und die Frage betraf eine Alternative zu einem Debugger.
SK-Logik
18

Holen Sie sich einen Kollegen und erklären Sie das Problem ausführlich, während Sie Schritt für Schritt über den lästigen Code gehen.

Häufig macht das Erklären entweder Ihrem Kollegen oder sich selbst klar.


quelle
12
+1 Und wenn Sie keinen Kollegen finden, verwenden Sie einen Teddybär .
Péter Török
4
@ Péter Török: Ich weiß nicht ... Teddybären neigen dazu, mit ihren kalten, toten Knopfaugen zurückzustarren ... alles zu ignorieren, was Sie sagen, und Sie fühlen sich wertlos, winzig, unbedeutend ... Es macht das Debuggen mit einem Teddybär ... . schwer.
FrustratedWithFormsDesigner
1
@FrustratedWithFormsDesigner, siehe den Link, den ich hinzugefügt habe.
Péter Török
2
@Peter, ich befürchte, dass ein Regal voller Teddybären, das Kollegen zum Debuggen bereithalten können, bei Kunden einen falschen Eindruck hinterlassen könnte.
1
@FrustratedWithFormsDesigner: Und wie unterscheidet sich das von vielen Kollegen
Mattnz
3

Gibt es ein Protokollierungssystem zur Verwaltung der Programmausgabe? Gibt es mindestens eine Konsole zum Drucken oder Dateien, in die Sie schreiben können? Mit Konsolen oder Protokolldateien können Sie ohne Debugger debuggen. Geben Sie dem Programm eine Eingabe, damit Sie wissen, wie die Ausgabe aussehen soll, überprüfen Sie, ob die Ausgabe funktioniert, und stellen Sie sicher, dass Ihre Protokollierung viele Details des Prozesses enthält. Versuchen Sie es dann mit einem Eingang, der den falschen Ausgang liefert. Hoffentlich geben Ihnen die Protokolle einen detaillierten Überblick darüber, was schief gelaufen ist.

FrustratedWithFormsDesigner
quelle
Sie können alles verwenden, ABER einen Debugger der Art von GDB oder was Sie in Ihrer IDE finden würden
Anto
@Anto: Das klingt wie eine Zeile aus einer Hausaufgabe, aber in diesem Fall verwendet die Protokollierung in einer Datei oder der Konsole keinen Debugger wie "gdb oder was Sie in Ihrer IDE finden". Mit gdb und anderen Debuggern können Sie Ihren Code zeilenweise durchlaufen und die Werte von Variablen während der Programmausführung überprüfen . Debugging-by-log bedeutet , dass Sie eine Spur benutzen (in der Datei oder Konsole) der Ausführung des Programms nach dem sie beendet wurde , und herauszufinden , was passiert ist .
FrustratedWithFormsDesigner
Ich weiß daher, was Sie empfohlen haben, ist erlaubt. Nein, dies ist keine Hausaufgabe. Ich bin in der Mittelschule und wir haben überhaupt keine Programmierung / cs.
Anto
2
@Anto: Ok. Der einzige Nachteil dieser Methode ist, wenn Sie versuchen, ein Programm mit Synchronisierungsproblemen zu debuggen. Wenn es sich beispielsweise um ein IPC-Problem handelt, können Ihre Druck- / Protokollanweisungen die Geschwindigkeit beeinflussen, mit der die Prozesse miteinander sprechen, und das Aktivieren oder Deaktivieren der Protokollierung kann sich darauf auswirken, ob das Problem reproduziert wird (in diesem Fall müssen Sie sich wirklich für @ entscheiden Thorbjørn Ravn Andersens Rat). In einigen Fällen kann die Protokollierung die Leistung erheblich beeinträchtigen, normalerweise jedoch nur, wenn bei der Verarbeitung sehr großer Datenmengen viel in einem großen System protokolliert wird. Dies ist jedoch zu beachten.
FrustratedWithFormsDesigner
3

Hängt davon ab. Hat es vorher funktioniert? Wenn der Code, der früher funktionierte, plötzlich kaputt ging, sollten Sie die letzten Änderungen sehr sorgfältig untersuchen.

Dima
quelle
2
Dieser Ansatz sollte nicht unterschätzt werden: Der Revisionsverlauf ist eine hervorragende Möglichkeit, Fehler im Code zu identifizieren, die zuvor funktioniert haben - auch beim Hinzufügen neuer Funktionen.
edA-qa mort-ora-y
2
Ich habe gehört, dass 'git bisect' diese Aufgabe etwas automatisiert. Ich muss es aber noch versuchen.
Clayton Stanley
2

1) Tun Sie alles, was Sie tun müssen, um den Fehler zu 100% reproduzierbar oder so nahe wie möglich an 100% zu machen

2) Verfolgen Sie das Problem mithilfe von printf () oder einer anderen Protokollierungsfunktion. Dies ist jedoch eine Kunst und hängt von der Art des Fehlers ab.

Wenn Sie absolut keine Ahnung haben, wo sich der Fehler befindet, aber beispielsweise wissen, dass eine Bedingung irgendwann falsch wird (der Status des Programms ist fehlerhaft - nennen wir es isBroken ()), können Sie einen Drilldown / eine Partitionssuche durchführen um den Ort des Problems herauszufinden. Protokollieren Sie beispielsweise den Wert von isBroken () am Anfang am Ende der Hauptmethoden:

void doSomething (void)
{
    printf("START doSomething() : %d\n", isBroken());
    doFoo();
    doBar();
    printf("END doSomething() : %d\n", isBroken());
}

Wenn im Protokoll sehen Sie

START doSomething() : 0
END doSomething() : 1

Sie wissen, dass dort etwas schief gelaufen ist. Sie entfernen also den gesamten anderen Protokollierungscode und probieren diese neue Version aus:

void doSomething (void)
{
    printf("START doSomething() : %d\n", isBroken());
    doFoo();
            printf("AFTER doFoo() : %d\n", isBroken());
    doBar();
    printf("END doSomething() : %d\n", isBroken());
}

Jetzt können Sie dies im Protokoll sehen

START doSomething() : 0
AFTER doFoo() : 0
END doSomething() : 1

Jetzt wissen Sie also, dass doBar () den Fehler auslöst, und Sie können den obigen Vorgang in doBar () wiederholen. Im Idealfall beschränken Sie den Fehler auf eine einzelne Zeile.

Dies kann Ihnen natürlich helfen, die Symptome des Fehlers und nicht die Grundursache aufzudecken. Sie finden beispielsweise einen NULL-Zeiger, der nicht NULL sein sollte, aber warum? Sie können sich dann erneut anmelden, aber nach einem anderen "defekten" Zustand suchen.

Wenn Sie einen Absturz anstelle eines fehlerhaften Zustands haben, ist dieser mehr oder weniger derselbe - die letzte Zeile des Protokolls gibt Ihnen einen Hinweis darauf, wo die Dinge kaputt gehen.

ggambett
quelle
2

Nachdem die anderen Antworten fehlgeschlagen sind , wird immer eine binäre Suche durchgeführt :

  1. Beseitigen Sie einen bestimmten Teil (vorzugsweise die Hälfte) der möglichen Ursachen (Codezeilen, Überarbeitungen , Eingaben usw.)
  2. Versuchen Sie, das Problem erneut zu reproduzieren.
  3. Wenn das Problem weiterhin besteht: Fahren Sie mit Schritt 1 fort.
  4. Wenn Sie nur noch eine Ursache haben (Codezeile, Revision, Eingabe usw.): Hurra! Prozedur beenden.
  5. Andernfalls: Setzen Sie Schritt 1 zurück und entfernen Sie nun die andere Hälfte.
  6. Gehen Sie zurück zu Schritt 2.

Hinweis: Diese Technik funktioniert natürlich nur, wenn Sie das Problem zuverlässig reproduzieren können.

Jeroen
quelle
1. Beseitigen Sie die Hälfte der möglichen Ursachen. Problem verschwindet. 2. Stellen Sie diese Hälfte wieder her und beseitigen Sie die andere. Problem verschwindet. 3. Beseitigen Sie nur wenige mögliche Ursachen. Das Problem verschwindet, wenn Sie beliebige 20% davon eliminieren. 4. Untersuchen Sie die Leistung, den zugrunde liegenden Motor und den Kreisverkehr. 5. Panik.
SF.
Mit großen, freundlichen Briefen.
Jeroen
0

"Das effektivste Debugging-Tool ist immer noch sorgfältiges Überlegen, gepaart mit vernünftig platzierten Druckanweisungen." - Brian Kernighan 'Zu seiner Zeit mag es wahr gewesen sein! Die effektivste Methode ist es, sich die Unit-Tests anzusehen, aber ich vermute, Sie haben keine.


quelle
Ich habe keine Komponententests, da ich kein bestimmtes Projekt oder keinen bestimmten Code habe. Ich
Anto
Warum um alles in der Welt würden Sie diese Antwort ablehnen? Hören Sie dann auf, im Dunkeln herumzuspielen, und testen Sie sie dann.
Ich war es nicht, der herabgestimmt hat, also weine mit jemand anderem
Anto
2
Unit-Tests ersetzen nicht das Debuggen, sondern helfen lediglich, die Fehler zu unterteilen und einzuschränken. Dies vereinfacht das Debuggen, wenn ein Fehler in einem codierten Komponententest auftritt. IME, die meisten kniffligen Fehler treten in Komponenteninteraktionen auf (schwer zu testen) und werden in Basher-Testsuiten im Regressionsstil viel häufiger entdeckt.
Clayton Stanley
-1) Wie behebt man Code, der durch einen fehlerhaften Komponententest identifiziert wurde - Sie debuggen ihn ...... Komponententests erkennen Fehler, Debugger und Debugging werden verwendet, um den Fehler zu beheben.
Mattnz
0

Es kommt auf den Fehler an.

Wenn es sich bei dem Fehler um die Art von "Warum macht der Code A" handelt, kann es hilfreich sein, Ihr eigenes Verständnis des Codes zu testen, der den Speicherort von "Code macht A" umgibt. Führen Sie neuen Code ein, von dem Sie erwarten, dass er neue Fehler generiert (dieser Code sollte B ausführen, dies sollte C bewirken). Normalerweise finde ich schnell neuen Code, der Verhalten erzeugt, das ich nicht erwarte. Dann warte ich geduldig, während mein Verstand ein aktualisiertes mentales Modell des Codeverhaltens erstellt, so dass die letzte Änderung sinnvoll ist, und dann erklärt diese mentale Modelländerung normalerweise, warum der Code A ausführt.

Der zweite Fall wurde hier ausführlich erörtert. Wenn Sie entweder den Code geerbt haben, kein solides mentales Modell für die Funktionsweise des Codes haben, keine gute Vorstellung von der spezifischen Position des Fehlers haben usw. In diesem Fall Drilldown / Divide-and- Eroberungsmethoden mit print-Anweisungen können funktionieren. Wenn es sich um eine Quellcodeverwaltung handelt, überprüfen Sie unbedingt die letzte Codeänderung.

Clayton Stanley
quelle
0

Erweiterung des "Das effektivste Debugging-Tool ist immer noch sorgfältiges Überlegen, gepaart mit vernünftig platzierten Druckanweisungen."

Versuchen Sie zunächst, den Moment einzugrenzen, in dem der Fehler auftritt. Machen Sie die vom Benutzer beobachtbaren Symptome systembeobachtbar. (Angenommen, einige Zeichenfolgen ändern sich in Kauderwelsch, fügen Sie eine Schleife hinzu, die den Inhalt des Skripts abfragt und Ihr Debug auslöst, wenn es sich ändert.) Wenn der Fehler ein Absturz ist, fügen Sie natürlich die Segfault-Behandlung hinzu.

Versuchen Sie dann, den Thread einzugrenzen, wenn das Problem in einer Umgebung mit mehreren Threads liegt. Geben Sie jedem Thread eine Kennung und sichern Sie sie, wenn der Fehler auftritt.

Sobald Sie den Faden haben, bestreuen Sie den Code des angegebenen Fadens reichlich mit printfs, um die Stelle festzunageln, an der er auftaucht.

Gehen Sie dann zurück zu dem Ort, an dem die eigentliche Aktion stattfindet, die sie erstellt (die destruktive Aktion dauert oft einiges, bevor die beschädigten Daten das Problem auslösen). Untersuchen Sie, welche Strukturen / Variablen in der Nähe des Speichers auftreten, beobachten Sie Schleifen, die sie betreffen, und überprüfen Sie Punkte wo der beschädigte Wert geschrieben wird.

Wenn Sie den Punkt erreicht haben, der das Problem verursacht hat, überlegen Sie sich zweimal, wie das richtige Verhalten aussehen soll, bevor Sie es beheben.

SF.
quelle