Fehlerbehandlung - Verhalten bei Fehlern www.delphi-treff.de
148
sind. Im Umkehrschluss können wir durch Doppelklick bzw. Quelltext anzeigen im Kontextmenü des 2. Eintrags im Aufruf-Stack, die aufrufende, dann hervorgehobene Stelle im Quelltext lokalisieren. Hier gibt es
einen Unterschied zu einer VCL-Anwendung, denn dort wird die auf den Aufruf folgende Zeile markiert. Zudem wäre dort der Aufruf-Stack deutlich umfangreicher, da in einer Formularanwendung erheblich mehr
Initialisierungsarbeit geleistet werden muss.
5.3. Verhalten bei Fehlern
Es ist unvermeidlich, dass Programmierer Fehler produzieren. Diese Fehler dann zu finden ist eine Sache der Erfahrung. Trotzdem gelingt es nicht immer, einen Fehler schnell zu lokalisieren und solch eine Fehlersuche
kann sich auch manches Mal über Stunden hinziehen. Kein Grund zu verzweifeln: Zumeist genügt es, eine Pause einzulegen, um mit klarem Kopf die Fehlersuche
wieder aufzunehmen und die eigene Strategie in Frage zu stellen.
5.3.1. Hinweise und Warnungen
Bevor es mit den Fehlern losgeht, ein paar Worte zu einem, von Einsteigern oft unterschätzten Thema: Der Compiler gibt uns nicht nur Fehlermeldungen aus, er macht uns auch auf mögliche Fehlerquellen aufmerksam.
Syntaktisch kann der Quelltext also in Ordnung sein, möglicherweise enthält er jedoch Fehler in der Programmlogik.
Unser Ziel muss es demnach sein, dass solche Meldungen gar nicht erst entstehen, damit potentielle Fehlerquellen von vornherein ausgeschlossen werden.
Betrachten wir die vom Compiler erzeugten Hinweise folgender Funktion: function MaximumAZahl1, AZahl2: Integer: Integer;
var i: Integer;
H2164 begin
Result := 0; H2077
if AZahl1 AZahl2 then Result := AZahl1
else Result := AZahl2;
end;
[DCC Hinweis] HinweiseUndWarnungen.dpr5: H2077 Auf Maximum zugewiesener Wert wird niemals benutzt
[DCC Hinweis] HinweiseUndWarnungen.dpr3: H2164 Variable i wurde deklariert, aber in Maximum nicht verwendet
Fehlerbehandlung - Verhalten bei Fehlern www.delphi-treff.de
149
Der untere Hinweis ist schnell abgehandelt, denn offensichtlich wurde hier nur vergessen, die Variablendeklaration von i zu entfernen. Ein Doppelklick auf diesen Hinweis im Meldungsfenster führt uns
direkt zur Fundstelle im Quelltext, also zur Deklaration der überflüssigen Variable i in der angegebenen, auf die gesamte Datei bezogenen Zeile
– hier und in der Folge allerdings angepasst auf den jeweiligen Textausschnitt. Markieren durch Einfachklick und Anfordern der Hilfe durch Drücken von F1 zeigen uns weitere Informationen
zu diesem Hinweis, ebenfalls zu erreichen durch Aufrufen der Hilfe und suchen nach der Meldungsnummer H2164.
Auch die Hinweismeldung der oberen Zeile spricht bereits für sich. Result wird in allen Fällen ein neuer Wert zugewiesen und damit ist die Initialisierung in Zeile 5 überflüssig und kann ebenfalls entfernt werden. Wie die
Meldung außerdem zeigt, können innerhalb von Funktionen der Funktionsname und Result gleichrangig verwendet werden. Aus Gründen der Einheitlich- und Übersichtlichkeit verwenden wir jedoch ausschließlich
Result. Während Hinweise meist stilistischer Natur sind, deuten Warnungen auf mögliche Fehlerquellen hin.
function PotenzABasis: Integer; AExponent: Cardinal: Integer; var
i: Integer; begin
Result := 1; Fehlende Initialisierung for i := 1 to AExponent do
Result := Result ABasis; Lesender Zugriff auf Result
end;
[DCC Warnung] HinweiseUndWarnungen.dpr8: W1035 Rückgabewert der Funktion Potenz könnte undefiniert sein
Abgesehen davon, dass man sich hier um die mögliche Größe des Ergebnisses wenig Gedanken macht, wird Result nicht mit dem richtigen Wert vorbelegt. Der lesende Zugriff darauf liefert also dort einen Zufallswert und
stellt einen Sonderfall einer nicht initialisierten lokalen Variable dar. Wäre die Funktion eine Prozedur und Result eine lokale Variable darin, dann würde sich folgende Warnmeldung ergeben:
[DCC Warnung] HinweiseUndWarnungen.dpr8: W1036 Variable Result ist möglicherweise nicht initialisiert worden
Glücklicherweise werden wir vom Compiler auf solche Schnitzer aufmerksam gemacht. Nicht immer ist jedoch direkt klar, was an unserem Quelltext eine Warnung hervorrufen sollte:
Fehlerbehandlung - Verhalten bei Fehlern www.delphi-treff.de
150
function SignumAZahl: Integer: Integer; begin
if AZahl 0 then Result := -1
else if AZahl 0 then
Result := 1 else
if AZahl = 0 then Stiftet Verwirrung
Result := 0; end;
[DCC Warnung] HinweiseUndWarnungen.dpr11: W1035 Rückgabewert der Funktion Signum könnte undefiniert sein
Offensichtlich ist hier jeder mögliche Fall für AZahl abgehandelt worden und Result wird auch das richtige Ergebnis zugewiesen. Der Compiler erkennt jedoch in der letzten If-Anweisung eine Bedingung, für die es dann
natürlich auch eine Alternative innerhalb eines Else-Zweiges geben könnte. Abhilfe schafft hier das einfache Entfernen der überflüssigen Bedingung, denn das abschließende Else behandelt alle anderen Fälle.
Gerade fehlende oder falsche Else-Zweige innerhalb von If- und Case-Anweisungen führen immer wieder zu schwer lokalisierbaren Fehlern. Handelt es sich nämlich nicht gerade um einen Initialwert, dann werden wir
auch nicht auf einen fehlenden oder semantisch falschen Wert in der Fallunterscheidung hingewiesen. Ist man sich hier unsicher, so könnte man einfach im abschließenden Else eine Exception erzeugen, die auf den
unbehandelten Wert in der Fallunterscheidung hinweist. Häufig sieht man auch folgende Warnmeldung:
var sTemp: AnsiString;
sTemp: string; begin
sTemp := 123
; WriteLnStrToIntsTemp;
Implizite Typumwandlung
[DCC Warnung] HinweiseUndWarnungen.dpr5: W1057 Implizite String-Umwandlung von AnsiString zu string
Seit Delphi 2009 verweist der Alias String nicht mehr auf einen AnsiString, sondern auf einen UnicodeString. Da die internen String-Funktionen natürlich weiterhin mit dem Alias arbeiten, wandelt Delphi den AnsiString in
einen UnicodeString um, was, in diese Richtung umgewandelt, auch nicht weiter fehleranfällig ist. Trotzdem
Fehlerbehandlung - Verhalten bei Fehlern www.delphi-treff.de
151
bereinigen wir den Quelltext, in dem wir mittels stringsTemp den Parameter für StrToInt explizit umwandeln oder direkt sTemp als string deklarieren. Das nackte Ergebnis der beiden Möglichkeiten ist erstmal gleich gut
– die Warnung verschwindet.
Typumwandlungen haben aber immer eine latente Fehleranfälligkeit, denn man muss die beiden Typen bewerten können, um zu wissen, ob hier unter Umständen Datenverlust möglich ist. Außerdem sollte man,
solange kein gegenteiliger Grund vorliegt, immer mit den generischen bzw. dynamischen Typen arbeiten, denn der Compiler ist darauf optimiert. Eine Ausnahme wäre z.B. die programmexterne Kommunikation mit einer
Datei, bei der fundamentale bzw. statische Typen mit ihrer konstanten Darstellungsbreite, über Delphi- Versionen hinweg, angebracht sind.
Wie auch in den nächsten Unterkapiteln, können wir hier nur einige wenige Meldungen exemplarisch zeigen. Aber allein das Lesen und Verstehen der Hinweise und Warnungen sollte uns, in Kombination mit dem
entsprechenden Hilfetext, zur Lösung des Problems führen.
5.3.2. Fehler zum Zeitpunkt der Kompilierung