Die Community zu .NET und Classic VB.
Menü

Debugging - Was ist das?

 von 

Einleitung 

Debugging bedeutet, Fehler in einer Anwendung auszubessern. In dieser Kolumne werden einige Möglichkeiten vorgestellt, die Visual Basic dazu bereithält.

Der Begriff "Debugging" kommt aus der Zeit, als Computer noch aus sehr vielen Röhren bestanden. Dummerweise haben sich Wanzen zwischen den warmen Röhren wohlgefühlt. Manche Wanzen haben schon mal einen Kurzschluss verursacht - und somit einen Fehler. Debugging heißt also im ursprünglichen Sinne: entwanzen.

Symbolleiste "Debuggen"  

Von Visual Basic wird die Symbolleiste "Debuggen" zur Verfügung gestellt, in der die wichtigen Punkte zum Debuggen einer Anwendung gesammelt zu finden sind. Diese Symbolleiste kann aktiviert werden, indem ein Rechtsklick mit der Maus auf einen freien Bereich der Symbolleisten ausgeführt und die entsprechende Symbolleiste ausgewählt wird. Nachfolgend werden die einzelnen Funktionen kurz angesprochen.

Starten, Unterbrechen, Beenden  

Dies sind die drei Buttons, die wohl am häufigsten benutzt werden und sich deshalb neben der Debuggen-Symbolleiste auch in der Standard-Symbolleiste befinden. Mit ihnen kann man das Programm starten (Taste F5; der Quelltext wird interpretiert und abgearbeitet), unterbrechen (Tastenkombination STRG+Pause; falls möglich wird die Zeile markiert, in der sich das Programm gerade befindet) oder beenden (siehe auch FAQ 0013: Eigene Anwendung beenden).
Die meisten Funktionen in dieser Symbolleiste sind nur dann sinnvoll, wenn die Anwendung gestartet und die Ausführung unterbrochen wurde.

Haltepunkt  

Haltepunkte sind Markierungen (im Quelltext farblich hervorgehoben, im Kompilat eine bestimmte Befehlskombination), die dem Interpreter (in der IDE) oder dem ausführenden Betriebssystem (kompilierte Version) mitteilen, dass die Ausführung erst einmal unterbrochen (nicht beendet) wird. Haltepunkte kann man auch mit der Taste F9 setzen oder entfernen.
Der Sinn der Haltepunkte im Quelltext besteht darin, Quelltext selbst zu prüfen (meistens Variablen), zu ändern oder manuell in den weiteren Ablauf einzugreifen.

Wird das Projekt geschlossen, in dem Haltepunkte gesetzt wurden, werden diese Haltepunkte nicht gespeichert. Beim nächsten Öffnen des Projektes muss man die Haltepunkte erneut setzen.
Abhilfe kann der Befehl Stop schaffen. Leider hat dieser Befehl einen Nachteil: Wird er nicht aus dem Quelltext entfernt, bevor der Quelltext kompiliert wird, erhält der Anwender des Kompilats die Fehlermeldung "Stop-Anweisung aufgetreten" und die Anwendung wird beendet.
Aber das kann man umgehen, indem man Debug.Assert verwendet. Breakpoints speichern [Tipp 0475] zeigt ein Beispiel, ein weiteres findet sich bei www.aboutvb.de.

Einzelschritt, Prozedurschritt und Prozedur abschließen  

Mit "Einzelschritt" (Taste F8) wird die Zeile ausgeführt, an der sich das Programm momentan befindet (normalerweise gelb markiert). Dieser Befehl ist vor allem für das Debugging einer Prozedur gedacht, in der Fehler vermutet werden. Dabei springt der Interpreter auch in die Prozeduren hinein, bei denen der Quelltext vorhanden ist (z. B. eigene Prozeduren).

"Prozedurschritt" (Tastenkombination Shift+F8) hat eine ähnliche Wirkung wie "Einzelschritt": Ist der nächste zu verarbeitende Befehl eine eigene Prozedur, springt der Interpreter nicht in diese Prozedur, sondern ruft sie auf, als ob der Quelltext nicht bekannt wäre. Dieses Kommando ist vor allem dann sinnvoll, wenn man sich sicher ist, dass die aufzurufenden Prozeduren fehlerfrei arbeiten und man deren Quelltexte nicht durchlaufen möchte.

"Prozedur abschließen" (Tastenkombination STRG+Shift+F8) schließlich hat zur Folge, dass der Rest der aktuell angezeigten Prozedur verarbeitet wird und der Interpreter beim nächsten Befehl nach Aufruf der aktuellen Prozedur anhält.

Wird eine Prozedur verlassen und liegt von dieser aufrufenden Prozedur kein Quelltext vor (z. B. bei Aufruf eines Ereignisses), unterbricht der Interpreter erst dann, wenn wieder Quelltext angezeigt werden kann.

Lokalfenster  

Mit diesem Kommando wird das sogenannte Lokal-Fenster aufgerufen, in welchem im Unterbrechungsmodus alle verwendeten Variablen mit ihren jeweiligen Werten angezeigt werden. Bei Datenfeldern und Datentypen können damit auch die einzelnen Elemente angezeigt werden. Wird eine fiktive Variable names Pos vom Typ PointAPI aufgeklappt, wird die Variable um ihre Elemente erweitert. Der Entwickler sieht dann die Variablen Pos.X und Pos.Y.

Direktfenster  

Das Direktfenster (Tastenkombination STRG+G) wird wahrscheinlich am häufigsten von diesen drei Fenstern benutzt, da man darin eine Prozedur oder etwas anderes testen kann, ohne ein Programm auszuführen. Es erwartet grundsätzlich normalen VB-Code, wobei jedoch einige kleine Änderungen vorhanden sind:

  • Es ist keine Variablendeklaration möglich oder erforderlich. Wird im Unterbrechugsmodus eine Variable aus der laufenden Anwendung verwendet, so ist sie vom in der Anwendung (hoffentlich) deklarierten Datentyp. Wurde die Anwendung nicht gestartet oder wird ein Variablenname verwendet, der in der Anwendung nicht deklariert ist, so ist die Variable vom Datentyp Variant.
  • Option Explicit hat im Direktfenster keine Wirkung. Schreibfehler bei verwendeten Variablen bewirken, dass der Interpreter diese Schreibfehler als verschiedene Variable erkennt.
  • Zur Anzeige von Variableninhalten, Funktionsrückgaben oder Ausdrücken kann der Befehl Print oder ? verwendet werden.
? exp(1)
 2,71828182845905

Listing 1: Beispiel für Print bzw. ?

  • Der Code wird mit einem Enter ausgeführt, die einzelnen Zeilen mehrzeiliger Anweisungen müssen mit Ausnahme der letzten Zeile mit einem Unterstrich abgeschlossen werden. Mehrere Befehle müssen durch jeweils einen Doppelpunkt getrennt werden.
For bla = 1 To 10 : _
? bla; : _
Next
 1  2  3  4  5  6  7  8  9  10

Listing 2: Beispiel für mehrzeilige Befehle

Überwachungsfenster  

Mit dem Überwachungsfenster ist es möglich, wie der Name schon sagt, ein Programm während der Ausführung zu überwachen. Um eine Überwachung einzufügen, klickt man mit der rechten Maustaste auf das Fenster und wählt den Menüpunkt "Überwachung hinzufügen". Damit erscheint ein Fenster, in welchem einige Einstellungen für diese Überwachung gemacht werden können:

  • Ausdruck : In dieses Textfeld kann man die zu überwachende Variable bzw. den Code eingeben. So ist es beispielsweise möglich, den Wert einer If-Anweisung zu überwachen, indem man schreibt i < 100.
  • Kontext : In diesem Rahmen kann man festlegen, wo sich die Überwachung befindet (in welchem Projekt, in welchem Modul und in welcher Prozedur).
  • Art der Überwachung : Hier kann bestimmt werden, was die Überwachung tun soll: Mit "Überwachungsausdruck" wird der Wert der Variablen im Unterbrechungsmodus direkt angezeigt, mit "Unterbrechen wenn der Wert True ist" wird das Programm unterbrochen, sobald die Bedingung im Ausdruck-Textfeld erfüllt, bzw. die Variable <> 0 ist und mit "Unterbrechen wenn Wert geändert wurde" wird die Ausführung unterbrochen, sobald sich der Zustand einer Bedingung bzw. der Wert einer Variablen geändert hat.

In Codemodulen kann man Variablen oder Objekteigenschaften direkt in das Überwachungsfenster holen, indem man mit der rechten Maustaste darauf klickt und den entsprechenden Punkt im Kontextmenü wählt.

Aktuellen Wert anzeigen  

Mit "aktuellen Wert anzeigen" (Tastenkombination Shift+F9) wird der Wert eines markierten Ausdrucks (also auch einer Bedingung) angezeigt. Um im Unterbrechungsmodus ganz einfach den Wert einer Variablen herauszufinden kann man den Cursor eine Weile darauf verweilen lassen, wodurch der Wert als Tooltip angezeigt wird.

Aufrufliste  

Mit diesem Kommando (Tastenkombination STRG+L) wird ein Fenster geöffnet, in dem der Weg der Prozedur-Aufrufe zur aktuellen Prozedur dargestellt wird.

Projekt1.frmMain.DrawPicture (markiert)
Projekt1.frmMain.Form_Load

Listing 3: Beispiel für eine Aufrufliste

Das heißt, dass sich das Programm momentan in der Prozedur "DrawPicture()" im Formular "frmMain" des Projekts "Projekt1" befindet, die von der Prozedur "Form_Load" aufgerufen wurde.

Fehlerabsicherung im Code  

Selbstverständlich ist es kaum möglich, dass in einem Programm nie Fehler auftreten, man denke dazu nur an das CommonDialog-Steuerelement, das bei einem Klick auf "Abbrechen" automatisch einen Fehler auslöst, um zu signalisieren, dass der Anwender auf die Schaltfläche "Abbrechen" oder die Taste Escape gedrückt hat. Da ist es nicht wirklich gewollt, dass eine Fehlermeldung erscheint und das Programm abgebrochen wird. Und genau dazu gibt es in Visual Basic einige Kommandos, mit denen sich sogenannte Laufzeitfehler (Runtime-Errors) vermeiden lassen:

Option Explicit

Wenn in einem Code-Modul die Anweisung Option Explicit steht, so wird eine explizite Variablendeklaration erzwungen. Das heißt, dass der Compiler keine Variablen zulässt, die nicht deklariert wurden. Fehlt eine Variablendeklaration, so lässt sich das Projekt nicht kompilieren. Um diese Anweisung automatisch in jedem neuen Code-Modul hinzuzufügen, gibt es unter "Extras" -> "Optionen" das Kontrollkästchen "Variablendeklaration erforderlich".
"Option Explicit" schützt nicht vor Schreibfehlern, diese Einstellung sorgt aber dafür, dass Schreibfehler auffallen, bevor ein unerwartetes Ergebnis auftritt.
Neben "Explicit" gibt es noch einige weitere Zusätze für die "Option"-Anweisung, die hier aber nicht weiter behandelt werden sollen.

On Error ...

Das Kommando On Error ..., Fehler abzufangen, ohne dass die Anwendung unterbrochen wird.

On Error Resume Next

Mit diesem Kommando werden auftretende Fehler ignoriert und die Anwendung beim nächsten Befehl, der nach auf den fehlerverursachenden Befehl folgt, fortgesetzt. Der Entwickler ist selbst dafür verantwortlich, auf den aufgetretenen Fehler entsprechend zu reagieren.

On Error Resume Next

Err.Raise CInt(20 * Rnd) 

If (Err.Number = 0) Then
    Debug.Print "Kein Fehler"
ElseIf (Err.Number < 10) Then
    Debug.Print "Ein Fehlercode zwischen 1 und 9"
Else
    Debug.Print "Ein Fehlercode größer gleich 10"
End If

Err.Clear

Listing 4: Beispiel für 'On Error Resume Next'

On Error Goto [Sprungmarke]

Tritt in der aktuellen Prozedur ein Fehler auf, so wird vom fehlerhaften Befehl zur ersten Befehl nach der Sprungmarke verzweigt. Ein Beispiel dafür wird später gezeigt.

On Error Goto 0

Dies ist ein Sonderfall von "On Error Goto [Sprungmarke]". Mit diesem Kommando wird die in der Prozedur zuletzt festgelegte Fehlerbehandlung deaktiviert. In der IDE hat dies zur Folge, dass VB den Fehler meldet und die Ausführung stoppt. Wurde das Kompilat des Projektes ausgeführt und es tritt nach diesem Kommando ein Fehler auf, so wird die Anwendung beendet.

On Error Goto -1

Auch dieses Kommando ist ein Sonderfall von "On Error Goto [Sprungmarke]". Tritt nämlich ein Fehler im Fehlerbehandlungsblock auf, dann wird die Anwendung unterbrochen. In der IDE erscheint der Dialog mit der Fehlermeldung, das Kompilat wird mit der Fehlermeldung beendet. "On Error Goto 0" kann da nichts ausrichten, es wirkt nicht.
Mit "On Error Goto -1" kann man die vorgegebene Fehlerbehandlung für die Fehlerbehandlung außer Kraft setzen und eine Fehlerbehandlung für die Fehlerbehandlung implementieren. Ein Beispiel dazu gibt es bei www.aboutvb.de.

Das Err-Objekt

Mit dem Err-Objekt können Eigenschaften wie Fehlernummer, -beschreibung usw. gelesen, bzw. Fehler ausgelöst werden.

  • Number : Mit Err.Number (oder einfach "Err", da "Number" die Standardeigenschaft ist), kann die Fehlernummer eines Fehlers gelesen werden. Der Zahlwert Null bedeutet, dass kein Fehler aufgetreten ist.
  • Description : In der Eigenschaft Description befindet sich eine Zeichenkette mit der Beschreibung des zuletzt aufgetretenen Fehlers. Ist Err.Number gleich Null, enthält Err.Description eine Zeichenkette der Länge Null.
  • Source : Die Eigenschaft Source gibt den Namen des fehlerhaften Objekts zurück.
  • Clear : Mit der Methode Clear werden die aktuellen Fehlerinformationen aus dem Err-Objekt entfernt.
  • Raise : Mit Err.Raise kann man manuell einen Fehler auslösen.

Daneben gibt es noch drei weitere Eigenschaften, auf die wir hier jetzt aber nicht eingehen wollen.

Resume [...]

Mit Resume [...] kann man festlegen, wie die Arbeit nach der Fehlerbehandlung wieder aufgenommen werden soll. Außerdem wird das Err-Objekt zurückgesetzt.

  • Resume : Die Anwendung wird ab (und inklusive) dem Befehl fortgesetzt, der den Fehler ausgelöst hat. Dies ist z. B. bei Zugriffen auf Wechselmedien sinnvoll, um zu prüfen, ob ein Medium eingelegt wurde.
  • Resume Next : Die Anwendung wird nach dem Befehl fortgesetzt, der den Fehler ausgelöst hat.
  • Resume [Sprungmarke] : Die Anwendung wird beim ersten Befehl nach der angegebenen Sprungmarke fortgesetzt.

In Verbindung mit "On Error Goto [Sprungmarke]" wird die Fehlerbehandlung meist an das Ende einer Prozedur gestellt. Dabei wird Resume oft weggelassen, weil die Prozedur nach der Fehlerbehandlung sowieso beendet ist. Dies ist aber so nicht korrekt, da die Fehlerbehandlung nicht abgeschlossen wurde! Im Programmablauf macht es keinen Unterschied, es ist aber nicht sauber.

Beispiel für eine Fehlerabsicherung

Public Sub Main()
    On Error Goto Fehler
    
    Debug.Print 1 / 0
    
    Call MsgBox("Weiter geht es", vbInformation)
    
Ende:
    Call MsgBox("Schluss mit lustig", vbInformation)
    
    Exit Sub

Fehler:
    Select Case MsgBox("Fehler: " & vbNewLine & Err.Description & _
                       vbNewLine & "Was nun?", vbAbortRetryIgnore Or _
                       vbCritical)
                       
        Case vbAbort
            Resume Ende
            
        Case vbRetry
            Resume
            
        Case vbIgnore
            Resume Next
        
    End Select
End Sub

Listing 5: Beispiel für manuelle Fehlerbehandlung

Compiler-Direktiven  

Compiler-Direktiven sind Anweisungen, die dem Compiler sagen, wie er sich zu verhalten hat. Diese haben zwar nicht unmittelbar etwas mit Debugging zu tun, können aber recht hilfreich sein.

Verwendet werden diese Direktiven oft, um Quelltext-Passagen auszukommentieren oder zusätzliche Anweisungen (z. B. für Logdateien) einzubinden.

#Const

Mit #Const kann man sich ein Symbol erstellen, welches der Compiler beim nächsten Lauf beachtet. Das bekannteste Symbol ist Win32, welches bereits durch Visual basic zur Verfügung gestellt wird und anzeigt, ob es sich um eine 32-Bit- (oder höher) oder um ein 16-Bit-Umgebung handelt.

#If...Then...#Else

Der #If...Then...#Else-Block kann benutzt werden, um ein Symbol zu testen und entsprechende Anweisung einzukompilieren oder aus dem Kompilat herauszuhalten.
Daneben kann es auch zum Auskommentieren großer Quelltext-Passagen verwendet werden.

Option Explicit

#Const MitHinein = True

Public Sub Main
    Debug.Print "Dieser Text ist immer zu sehen"

    #If MitHinein Then
         Debug.Print "Dieser Text ist nur zu sehen, wenn " & _
                     "'MitHinein' entsprechend gesetzt wurde."
    #End If

    #If False Then
         Debug.Print "Dieser Text ist nie zu sehen."
    #End If

    Debug.Print "Das war es schon."
End Sub

Listing 6: Beispiel für Compiler-Direktiven

Nachwort  

Wie Sie sehen, ist es mit Visual Basic relativ einfach und effizient möglich, eine Anwendungen zu debuggen. Ich wage sogar zu behaupten, dass es keine Sprache gibt, in der das Debugging einfacher ist.
Deshalb noch einen Rat: Testen Sie Ihre Anwendung so häufig wie möglich, denn Fehler können sich überall einschleichen, und versuchen Sie sie zu beheben!

Mit freundlichen Grüßen
Philipp Burch

Dem Rat von Philipp Burch können wir Administratoren von ActiveVB uns nur anschließen. Vor allem bei den Ausschreibungen erleben wir häufig, dass Einsendungen gar nicht laufen oder Fehler verursachen, die den Ablauf massiv stören.

Helge Rex
Im Namen von ActiveVB

Ihre Meinung  

Falls Sie Fragen zu diesem Artikel haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.

Archivierte Nutzerkommentare 

Klicken Sie diesen Text an, wenn Sie die 4 archivierten Kommentare ansehen möchten.
Diese stammen noch von der Zeit, als es noch keine direkte Forenunterstützung für Fragen und Kommentare zu einzelnen Artikeln gab.
Aus Gründen der Vollständigkeit können Sie sich die ausgeblendeten Kommentare zu diesem Artikel aber gerne weiterhin ansehen.

Kommentar von Thomas Bonhoeffer am 09.09.2007 um 12:30

Gibt es eine allgemeinzugängliche Liste der Fehlernummern mit hilfreichen Erklärungen? Der mit der Fehlermeldung zusammen ausgegebene Kommentar ist meist zu einsilbig, um brauchbar zu sein!
Ich arbeite an einem Makro für Word.
Vielen Dank für Ihre Hilfsbereitschaft!
ThB

Kommentar von am 06.12.2005 um 18:29

Jede Zeile eine Nummer geben ist sehr sinnvoll, aber nicht unbedingt erforderlich. Man kann nur bestimmte Zeile nummerieren, dann weiss man natürlich durch die ERL-Funktion nur, in welchem "Zone" der Fehler ausgelöst ist. In diesem Fall zeigt die ERL-Funktion die Zeilennummer an, welche überhaupt eine Nummer hat, bevor ein Fehler aufgetreten ist. (lezte ausgeführte "und" nummerierte Zeile)

Beispiel:
100: Anweisung1
Anweisung2
Anweisung3
200: Anweisung4
Anweisung5
Anweisung6
300: Anweisung7

wenn zB in der Zeile 6 ein Fehler ausgelöst, zeigt ERL die Zeilennummer 200


Kommentar von Maschek am 21.08.2005 um 13:47

Wie kann ich den Debugger ausschalten? Die Programme funktionieren einwandfrei, und auf einmal kommt das Fenster von Debugger und ween ich ihm schliesse kommt wieder und wieder 12x bis er werschwindet und später das selbe Spiel
danke

Kommentar von password am 13.11.2004 um 20:23

Habe ich da etwas übersehen, oder wird hier wirklich die "Erl"-Funktion nicht erwähnt?

Findet man zwar nirgends, gibts aber trotzdem:
Wer vor jede Zeile eine Zeilennummer stellt, also die Zeilen nummeriert, kann im Fehlerfall mit "Erl" auf diese Nummer zugreifen. Sehr sinnvoll beim erstellen von Logs (Funktioniert auch kompiliert soweit ich weiss).

Wenn die Zeilen nicht nummeriert sind, gibt "Erl" natürlich die 0 zurück.

Ansonsten doch recht Hilfreiche Kolumne.