Angenommen, Sie haben keinen Debugger zur Verfügung. Was wäre ein effektiver Ansatz zum Debuggen von Code, der (wie erwartet) nicht funktioniert?
Angenommen, Sie haben keinen Debugger zur Verfügung. Was wäre ein effektiver Ansatz zum Debuggen von Code, der (wie erwartet) nicht funktioniert?
Antworten:
Es gibt eine Reihe von Techniken:
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".
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.
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.
Ä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.
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.
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.
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.
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.
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.
Nachdem die anderen Antworten fehlgeschlagen sind , wird immer eine binäre Suche durchgeführt :
Hinweis: Diese Technik funktioniert natürlich nur, wenn Sie das Problem zuverlässig reproduzieren können.
"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.
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.
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.