2014-01-25

Nachhaltig programmieren

Im Teil 29 von Ultimate Swing hatte ich Ihnen gezeigt, wie leicht man mittels Drag and Drop in Notes and Tasks Aufgaben hinzufügen kann. In diesem Zusammenhang gab es eine kleine Hausaufgabe:

Bauen Sie doch einmal eine Testdatei und ziehen diese auf das Hauptfenster. Einige Aufgaben werden angelegt, aber nicht alle. Sind Sie tief genug mit dem Quelltext vertraut, um herauszufinden, was da schief geht? Das Problem liegt in einer Methode, die in fileDropped() aufgerufen wird.

Um das Problem zu identifizieren, müssen wir zunächst einen Blick auf die genannte Methode werfen. Sie liest die Datei mit den Aufgaben zeilenweise ein, erzeugt Task-Objekte und speichert diese. Dass Aufgaben beim Erzeugen verloren gehen, scheint unlogisch. Eher schon könnte es Probleme beim Schreiben auf Platte geben. Dann nämlich, wenn eine bereits vorhandene Aufgabe überschrieben wird. Die Methode save() der Klasse LocalTasksService nutzt die Eigenschaft id einer Aufgabe als Dateiname. Wäre die Id mehrfach vorhanden, käme es zu dem beschriebenen Phänomen. Sehen wir uns nun an, wie Aufgaben erzeugt werden. Dies geschieht in der Methode createTask(). Sie sah damals folgendermaßen aus:

 public static Task createTask(String text) {  
  return new Task(text, System.currentTimeMillis(), null,  
    Long.toString(System.currentTimeMillis()), false, null);  
 }  

Die Id wird also auf System.currentTimeMillis() gesetzt. Klingt doch recht sauber, oder? Nein, im Gegenteil. Die Wahrscheinlichkeit, dass das Erzeugen von zwei Aufgaben (während dem zeilenweisen Lesen) weniger als 1 Millisekunden braucht, ist recht hoch. Dass genau hier das Problem liegt, lässt sich recht einfach belegen, indem man von jeder erzeugten Aufgabe die Id ausgibt.

Sehen Sie sich nun an, wie ich das Problem löse:

 public static Task createTask(String text) {  
  return new Task(text, System.currentTimeMillis(), null,  
    UUID.randomUUID().toString(), false, null);  
 }  

Auch UUID.randomUUID() birgt das potenzielle Risiko einer Kollision. Die Wahrscheinlichkeit hierfür ist aber vergleichsweise gering.

Was will ich damit sagen? Wir treffen im Rahmen der Programmierung regelmäßig Entscheidungen, die auf Annahmen beruhen. In meinem Fall war dies die Annahme, dass Aufgaben nicht öfter als alle 2 Millisekunden angelegt werden. Sofern der Benutzer die Aufgaben von Hand anlegt, stimmt das ja auch. Der Anwendungsfall Stapelverarbeitung war damals nicht im Fokus. Und deshalb kam die ursprüngliche Implementierung damit nicht zurecht. Falsch wäre, jede mögliche Änderung im Voraus einplanen zu wollen. Vielmehr rate ich zu einer gesunden Vorausschau. In meinem Fall wäre dies das Beantworten der Frage: Ist eine Id auf der Basis von Millisekunden genau genug? Falls Sie jetzt sagen: “Na, dann soll er halt den Dateinamen anders aufbauen” – ja, stimmt, könnte ich. Aber vielleicht tritt das Problem mit der zu ungenauen Id an anderer Stelle ja auch auf. Fixe ich nur den Dateinamen, habe ich keine nachhaltige Lösung. Fixe ich die Id selbst, bin ich in jedem Fall auf der sicheren Seite.

No comments:

Post a Comment