2007-12-18

Defensives Programmieren


In meinem heutigen Eintrag möchte ich mich mit dem Thema Defensives Programmieren beschäftigen. Wenn Sie den Begriff (oder seine englische Entsprechung defensive programming) in das Eingabefeld der Suchmaschine Ihres Vertrauens eintragen, werden Sie sehr schnell entsprechende Wikipedia-Seiten finden. Die momentan aktuelle (nachgeschlagen am 18.12.2007) deutsche Fassung enthält eine aus meiner Sicht leider nur eingeschränkt tragfähige (weil unvollständige) Definition. Der Eintrag beschreibt nämlich (völlig zu Recht), was aus fachlicher Sicht zu tun ist, um angemessen auf Fehleingaben oder unvorhergesehene Ereignisse zu reagieren. Er lässt technische Belange aber nahezu vollständig außen vor.
Defensive Programmierung lässt sich jedoch nicht auf Bevor Du einen Drucker löscht, prüfe, ob er dem System überhaupt noch bekannt ist. reduzieren. Neben dieser fachlich motivierten Robustheit spielt die Qualität des Quelltextes eine entscheidende Rolle. Sie äußert sich unter anderem in seiner kompromisslosen Toleranz gegenüber Fehlern von innen und außen, getreu dem Motto Traue Nichts und Niemandem. Diesem Aspekt trägt die englische Fassung übrigens viel mehr Rechnung.
Welche Auswirkungen hat ein solcher Programmierstil? Flapsig ausgedrückt könnte man sagen, Sie müssen die Paranoia zum Maß aller Dinge machen. Da jeder Methodenaufruf fehlschlagen oder eine Ausnahme werfen kann, ist es unabdingbar, nicht nur sauber alle Rückgabewerte zu verarbeiten, sondern auch mit unerwarteten Exceptions zu rechnen. Denken Sie daran, dass in Java nicht nur dann eine Ausnahme geworfen werden kann, wenn dies mit throws explizit gekennzeichnet wird. Bitte sehen Sie sich die folgende kleine Klasse an.
   
package com.thomaskuenneth.demo;
   
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
   
public class BadFileDemo {
   
public static void main(String[] args) {
File f = new File(System.getProperty("user.home"), "BadFileDemo.txt");
try {
FileOutputStream fos = new FileOutputStream(f);
fos.write("Test\n".getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
   
Auf den ersten Blick scheint es sich um ein recht ordentlich geschriebenes Stück Software zu handeln. Oder? Lassen Sie uns sehen, wie man es ein bisschen besser machen kann…
   
package com.thomaskuenneth.demo;
   
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
   
public class BetterFileDemo {
   
public static void main(String[] args) throws IOException {
File f = new File(System.getProperty("user.home", "."),
"BadFileDemo.txt");
if ((f.exists() == true) && (f.isDirectory() == true)) {
throw new IOException(f.getAbsolutePath()
+ " exists and is a directory");
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
fos.write("Test\n".getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
   
Haben Sie die Unterschiede bemerkt? Oder, anders gefragt, was sind denn die gröbsten Schnitzer im ersten Programm? Diese zweite Klasse wirkt zugegeben um einiges länger. Schuld daran ist der neue finally {}-Block, der um einiges sorgfältiger beim Schließen des Ausgabestroms zu Werke geht. Falls Sie sich fragen, ob der Aufwand nötig ist… Aber logo. Warum sollte diese Aktion nicht fehlschlagen können? Außerdem wird so ein großes Manko beseitigt. Wenn im ersten Programm während des Schreibens in den Strom ein Fehler auftritt, wird er nicht geschlossen. Nicht schön, nicht wahr? :-)
Ein weiterer, subtiler Schutz ist übrigens noch dazu gekommen. Finden Sie ihn?

No comments:

Post a Comment