Fehler verstecken leicht gemacht
Mit Try / Catch / Finally können in .NET Fehler behandelt werden.
Eigentlich eine gute Sache, allerdings sollte man aufpassen wo und wie man Fehler behandelt.
Vielerorts kann gelesen werden, dass das Fangen einer Allgemeinen Exception nicht gut sei, aber wieso ist das so?
Da ich es auch schon selber praktisch mehrmals erlebt habe, was das für schlimme Auswirkungen haben kann, ist es für mich nicht so schwer, diese Frage zu beantworten.
Ich möchte dies anhand eines kleinen, nachvollziehbaren Beispiels erläutern.
Gegeben ist folgender Code:
protected void Page_Load(object sender, EventArgs e)
{
try
{
this.GridView1.DataSource = <DataSource>;
this.GridView1.RowDataBound += GridView1_RowDataBound;
CommandField deleteField = new CommandField
{
ShowDeleteButton = !this._recordsAreReadOnly,
DeleteText = “Löschen”,
DeleteImageUrl = "delete.png",
ButtonType = ButtonType.Image
};
GridView1.Columns.Add(deleteField);
// zusätzlichen Code…
this.GridView.DataBind();
}
catch(Exception ex)
{
this.Logger.Log(ex);
}
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType == DataControlRowType.DataRow)
{
// zusätzlicher Code…
ImageButton deleteButton = e.Row.FindControlRecursive<ImageButton>(c => c.CommandName == “Delete”);
deleteButton.CssClass = “button_delete”;
// zusätzlicher Code…
}
}
Dieser Code, läuft - wie er jetzt da steht - ohne Probleme, solange die Daten nie als readonly gekennzeichnet werden.
Wird jetzt allerdings der Code erweitert, gibt es ein höchst merkwürdiges Verhalten, das ich mir zuerst überhaupt nicht erkären konnte.
Folgende Änderung:
ShowDeleteButton = !this._recordsAreReadOnly,
wird zu:
ShowDeleteButton = !this._recordsAreReadOnly && <Sicherheitsabfrage ob gelöscht werden darf>,
Der erste Teil der Bedingung liefert mit der Negation schlussendlich true und die Sicherheitsabfrage false, was ausgewertet dann einem false entspricht.
Somit werden die Löschen-Buttons nicht dargestellt. Gut. Eigentlich genau das was ich wollte.
Jedoch – und jetzt kommt der Haken – wird jetzt nur noch ein Datensatz im GridView angezeigt, anstelle von den zwei die in der Datenquelle vorhanden sind.
Im ersten Moment kam mir das ziemlich merkwürdig vor, da ich auch nirgendwo nochmals auf die Bedingung zugreifen und vor allem, weil auch kein Fehler geworfen wurde.
Kurz nachdem ich den Debugger angeworfen und kurz in den EventHandler “GridView1_RowDataBound” reingeschaut habe, kam es mir in den Sinn:
Böses Try / Catch, ein Fehler wurde versteckt, ohne das ich es gemerkt habe.
Was ist denn genau passiert?
Nun, da über die komplette Page_Load-Methode ein Try / Catch Konstrukt gespannt ist, das generell alle Fehler abfängt (Nicht behandelt, sondern eben “verschluckt”), bekomme ich den Fehler nicht zu Gesicht und der Code läuft in einem inkonsistenten Zustand weiter.
Der Eventhandler wird angemeldet und ein GridView1.DataBind()-Aufruf läuft im Eventhandler selber, also auch im Scope von besagtem Try / Catch, sodass die NullReferenceException, die eigentlich geworfen werden sollte, verschluckt wird.
Das hat dann dazu geführt, dass das GridView “irgendwie” noch halb fertig gerendert wird und man nichts vom Fehler mitbekommt, sondern eben nur einen Datensatz halb fertig gerendert wird.
Ich hoffe das dieses Beispiel ein wenig Klarheit bringt und vor allem euch aufweckt, Exception Handling mit Bedacht einzusetzen und nicht an einem solch komischen Verhalten zu verzweifeln.
Zusatz / Fazit:
Wie man sehen kann, ist der Scope (Wirkungsbereich) von Try / Catch um die ganze Page_Load-Methode gelegt.
Dies kann Sinn machen, jedoch nicht in der Mehrzahl der Fälle, wie auch in diesem Fall.
Meiner Meinung nach, und so steht es auch in den meisten Büchern, sollten Fehler punktuell abgefangen werden, nur dort wo man ihn auch behandeln kann und der Scope sollte möglichst klein gehalten werden, damit das Problem auch schnell identifiziert ist.
Die unbehandelten Fehler kann man dann generell in ASP.NET bspw. in der Global.asax.cs in der Methode Application_Error() loggen.
Ich persönlich gehe sogar so vor, das ich zuerst ohne Fehlerbehandlung entwickle und diese erst später hinzufüge.
So gehen während der Entwicklung keine Fehler vergessen und das Programm befindet sich nie in einem inkonsistenten Zustand.
Solchen Code wie oben gezeigt ist - wie auch Gregor im Kommentar bemerkt hat - vielerorts zu finden, was ich tragisch finde.
Die Konsequenzen daraus müssen in keinem Fall so harmlos sein, wie in diesem Beispiel gezeigt. Man stelle sich nur mal ein Atomkraftwerk vor, das durch eine inkonsistente Software gesteuert wird.
Bearbeitung / Korrekturen:
13.11.09 - Fehlendes Komma in den Codesnippets hinzugefügt
13.11.09 - Zusatz / Fazit hinzugefügt