2011-12-29

Ultimate Swing, Teil 10

Das folgende Quelltextfragment taucht so oder in ähnlicher Form an mehreren Stellen von Notes and Tasks auf:

          for (Task task : tasks) {
            // FIXME: SwingWorker and error handling
            MainController.this.tasksModel.removeTask(task);
            tasksDelegate.delete(task);
          }

Selbst wenn der Kommentar die Defizite nicht unmittelbar benennen würde, fiele ihnen sicher einige Fragen ein. Zum einen ist es unwahrscheinlich, dass weder removeTask() noch delete() Rückgabewerte haben. Warum werden sie nicht ausgewertet? Zum anderen könnte das Löschen von Aufgaben – gerade wenn hierzu ein Webservice aufgerufen wird – durchaus länger dauern. Warum wird die Aktion nicht in einen eigenen Thread ausgelagert? Und schließlich: warum gibt es überhaupt zwei Aufrufe für das Löschen einer Aufgabe? Läge es nicht nahe, sie zu einem zusammenzufassen?

Den letzten Aspekt – die Zerlegung des Löschvorgangs in zwei Teile - möchte ich zunächst nicht weiter vertiefen. Er betrifft die Schichtung von Anwendungen und die Trennung von UI-Logik und Persistenz. Belassen wir es deshalb bis auf Weiteres bei der Information, dass der erste Aufruf (removeTask()) eine Zeile aus der Aufgabenliste entfernt, wohingegen die Methode delete() die Aufgabe physikalisch löscht. Derzeit wird diese als Datei im lokalen Dateisystem abgelegt; später werden wir auf ein Google-Konto zugreifen. Es bleiben also zwei Fragen offen. 1) Wie lagert man die beiden Aufrufe sinnvoll in Threads aus und 2) wie verfährt man im Fehlerfall? Die Antwort auf die zweite Frage scheint auf der Hand zu liegen. “Na, ich fange die Fehler ab und gebe eine entsprechende Meldung aus.”  Das klappt bei einer Aufgabe prima. Aber führen 5 oder 10 Fehlersituationen auch zu entsprechend vielen Meldungen? Was geschieht, wenn solche Meldungen nicht auf irgend einer Console ausgegeben werden, sondern als modaler Dialog?

Treten wir also gedanklich einen Schritt zurück und fragen, warum es zur Ausgabe von mehreren Meldungen kommt. Der Anwender hat zum Beispiel aus einer Liste mehrere Elemente (Aufgaben oder Kategorien) ausgewählt und möchte diese löschen. Auch wenn daraus mehrere (Webservice-)Aufrufe resultieren, repräsentieren diese doch eine Aktion. Konsequenterweise darf es im Fehlerfall auch nur eine entsprechende Meldung geben, die aber – und das ist durchaus aufwendig – über alle aufgetretenen Probleme informiert. Bei dem hier skizzierten Löschen handelt es sich also um eine (fachliche) Transaktion. Das bedeutet, dass alle Änderungen rückgängig gemacht werden müssen, wenn ein Teilvorgang fehl schlägt. Anders formuliert: der Benutzer müsste wieder den Zustand vor dem Anklicken der Schaltfläche Löschen vorfinden. Dies betrifft insbesondere die Liste und die darin ausgewählten Elemente. Unglücklicherweise wird dies sehr schnell sehr aufwendig, wenn Webservices im Spiel sind.Denn dann gibt es in der Regel weder beginTransaction() noch commit() und rollback(). Natürlich gibt es hier Hilfskonstrukte, zum Beispiel in Form kompensierender Serviceaufrufe.Ob es freilich sinnvoll ist, 9 erfolgreich gelöschte Kategorien neu anzulegen, weil das Löschen der 10. fehlgeschlagen ist, muss meiner Meinung nach im Einzelfall geprüft werden.

Für die Architektur von Swing-Anwendungen gilt aber in jedem Fall, dass eine fachliche Transaktion zu maximal einer Meldung führen darf, der Anwender muss aber erkennen, was schief gegangen ist. Wie akribisch die Rekonstruktion des Zustands vor der Transaktion erfolgt, würde ich von den technischen Rahmenbedingungen abhängig machen. Steht eine Transaktionskontrolle zur Verfügung, müssen Sie diese natürlich nutzen.

No comments:

Post a Comment