"Fehler suchen"-Tutorial

Übersicht BlitzBasic FAQ und Tutorials

Neue Antwort erstellen

Midimaster

Betreff: "Fehler suchen"-Tutorial

BeitragSo, Dez 27, 2009 10:51
Antworten mit Zitat
Benutzer-Profile anzeigen
"Fehler suchen"-Tutorial

Dieses Tutorial soll helfen Fehler in eigenen Programmen zu finden oder vor vorne herein zu vermeiden.

Dabei werden Strategien erklärt, wie der Programmcode zeitsparend und systematisch abgesucht werden kann. Außerdem werden Empfehlungen gegeben, wie der Programmcode vorzubereiten ist, damit er in den Blitz-Foren von Dritten leicht gelesen werden kann.

(Ich bitte alle, sich mit eigenen Einträgen rauszuhalten und dafür lieber den Thread "Kritik an Midimaster-Tutorials" zu nutzen. Dieses Tutorial soll den Anfänger möglichst durch keine Diskussionen irritieren.) Kritik hier hin: Kritik-Thread

Allen Anfängern lege ich wärmstens ans Herz dieses Tutorial durchzuarbeiten. Es spart euch viele Fehler in späteren Programmen. Fragen von Hilfesuchenden bitte ins BB-Beginners-Corner (https://www.blitzforum.de/forum/viewforum.php?f=18), aber auf keinen Fall hier hin posten!

Lektion I: Fehler vermeiden Arrow zur Lektion I

Lektion II: Wie anfangen? Arrow zur Lektion II

Lektion III: Code zerteilen Arrow zur Lektion III

Lektion IV: Fehlersuche Arrow zur Lektion IV

Lektion V: weitere Tipps zur Fehlersuche Arrow zur Lektion IV
  • Zuletzt bearbeitet von Midimaster am Di, Mai 11, 2010 9:45, insgesamt 8-mal bearbeitet

Midimaster

Betreff: Fehler vermeiden

BeitragSo, Dez 27, 2009 11:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Fehler vermeiden

Zunächst wird erklärt, wie der Code aussehen soll, damit möglichst wenig Fehler entstehen. Wenn schon ein Fehler entstanden ist, der sich einfach nicht finden lässt, sollte der Code nach diesen Regeln umgebaut werden, bevor die Fehlersuche beginnt.

Einrücken des Quelltextes

Rücke Deine Zeilen um einen weiteren Tab ein, wenn Dein Programm eine dieser Strukturen benutzt:

Zitat:
Repeat, For, If, Select, OpenFile, OpenStream, etc..



BlitzBasic: [AUSKLAPPEN]
For I=1 To 100
If I>50 Then
Print "Größer"
EndIf
Next


Gewöhne dir beim Schreiben an, gleich das Ende der Struktur zu schreiben, bevor du dich dem Inhalt widmest:
Schritt 1bis 3:
BlitzBasic: [AUSKLAPPEN]
For I=1 To 100
Next
BlitzBasic: [AUSKLAPPEN]
For I=1 To 100
If I>50 Then
EndIf
Next
BlitzBasic: [AUSKLAPPEN]
For I=1 To 100
If I>50 Then
Print "Größer"
EndIf
Next



keine Monster-Zeilen


Nicht alles, was erlaubt ist, ist auch immer sinnvoll. Vermeide Zeilen, in denen mehrere Befehle stehen. Sie bringen sowieso keinen Performance-Vorteil:

(nicht nachmachen)
Zitat:
If StringWidth(Left$(t$,i))+x>=MouseX() Input_mark_x=i:Exit


So geht es richtig:
BlitzBasic: [AUSKLAPPEN]
If StringWidth(Left$(t$,i))+x>=MouseX() Then
Input_mark_x=i
Exit
EndIf

Nur wenn jeder Befehl eine eigene Zeile bekommt, können Dritte ihn schnell erfassen.

Aber nicht nur Zeilen mit mehr Befehlen können zerlegt werden. Teile auch reine Code-Zeilen:

(nicht nachmachen)
Zitat:
Rect StringWidth(Left$(t$,Input_mark_x))+x,y,StringWidth(Mid$(t$,Len(Left$(t$,Input_mark_x)+1),Input_mark_w)),FontHeight(),1

Alles klar? Man ahnt wohl, was hier gecodet wurde, aber würde man auch einen Fehler darin finden?

So wäre es besser:
BlitzBasic: [AUSKLAPPEN]
VonX=StringWidth(Left$(t$,Input_mark_x))
BisX=StringWidth(Mid$(t$,Len(Left$(t$,Input_mark_x)+1),Input_mark_w))
Rect VonX+X,Y,BisX,FontHeight(),1


So geht es richtig:
BlitzBasic: [AUSKLAPPEN]
Teil$ = Left$(t$, Input_mark_x)
VonX% = StringWidth(Teil)
Lang% = Len(Teil)+1
Teil = Mid$(t$,Lang, Input_mark_w)
Bis% = StringWidth(Teil)
Rect Von+X, Y, Bis, FontHeight(), 1


IF Logik und Lesbarkeit


BlitzBasic: [AUSKLAPPEN]
If KeyDown(203) And input_cursor_pos> m+v And msecs>Input_timer+100

lässt sich auch so schreiben:
BlitzBasic: [AUSKLAPPEN]
If KeyDown(203) Then
If input_cursor_pos > (m+v) Then
If msecs > (Input_timer+100) Then
....
EndIf
EndIf
EndIf

Logische AND-Verknüpfungen lassen sich immer in mehrere Zeilen zerlegen. Von der Logik ist beides das Gleiche
Zwei Dingen sind dabei wichtig: Gewöhne dir an immer das Then am Ende der Zeile zu schreiben. Setze Klammern, wenn eine der beiden Bedingungen mehr als ein Ausdruck ist:

BlitzBasic: [AUSKLAPPEN]
If ( (x+dx)<0 ) Or ( (x+dx)>1000 ) Then 

Hierbei bemerktst du dann oft schon im Vorfeld Deine Logikfehler


Sinnvolle Funktions- und Variablennamen

Bildschirm-Koordinaten werden mit X Y und Z beschrieben. Verwende also diese Zeichen nur, wenn es wirklich um Koordinaten geht. Eine Mauskoordinate könnte z.B. MausX heißen, die aktuelle Position PosX, ZSpeed für eine Geschwindigkeit in Z-Richtung, usw....

Kleine Zählvariablen, nur kurz benötigte Variablen heißen gerne i oder j. Verwende diese Namen deshalb nie für bedeutende Variablen im Programm. Einer, der den Code liest, könnte ihre Bedeutung übersehen.

Bedeutende Variablen und Funktionen bekommen komplette Worte oder werden aus mehreren Wortteilen zusammengesetzt, wobei der Wortanfang immer groß geschrieben wird.

Hier verweise ich jetzt einfach einmal auf mein Namens-Tutorial:

https://www.blitzforum.de/foru...050#354014
  • Zuletzt bearbeitet von Midimaster am Mo, Jan 11, 2010 12:32, insgesamt 3-mal bearbeitet

Midimaster

Betreff: Lektion II Wie anfangen?

BeitragMo, Dez 28, 2009 2:37
Antworten mit Zitat
Benutzer-Profile anzeigen
Wie anfangen?

Wenn du ein neues Projekt anfängst stehst du vor lauter unfertigen Einzel-Baustellen. Um das Programm zu testen, müssten sie eigentlich erst alle fertiggestellt sein. Doch bis zu diesem Zeitpunkt hast Du vielleicht bereits 10 Fehler im Code. Und die Suche nach den Fehlern wird zur Qual.

Wir müssen also einen Weg finden, wie wir einzelne Teile bereits im Gesamtprogramm testen können, bevor die anderen Teile überhaupt geschrieben sind. Wie soll das möglich sein?

Wir faken (von engl. "to fake"=schwindeln)!

Diese Tutorial zeigt Dir, wie man nach wenigen Minuten das gesamte Programm bereits testen kann. Wir erklären das am Projekt "Dia-Show". Die Dia-Show soll ein beliebiges Verzeichnis an Bildern einlesen können, daraus eine Playlist erstellen die Bilder laden dann auf dem Bildschirm darstellen. Dazu brauchen wir zunächst diese drei Funktionen:

VerzeichnisEinlesen()
LadeBild()
BildDarstellen()

im Laufe des Projektes werden immer mehr Funktionen dazukommen. Aber wir werden jede Funktion zunächst "faken".

BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
VerzeichnisEinlesen()
Repeat
BildDarstellen()
Until KeyHit(1)
End

Function VerzeichnisEinlesen()
Print "Lese Verzeichnis"
End Function

Function BildDarstellen()
LadeBild()
Print "zeige das Bild "
End Function

Function LadeBild()
Print "Bild geladen"
End Function


Dieses Programm läuft bereits! Teste es. Hier ist der größte Fehler der Beginners: die würden jetzt noch mehr schreiben, bevor sie es das erste Mal testen. Unser Programm kann fast noch nichts: Starten, Leben, Ende. Aber das können wir schon einmal testen.... Wichtig dabei sind die PRINT-Anweisungen. Sie zeigen Dir, welche Funktionen wann aufgerufen werden. Richtige Bilder sehen wir noch nicht, aber wir erhalten stattdessen den Text "Zeige das Bild".

Der fake besteht zunächst darin, die Funktionen völlig leer zu halten. Aber wir sehen jetzt schon zwei Fehler: es werden keine unterschiedlichen Bilder "gezeigt" und der Wechsel aus Laden und Anzeigen erfolgt in einem rasanten Tempo. Ein Timer wird nötig und wir wollen nicht immer das gleiche Bild "sehen":
BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Global DarstellZeit%, BildNummer%
Global AktBildDatei$
....

Function BildDarstellen()
If DarstellZeit > MilliSecs()
DarstellZeit = MilliSecs+1000
BildNummer=BildNummer+1
LadeBild BildNummer
Print "zeige das Bild " + AktBildDatei
EndIf
End Function

Function LadeBild(nr%)
AktBildDatei="C:\test\image" + nr + ".png"
Print "Lade Bild " + AktBildDatei
End Function


Um wirklich ein Bild darzustellen müssten wir es vorher geladen haben. Um es aber laden zu können, müssten wir jetzt auch wissen, wie die Datei heißt, das faken wir einfach.

Wir sind jetzt an einem Punkt, an dem das Programm laufen würde. Sorge zunächst dafür, dass in dem Verzeichnis einige Bilde liegen, die IMAGEx.PNG heißen. "x" ist dabei eine laufende Nummer von 1 bis 9.
Natürlich musst Du den Pfad in der Funktion LadeBild() an deine Gegebenheiten anpassen.
BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Global DarstellZeit%, BildNummer%
Global AktBildDatei$, Bild

VerzeichnisEinlesen()
Repeat
BildDarstellen()
Flip
Until KeyHit(1)
End

Function BildDarstellen()
If DarstellZeit > MilliSecs()
DarstellZeit = MilliSecs+1000
BildNummer=BildNummer+1
LadeBild BildNummer
Print "zeige das Bild " + AktBildDatei
DrawImage Bild,0,0
EndIf
End Function

Function LadeBild(nr%)
AktBildDatei="C:\test\image" + nr + ".png"
Print "Lade Bild " + AktBildDatei
Bild=LoadImage(AktBildDatei)
End Function

Function VerzeichnisEinlesen()
Print "Lese Verzeichnis"
End Function


Entferne die PRINT-Texte auf keinen Fall vor Fertigstellung des gesamten Projektes. Durch sie erfährst Du auch dann was abläuft, wenn einmal wegen eines Fehlers kein Bild gezeigt wird.

Das Prinzip ist jetzt klar. Es wird also immer nur an einer Routine gebastelt, während die anderen Routinen 100% stabil "funktionieren".

Wir fügen jetzt das VerzeichnisLesen() ein. Dazu wird eine Liste der Dateinamen als Array dimensioniert und dann mit den Namen der Dateien eines Verzeichnisses gefüllt:

BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Global DarstellZeit%, BildNummer%
Global AktBildDatei$, Bild
Dim Datei$(999)
....
Function LadeBild(nr%)
AktBildDatei=Datei(Nr)
Print "Lade Bild " + AktBildDatei
Bild=LoadImage(AktBildDatei)
End Function

Function VerzeichnisEinlesen()
Local i%
Print "Lese Verzeichnis"
For i=0 To 9
Datei(i)="C:\test\image" + i + ".png"
Next
End Function


Du siehst: es ist wieder gefaked. wir lesen das Verzeichnis gar nicht wirklich ein. Wir schreiben Code in VerzeichnisEinlesen(), der eine gefakte Liste von Dateinamen erstellt. Irgendwann wirst Du diese Routine durch die Echte ersetzen. Aber schon jetzt kannst Du in den anderen Programmteilen weitere Features testen und Dich dabei 100% auf die Verzeichnis-Routine "verlassen".
  • Zuletzt bearbeitet von Midimaster am Mi, Dez 30, 2009 11:23, insgesamt 5-mal bearbeitet

Midimaster

Betreff: Lektion III: Den Quellcode gliedern

BeitragMo, Dez 28, 2009 2:39
Antworten mit Zitat
Benutzer-Profile anzeigen
Den Quellcode gliedern

Teile Deinen Quellcode optisch ein, indem Du Leerzeilen einfügst. Der Code wird beim Speichern dadurch nicht länger und auch die Performance leidet nicht. Füge nach jedem zusammengehörenden Arbeitsschritt (meist 3 bis 5 Zeilen) solche Leerzeilen ein.

Versuche, manche dieser Arbeitsabschnitte in einer Funktion auszulagern. Versuche es vor allem dann, wenn Du das Gefühl hast, zwei Code-Stellen sehen sich sehr ähnlich.


Hier am Beispiel einer Anfrage aus dem Forum. Der User sucht den Fehler in dieser Funktion:
Zitat:

Function Input$(t$,x,y,size_x,size_y,cursor=False,mark_color=128,maxlength=999999,Lengthtype=0)
' ---------------------------------------Block I
Input_key=GetKey()
If KeyHit(82) input_key=48
If KeyHit(79) input_key=49
If KeyHit(80) input_key=50
If KeyHit(81) input_key=51
If KeyHit(75) input_key=52
If KeyHit(76) input_key=53
If KeyHit(77) input_key=54
If KeyHit(71) input_key=55
If KeyHit(72) input_key=56
If KeyHit(73) input_key=57
If KeyHit(181) input_key=47
If KeyHit(55) input_key=42
If KeyHit(74) input_key=45
If KeyHit(78) input_key=43
If KeyHit(83) input_key=44
If KeyHit(156) input_key=13
'-------------------------------------------------Block II
If KeyDown(203) And input_cursor_pos>0 And msecs>Input_timer+100
input_cursor_pos=input_cursor_pos-1
Input_timer=msecs
Input_key=0
EndIf
If KeyDown(205) And input_cursor_pos<Len(t$) And msecs>Input_timer+100
input_cursor_pos=input_cursor_pos+1
Input_timer=msecs
Input_key=0
EndIf
If KeyDown(211) And msecs>input_timer+100
Input_timer=msecs
Input_key=0
If Input_mark_x>-1
t$=Left$(t$,Input_mark_x)+Mid$(t$,Input_mark_w+input_mark_x+1)
Else
t$=Left$(t$,input_cursor_pos)+Mid$(t$,input_cursor_pos+2)
EndIf
Input_mark_x=-1
EndIf
If KeyDown(14) And Input_cursor_pos>0 And msecs>Input_timer+100
Input_timer=msecs
Input_key=0
If Input_mark_x>-1
t$=Left$(t$,Input_mark_x)+Mid$(t$,Input_mark_w+input_mark_x+1)
Else
t$=Left$(t$,input_cursor_pos-1)+Mid$(t$,input_cursor_pos+1)
input_cursor_pos=input_cursor_pos-1
EndIf
Input_mark_x=-1
EndIf
If (KeyDown(29) Or KeyDown(157)) And KeyHit(47)
input_key=0
tmp$=ReadClipboardText$()
t$=Left$(t$,input_cursor_pos)+tmp$+Mid$(t$,input_cursor_pos+1)
input_cursor_pos=input_cursor_pos+Len(tmp$)
EndIf
If KeyHit(207) input_cursor_pos=Len(t$):Input_key=0
If KeyHit(199) input_cursor_pos=0:Input_key=0

; ------------------------------------------------------Block III
If MouseHit(1)
Input_mark_w=0
Input_mark_x=-1
For i=1 To Len(t$)
If StringWidth(Left$(t$,i))+x>=MouseX() Input_mark_x=i:Exit
Next
Input_cursor_pos=i
EndIf
If MouseDown(1) And Input_mark_x>-1
If MouseX()>=x And MouseX()<=StringWidth(t$)+x
If MouseY()>=y And MouseY()<=FontHeight()+y
; Maus in Textbereich
For i=1 To Len(t$)
If MouseX()<StringWidth(Left$(t$,i))+x Input_mark_w=Len(Mid$(t$,Input_mark_x,i-Input_mark_x)):Exit
Next
EndIf
EndIf
EndIf

If Input_key>=32 ; Eingabe getätigt
t$=Left$(t$,input_cursor_pos)+Chr$(Input_key)+Mid$(t$,input_cursor_pos+1)
input_cursor_pos=input_cursor_pos+1
EndIf


If Len(t$)>maxlength t$=Left$(t$,maxlength)
If cursor
If einf c_width=FontWidth() Else c_width=2
Rect x+StringWidth(Left$(t$,input_cursor_pos)),y-1,c_width,FontHeight()+2,1
EndIf
If Input_mark_x>-1
Color mark_color,mark_color,mark_color
Rect StringWidth(Left$(t$,Input_mark_x))+x,y,StringWidth(Mid$(t$,Len(Left$(t$,Input_mark_x)+1),Input_mark_w)),FontHeight(),1
EndIf
Return t$
End Function




Zunächst fällt auf, dass die Funktion zu lang ist. Zur besseren Übersicht habe ich ihn hier im Zitat Markierungen ("....Block II...") eingefügt. Es sind mehrere eigentlich völlig unabhängige Funktionalitäten enthalten:

-Tastatur-Abfrage
-Tastencode-Konvertierung
-InputString-Erstellung und Manipulation
-Maussteuerung
-TextMarkierungsfunktion
Jeder dieser Bereiche sollte nun in eine eigene Funktion:

BlitzMax: [AUSKLAPPEN]
Input_Key=GetInputKey()

Funktion GetInputKey%()
Local In%
Input_key=GetKey()
If KeyHit(82) in=48
If KeyHit(79) in=49
If KeyHit(80) in=50
If KeyHit(81) in=51
.....
Return In
End Function


Eine Funktion sollte (wenn möglich) nicht größer als 50 Zeilen lang sein. Dies ist in etwa die Menge an Zeilen, die auf eine Bildschirmseite passt. Unser Code wurde durch diese Aktion nicht kürzer, aber ein "unkritischer" Teil des Codes wurde so optisch entfernt.

Jetzt zum zweiten Block

Dieser Block war ursprünglich 40 Zeilen lang und wenig übersichtlich. Es werden bei jedem Durchgang alle IF-Abfragen jedes Mal abgefragt. Dabei schließen sie sich gegenseitig aus. Der User drückt meist nur 1 Taste gleichzeitig. Einige der Abfragen waren auch von einem Timer abhängig. Sie wurden zu einem Block zusammengefasst:

BlitzBasic: [AUSKLAPPEN]
If msec > (Input_Timer +100)
Input_timer=msecs
Input_key=0

If (KeyDown(203)) And (input_cursor_pos>0) Then
input_cursor_pos=input_cursor_pos-1
ElseIf (KeyDown(205)) And (input_cursor_pos<Len(t$)) Then
input_cursor_pos=input_cursor_pos+1
ElseIf KeyDown(211)
If Input_mark_x>-1
t$=Left$(t$,Input_mark_x)+Mid$(t$,Input_mark_w+input_mark_x+1)
Else
t$=Left$(t$,input_cursor_pos)+Mid$(t$,input_cursor_pos+2)
EndIf
ElseIf KeyDown(14) And Input_cursor_pos>0
...
EndIf
EndIf

If (KeyDown(29) Or KeyDown(157)) And KeyHit(47)
tmp$=ReadClipboardText$()
t$=Left$(t$,input_cursor_pos)+tmp$+Mid$(t$,input_cursor_pos+1)
input_cursor_pos=input_cursor_pos+Len(tmp$)
ElseIf KeyHit(207)
.....
EndIf


Was hier noch auffällt ist die Häufung von Stringsschneide-Aktionen. Ich werfe sie in eine eigene Funktion:
BlitzBasic: [AUSKLAPPEN]
Function CutString$(locString$,Bis%,Ab%)
Return Left(locString,Bis)+Mid(locString,Ab)
End Function


Im Rest erlaube ich mir wegen der Lesbarkeit alle Namen wie input_cursor_pos% durch kürzere wie Cursor% zu ersetzen. Dies ist möglich, weil ich am Anfang der Funktion...
Zitat:
Cursor=input_cursor_pos
MarkerX=Input_mark_x
...schreibe und am Ende...
Zitat:
input_cursor_pos=Cursor
Input_mark_x=MarkerX


Danach sieht Block II jetzt so aus:
BlitzBasic: [AUSKLAPPEN]
Function ChangeInput$(t$)
Local tmp$, Cursor%, MarkerX%

Cursor=input_cursor_pos
MarkerX=Input_mark_x
MarkerW=Input_mark_w

If msec>Input_Timer +100
Input_timer=msecs
Input_key=0

If (KeyDown(203)) And (Cursor>0) Then
Cursor=Cursor-1
ElseIf (KeyDown(205)) And (Cursor<Len(t$)) Then
Cursor=Cursor+1
ElseIf KeyDown(211)
If MarkerX>-1
t$=CutString(t$,MarkerX,MarkerX+MarkerW+1)
Else
t$=CutString(t$,Cursor,Cursor+2)
EndIf
ElseIf KeyDown(14) And (Cursor>0)
....
EndIf
EndIf

If (KeyDown(29) Or KeyDown(157)) And KeyHit(47)
tmp$=ReadClipboardText$()
t$=Left$(t$,Cursor) + tmp$ + Mid$(t$,Cursor+1)
Cursor=Cursor+Len(tmp$)
ElseIf KeyHit(207)
.......
EndIf
input_cursor_pos=Cursor
Input_mark_x=MarkerX
Return t$
End Function


Der dritte Teil dient als Routine zum Zeichnen eines Markierungsblocks. Er wird natürlich ebenfalls in eine eigene Funktion gepackt und im Hauptprogramm nur aufgerufen:

BlitzBasic: [AUSKLAPPEN]
Function TextMarkieren(t$)
Local i%

If MouseHit(1)
; erster Marker-Pos festlegen
Input_mark_w=0
Input_mark_x=-1
For i=1 To Len(t$)
If StringWidth(Left$(t$,i))+x>=MouseX() Then
Input_mark_x=i-1
Exit
EndIf
Next
Input_cursor_pos=i
EndIf

If MouseDown(1) And Input_mark_x>-1
; Markerbereich noch variabel
If MouseX()>=x And MouseX()<=StringWidth(t$)+x
If MouseY()>=y And MouseY()<=FontHeight()+y
; Maus in Textbereich

For i=1 To Len(t$)
If MouseX()<StringWidth(Left$(t$,i))+x
Input_mark_w=Len(Mid$(t$,Input_mark_x,i-Input_mark_x))
Exit
EndIf
Next

EndIf
EndIf
EndIf

If Input_mark_x>-1
;Marker-Bereich zeichnen
Color mark_color,mark_color,mark_color
Teil$=Left$(t$,Input_mark_x)
VonX%=StringWidth(Teil)
Lang%=Len(Teil)+1
Bis%=StringWidth(Mid$(t$,Lang,Input_mark_w))
Rect Von+X,Y,Bis,FontHeight(),1,1
EndIf
End Function


Die ursprüngliche Hauptfunktion ist nun übersichtlich geworden:

BlitzBasic: [AUSKLAPPEN]
Function Input$(t$,x,y,size_x,size_y,cursor=False,mark_color=128)
Input_Key=GetInputKey()
ChangeInput$ t$
TextMarkieren t$
End Function


Der Code hier funktioniert übrigens nicht, er ist nur symbolisch zu verstehen. Die Fehler des Users sind nicht verbessert und es wurde auch noch nicht über das LOCAL-machen der Funktionsparameter nachgedacht (Kapselung der Funktionen)
  • Zuletzt bearbeitet von Midimaster am Mi, Dez 30, 2009 11:52, insgesamt einmal bearbeitet

Midimaster

Betreff: Lektion IV: Fehlersuche

BeitragMo, Dez 28, 2009 13:08
Antworten mit Zitat
Benutzer-Profile anzeigen
Lektion IV: Fehlersuche

Der erste und wichtigste Schritt bei der Fehlersuche sind kurze Code-Zeilen. Dadurch kannst du den Fehler schneller einkreisen und einer bestimmten Aktion zuordnen. Sollte Dein Code diesen Grundsatz noch nicht erfüllen, so lies Lektion I.

Der zweite Schritt ist, sich auf bereits 100% Funktionierendes verlassen zu können. Sollte Dein Code hier nicht sicher sein, so handle das nächste Mal nach Lektion II und III.

Traue keiner einzigen Deiner Code-Zeilen!

Der Hauptgrund, warum du einen Fehler nicht findest, ist immer, weil man an der Stelle sucht, wo man seine Auswirkungen im Programm bemerkt hat. Dazu ein fehlerhaftes Beispiel:

BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Repeat
A$=Chr$(GetKey())
If A="F" Then
Datei="C:\test\demobild.pmg"
Bild=LoadImage(Datei)
DrawImage Bild,0,0
EndIf
Until KeyHit(1)


Der User startet das Programm und das Bild wird nicht angezeigt. Der Fehler ist so offensichtlich, weil es ja ein Beispiel sein soll!!! Uns soll es jetzt mehr um die Strategie gehen, wie man diesen Fehler entdecken könnte.

DrawImage erzeugt einen RuntimeError, wenn es versucht ein Bild anzuzeigen, das nicht geladen werden konnte. Dies passiert bei diesem Fehler aber nicht. Also glaubt der User er hätte einen Blitz-Bug entdeckt. Er fragt im Forum nach, ohne auf die Idee gekommen zu sein, ob überhaupt das Programm läuft oder überhaupt irgendetwas angezeigt wird.

PRINT und DEBUGLOG sind Deine Freunde!

Verwende bei jeder Funktion, die Du aufrufst, bei jeder Struktur (IF, REPEAT, FOR, etc...), die du betrittst immer einen PRINT-Befehl, um zu überprüfen, ob du diesen Abschnitt überhaupt betreten hast. Die Ausgabe solcher PRINTS erfolgt in das DEBUG-Fenster. Ein zweites Fenster neben Deinem Spiel, in dem du den Programm-Ablauf verfolgen kannst. Im Menü über "Program -> Debug Enabled?" kann man den Debugger einschalten. Im Falle eines Fehlers stoppt das Programm und ermöglicht dir alle bis dahin ausgegebenen PRINTs durchzulesen. Auch du kannst das Programm an jeder Stelle durch ein einfaches Waitkey() anhalten und so das DEBUG-Fenster lesen.

Diese Befehle bleiben im Programm, bis das Projekt beendet ist. Selbst danach kannst su sie ausmarkieren, statt sie zu löschen, dann dienen sie immer noch als Kommentare. Ob su PRINT oder DEBUGLOG verwendest, hängt von deiner BlitzBasic-Version ab. In den Beispielen schreibe ich jetzt immer DEBUGLOG

BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
DebugLog " Programm startet"
Repeat
DebugLog "Repeat Schleife"
A$=Chr$(GetKey())
If A="F" Then
DebugLog " IF betreten"
Datei="C:\test\demobild.pmg"
Bild=LoadImage(Datei)
DrawImage Bild,0,0
EndIf
Until KeyHit(1)


Beobachte immer genau was su erlebst. Ein ungeübter User wird sagen, dass er sein Bild nicht sieht. Ein geübter User wird das ganze größer fassen und sagen: "Eigentlich sehe ich gar nichts!". Wenn auf dem Monitor noch gar nichts zu sehen war, wäre es klug zu testen, ob den überhaupt etwas angezeigt wird. Wir fügen daher gleich nach Graphics eine Zeile

BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Rect 100,100,100,100
DebugLog " Programm startet"
....


Natürlich fehlte der FLIP. Der User ergänzt ihn und stellt fest, das Rechteck wird gemalt, das Bild aber weiterhin nicht. Doch die DEBUGLOGs geben noch mehr Auskunft:
Zitat:
DEBUGLOG:
Programm startet
Repeat Schleife
Repeat Schleife
Repeat Schleife
....

Zwar sieht man ständig den Satz "Repeat Schleife" , aber es kommt nie zum " Taste F" , auch wenn ich die <F>-Taste drücke.

Ergebnis der Recherche: Es kommt nie zum Sprung in die IF-Struktur.

Angenommen wir erkennen nicht, warum das so sein kann, dann fügen wir einfach einen weiteren DEBUGLOG ein, der uns die Argumente, die in IF-Abfrage vorkommem, anzeigt:
BlitzBasic: [AUSKLAPPEN]
Graphics 800,600
Rect 100,100,100,100
DebugLog " Programm startet"
Repeat
DebugLog "Repeat Schleife"
A$=Chr$(GetKey())
DebugLog "gedrückt wurde" + a$
If A="F" Then
.....


Zitat:
DEBUGLOG:
Programm startet
Repeat Schleife
Repeat Schleife
Repeat Schleife
gedrückt wurde f
Repeat Schleife
....


Jetzt ist klar, dass es am Buchstaben "F" lag. Die GetKey() liefert uns eine klein geschriebenes "f" zurück. Wir ändern das und erhalten prompt einen RuntimeError. Jetzt wird wieder das Blitzforum kontaktiert mit der Behauptung: "Wenn ich If A="f" Then schreibe, meldet BlitzBasic einen RuntimeError.

Zitat:
DEBUGLOG:
Programm startet
Repeat Schleife
Repeat Schleife
Repeat Schleife
gedrückt wurde f
IF betreten


Da wir aber das DEBUGLOG genauer ansehen, können wir erkennen, das die Zeile " IF betreten" noch ausgeführt wurde. Erst danach muss es zu Problemen gekommen sein. Wir fügen weitere DEBUGLOG ein und stellen fest, direkt beim DRAWIMAGE beendet sich unser Programm. Dort muss der Fehler liegen:
BlitzBasic: [AUSKLAPPEN]
...
Datei="C:\test\demobild.pmg"
Bild=LoadImage(Datei)
DebugLog " Bis hierher geht es "
DrawImage Bild,0,0
...


Anmerkung: stelle für die weiteren Tests sicher, dass sich wirklich ein Testbild DEMOBILD.PNG in dem Verzeichnis "C:\test" befindet.


Durch das Laden des Bildes wird die Variable BILD zu einem Pointer oder Handle. Das ist ein Zeiger auf den Speicher mit den Bildinformationen. Wenn das Laden gelungen wäre, dann hätte sie einen Wert größer Null.

BlitzBasic: [AUSKLAPPEN]
...
Datei="C:\test\demobild.pmg"
Bild=LoadImage(Datei)
DebugLog " Bis hierher geht es Handle=" + Bild
DrawImage Bild,0,0
...


Aha, bei uns ist er 0, also ist das Laden schief gelaufen. Das kann nur am Dateinamen...
Zitat:
"C:\test\demobild.pmg"

...liegen. Wir entdecken das M in PMG und ändern es in PNG. Endlich! Jetzt wird es laufen, oder?

Aber wieder ein RuntimeError. Jetzt nicht verzweifeln, sonder in das DEBUGLOG schauen. Das selbe Ergebnis wie vorhin: Bild ist immer noch 0.

Zitat:
DEBUGLOG:
Programm startet
Repeat Schleife
Repeat Schleife
Repeat Schleife
gedrückt wurde f
IF betreten
Bis hierher geht es Handle=0


Der Fehler muss also beim Laden sein. Was laden wir? "C:\test\demobild.png" ? Nein!!! Wir laden Datei!
Also, kurz mal Datei überprüfen:

BlitzBasic: [AUSKLAPPEN]
...
Datei="C:\test\demobild.pmg"
DebugLog " Wir laden " + Datei
Bild=LoadImage(Datei)
DebugLog " Bis hierher geht es Handle=" + Bild
DrawImage Bild,0,0
...


und so sieht das DebugLog aus:

Zitat:
DEBUGLOG:
Programm startet
Repeat Schleife
Repeat Schleife
Repeat Schleife
gedrückt wurde f
IF betreten
Wir laden 0
Bis hierher geht es Handle=0


Also das hätten wir ja nun wirklich nicht erwartet. Einen Textstring hätten wir ja noch verstanden, aber eine Zahl? Datei soll doch den String aufnehmen, äh?? Schei... Datei$= !!!

Noch einmal mein Satz vom Anfang, und jetzt stimmst du vielleicht zu:

Traue keiner einzigen Deiner Code-Zeilen!

Immer schön nachprüfen und DEBUGLOGs anlegen. Oder hättest du am Anfang dieser Lektion gedacht, dass der Code nicht einen sondern 4 Fehler enthält?

Midimaster

Betreff: Lektion V: Weitere Tipps und Tricks zur Fehlersuche....

BeitragDi, Mai 11, 2010 9:43
Antworten mit Zitat
Benutzer-Profile anzeigen
Weitere Tipps und Tricks zur Fehlersuche


Alles geht so schnell....

Ein Hauptproblem beim Finden von Fehlern ist die Geschwindigkeit mit der Programme heutzutage ablaufen. Hier wäre es gut, wenn man eine Speed-Bremse einbauen könnte, oder im Ernstfall das Programm bis zum Stillstand bringt.


Daher dieser Tipp: baue folgende Zeilen in die REPEAT/UNTIL-Schliefe ein und lasse sie dort bis zur Fertigstellung des Projektes:

BlitzBasic: [AUSKLAPPEN]
If KeyHit(2)  ;wenn Taste "1" gedrückt wird Stoppen
WaitKey()
ElseIf KeyHit(3) ;wenn Taste "2" gedrückt wird Speed runter auf 10 fps
DelayZeit= 100
ElseIf KeyHit(4) ;wenn Taste "3" gedrückt wird Speed runter auf 1 fps
DelayZeit= 1000
ElseIf KeyHit(5) ;wenn Taste "4" gedrückt wird Speed wieder normal
DelayZeit=0
EndIf

If DelayZeit>0 Then
Delay DelayZeit
EndIf


So kannst Du das Programm um die Kritische Stelle abbremsen, bzw sogar komplett anhalten um einen Wert in der DEBUGLOG in Ruhe auszulesen.



Uhrzeit mitanzeigen
Oft glauben User, dass eine bestimmte Funktion nur ganz selten aufgerufen wird. Im DEBUGLOG scheint die dafür gesetzte Hinweiszeile auch gelegentlich zu kommen.


BlitzBasic: [AUSKLAPPEN]

Global Punkte%

Function Punkte()
If Gegner=0 Then
DebugLog "neuer Gegner"
Punkte=Punkte + 500
Gegner=10
EndIf
End Function
Hinweis: Da im Code vergessen wurde, die Variable Gegner% GLOBAL zu definieren, ist sie jedesmals wieder 0, wenn die Funktion aufgerufen wird. Folge: Es werden zu viele Punkte vergeben.

Da im Debugger nur die letzten 3 Zeilen zu sehen sind, steht dort ziemlich statisch 3x der Hinweis
Zitat:
neuer Gegner


Erst durch das Hinzufügen einer Zeitanzeige merkt der User, dass die Funktion viel zu oft gerufen wird:
BlitzBasic: [AUSKLAPPEN]
       DebugLog MilliSecs() + " neuer Gegner"



Beim Berechnen enstehen INTEGER-Werte


Eine Hauptursache für Fehler, die hier im Forum gemeldet werden ist die Tatsache, dass der Autor sich nicht genügend Gedanken über den Variablen-Typ macht. Er überlässt BB diese Definition und wundert sich dann, warum ganz bestimmte Zustände nicht eintreten. Ganz typisch dieses Beispiel:

BlitzBasic: [AUSKLAPPEN]
SpielerX=400
SpielerY=320
....
Repeat
....
SpielerX = SpielerX + Sin(SpielerWinkel)
....


Natürlich ist es hier ein sehr offensichtliches Beispiel. Aber tatsächlich geschieht dies bei den meisten Programmen dutzendfach: Durch die Festsetzung der Startkoordinate auf die Bildschirmmitte, wird die Variable SpielerX zu einer INTEGER gemacht. 400 ist ja eine INTEGER-ZAHL. Später wird aber nur noch sin() dazugezählt, und der ist immer eine Nachkommazahl unter 1. Folge: Die Summe wird immer wieder auf 400 gerundet!

Die richtige Lösung lautet:
BlitzBasic: [AUSKLAPPEN]
Global SpielerX#=400
Global SpielerY#=320
....
Repeat
....


Generell SOLL jede Variable in Geltungsbereich und Typ vom Autor festgelegt werden! Gewöhne es dir an!

weitere Tipps werden folgen....

Neue Antwort erstellen


Übersicht BlitzBasic FAQ und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group