1. Fehlerbehandlung: Ignorieren Sie keine Ausnahmen!
Die Arbeit mit Dateien ist immer eine Interaktion mit der Außenwelt, die sehr unvorhersehbar sein kann. Datenträger können voll laufen, Dateien können verschwinden, Rechte können sich ändern, und Benutzer können Unerwartetes tun (zum Beispiel Ihr Programm aus einem Ordner mit Leerzeichen im Namen starten oder von einem USB‑Stick ausführen, der gleich abgezogen wird). Wenn Ihr Code darauf nicht vorbereitet ist, riskiert er nicht nur, „abzustürzen“, sondern auch, den Benutzer ohne die benötigten Daten zurückzulassen.
Best Practices sind nicht einfach „modische Tipps“, sondern eine Sammlung bewährter Vorgehensweisen, die helfen, die unangenehmsten Szenarien zu vermeiden: Datenverlust, Speicherlecks, Offenlegung vertraulicher Informationen und einfach dumme Bugs, für die man sich später vor den Kollegen (und besonders vor den Benutzern) schämt.
Warum darf man keinen leeren catch-Block schreiben?
In Java (und nicht nur dort) ist es sehr verlockend, etwas zu schreiben wie:
try {
// Arbeit mit der Datei
} catch (IOException e) {
// nun, hat nicht geklappt, schon gut!
}
Das ist das Schlimmste, was man tun kann. Solcher Code „verschluckt“ den Fehler nicht nur: Er macht ihn sowohl für den Benutzer als auch für Sie unsichtbar. Wenn also etwas schiefgeht, erfahren Sie nie, was genau und wann passiert ist.
Wie macht man es richtig?
- Protokollieren Sie Fehler: geben Sie wenigstens eine Meldung auf der Konsole aus oder schreiben Sie in eine Logdatei.
- Informieren Sie den Benutzer: wenn der Fehler kritisch ist, zeigen Sie eine freundliche Meldung an.
- Geben Sie nicht zu viel preis: zeigen Sie dem Benutzer keine internen Systemdetails (zum Beispiel ist ein vollständiger Stacktrace eher für Entwickler gedacht).
Beispiel:
try {
List<String> lines = Files.readAllLines(Path.of("data.txt"));
// Datenverarbeitung
} catch (IOException e) {
System.err.println("Fehler beim Lesen der Datei: " + e.getMessage());
// Details können in eine Logdatei geschrieben werden
e.printStackTrace(System.err);
}
Warum ist es wichtig, spezifische Ausnahmen zu fangen?
Weil unterschiedliche Fehler unterschiedliche Reaktionen erfordern. Wenn zum Beispiel die Datei nicht gefunden wurde, können Sie dem Benutzer anbieten, eine andere Datei auszuwählen (NoSuchFileException oder FileNotFoundException). Wenn keine Rechte vorhanden sind, bitten Sie darum, das Programm mit den erforderlichen Rechten zu starten (AccessDeniedException). Wenn der Datenträger voll ist, schlagen Sie vor, Speicherplatz freizugeben (eine IOException beim Schreiben).
2. Zugriffsrechte und Sicherheit
Prüfen Sie die Zugriffsrechte vor den Operationen
Bevor Sie eine Datei lesen oder schreiben, ist es sinnvoll sicherzustellen, dass Sie die entsprechenden Rechte haben. In Java gibt es die Methoden:
- File.canRead()
- File.canWrite()
Selbst wenn diese Methoden true zurückgeben, garantiert das keinen Erfolg – Rechte können sich jederzeit ändern (zum Beispiel, wenn ein anderer Prozess die Rechte geändert hat). Seien Sie deshalb immer auf Ausnahmen vorbereitet.
Beispiel:
File file = new File("config.properties");
if (!file.canRead()) {
System.err.println("Keine Leseberechtigung für die Datei!");
return;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
// Datei lesen
} catch (IOException e) {
System.err.println("Fehler beim Lesen: " + e.getMessage());
}
Geben Sie keine internen Details preis
Wenn Ihr Programm mit vertraulichen Dateien arbeitet (z. B. Passwörtern), geben Sie deren Pfade oder Inhalte nicht in Fehlermeldungen aus, die für Benutzer sichtbar sind.
3. Verwenden Sie keine relativen Pfade für kritische Operationen
Ein relativer Pfad (new File("data.txt")) ist ein Pfad relativ zum aktuellen Arbeitsverzeichnis, das je nach Startart des Programms unterschiedlich sein kann (zum Beispiel aus der IDE oder von der Kommandozeile). Das kann zu Verwirrung und Fehlern führen.
Best Practice: Für wichtige Dateien verwenden Sie absolute Pfade oder legen das Arbeitsverzeichnis ausdrücklich fest.
Beispiel:
String userHome = System.getProperty("user.home");
Path configPath = Path.of(userHome, "myapp", "config.properties");
4. Arbeit mit temporären Dateien und Verzeichnissen
Wozu dienen temporäre Dateien?
Temporäre Dateien werden für verschiedene Aufgaben benötigt. Manchmal dienen sie Zwischenoperationen: Beispielsweise werden Daten zuerst in eine temporäre Datei geschrieben, und anschließend ersetzt diese Datei die ursprüngliche. Ein anderer Fall – temporäre Dateien helfen, Informationen zu speichern, die nach Programmende nicht mehr benötigt werden und sicher gelöscht werden können.
Wie erstellt man temporäre Dateien sicher?
Verwenden Sie Methoden aus java.nio.file.Files:
Path tempFile = Files.createTempFile("myapp_", ".tmp");
// ... Arbeit mit der Datei
Files.deleteIfExists(tempFile);
Temporäre Verzeichnisse
Path tempDir = Files.createTempDirectory("myapp_");
5. Zuverlässigkeit: Backups und Integritätsprüfung
Verwenden Sie Backups beim Ändern wichtiger Dateien
Bevor Sie eine wichtige Datei überschreiben (z. B. Einstellungen), erstellen Sie eine Kopie:
Path config = Path.of("config.properties");
Path backup = Path.of("config.properties.bak");
if (Files.exists(config)) {
Files.copy(config, backup, StandardCopyOption.REPLACE_EXISTING);
}
Wenn beim Schreiben etwas schiefgeht, können Sie immer aus der Sicherung wiederherstellen.
Überprüfen Sie die Datenintegrität
Für besonders wichtige Daten können Sie Prüfsummen verwenden (z. B. MD5 oder SHA-256). Nach dem Schreiben der Datei berechnen Sie die Checksum und speichern sie daneben. Beim Lesen überprüfen Sie, ob sich die Datei geändert hat.
Beispiel für die Berechnung von SHA-256 (für Kryptografie-Fans):
import java.security.MessageDigest;
import java.nio.file.Files;
import java.nio.file.Path;
byte[] data = Files.readAllBytes(Path.of("important.dat"));
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
// Den Hash in eine separate Datei speichern oder beim Lesen vergleichen
6. Minimieren Sie das Zeitfenster zwischen Prüfung und Verwendung der Datei
Das ist das bekannte klassische TOCTOU-Problem (Time Of Check To Time Of Use): Zwischen dem Moment, in dem Sie geprüft haben, dass die Datei existiert, und dem Beginn des Lesens kann die Datei verschwinden oder geändert werden.
Versuchen Sie daher, Prüfung und Verwendung in einem try-Block zu bündeln. Und behandeln Sie unbedingt Ausnahmen, auch wenn Sie die Datei gerade erst geprüft haben.
Beispiel:
Path filePath = Path.of("data.txt");
if (Files.exists(filePath)) {
try (BufferedReader reader = Files.newBufferedReader(filePath)) {
// Datei lesen
} catch (IOException e) {
System.err.println("Fehler beim Lesen der Datei (möglicherweise wurde die Datei entfernt): " + e.getMessage());
}
}
7. Noch einige nützliche Tipps
Verwenden Sie try-with-resources für alle Ressourcen
Alle Klassen, die das Interface AutoCloseable implementieren (und das sind fast alle Java IO/NIO-Streams), können in try-with-resources verwendet werden. Das schützt vor Ressourcenlecks.
try (BufferedReader reader = Files.newBufferedReader(Path.of("data.txt"))) {
// Lesen
}
Vergessen Sie nicht, temporäre Dateien zu löschen
Files.deleteIfExists(tempFile);
Schließen Sie Ressourcen nicht doppelt
Wenn Sie try-with-resources verwenden, rufen Sie close() nicht manuell auf – das kann zu Fehlern und doppelten Schließversuchen führen.
8. Typische Fehler beim Arbeiten mit Dateien
Fehler Nr. 1: Ausnahmen ignorieren.
Einen leeren catch zu schreiben ist, als würde man Fliegen mit der Hand fangen und wieder freilassen. Protokollieren Sie immer oder informieren Sie zumindest den Benutzer darüber, was schiefgelaufen ist.
Fehler Nr. 2: Streams nicht schließen.
Wenn man vergisst, einen Stream zu schließen, kann die Datei gesperrt bleiben und dem System gehen die Dateideskriptoren aus. Verwenden Sie try-with-resources.
Fehler Nr. 3: Relative Pfade für wichtige Dateien verwenden.
Verlassen Sie sich nicht darauf, dass das Arbeitsverzeichnis immer das ist, was Sie erwarten. Besser ist es, den Pfad explizit zu setzen oder spezielle Verzeichnisse zu verwenden (user.home, java.io.tmpdir).
Fehler Nr. 4: Wichtige Dateien ohne Backup überschreiben.
Bevor Sie etwas Wichtiges überschreiben, erstellen Sie ein Backup. Das schont die Nerven und die Daten der Benutzer.
Fehler Nr. 5: Zugriffsrechte nicht prüfen.
Prüfen Sie, ob der Benutzer Lese-/Schreibrechte für die erforderlichen Dateien oder Verzeichnisse hat – andernfalls erhalten Sie unerwartete AccessDeniedException.
Fehler Nr. 6: TOCTOU-Zeitfenster.
Zwischen Prüfung und Verwendung kann die Datei von jemand anderem geändert oder gelöscht werden. Behandeln Sie Ausnahmen immer, auch nach einer Prüfung.
Fehler Nr. 7: Temporäre Dateien und Müll liegen lassen.
Nach Abstürzen oder Fehlern können temporäre Dateien verbleiben. Vergessen Sie nicht, sie zu löschen, besonders wenn es sich um sensible Daten handelt.
GO TO FULL VERSION