Bernhard: Konkretes Beispiel

Beitrag lesen

Mahlzeit,

um also etwas konkreter zu werden, was so meine Probleme sind, hier ein aktuelles Beispiel aus der realen Welt. Das Beispiel ist mMn wirklich gut, weil die Programmierung grundsätzlich niemandem technische Schwierigkeiten bereiten sollte, aber das ganze sauber, flexibel und erweiterbar hinzukriegen ist dann (zumindest leider für mich) gar nicht mehr so einfach. Die Anforderungen lauten:

* Der Benutzer gibt 0 bis n Eingabedateien im CSV-Format (eigentlich ist es kein CSV-Format, aber sehr ähnlich) an (und zu jeder Datei die verwendete Zeichenkodierung).
* Der Benutzer gibt eine XML-Ausgabedatei an (Kodierung immer UTF8, die Datei muss einem fix vorgegebenem Schema genügen).
* Der Benutzer gibt an, ob die Ausgabedatei überschrieben werden soll oder ob bestehende XML-Elemente aktualisiert (bzw. neue neu eingefügt) werden sollen.

* Die Software liest die Eingabedateien, baut aus jeder Zeile in den CSV-Dateien ein XML-Element und fügt dieses in die (neue oder bereits bestehende) Ausgabedatei ein bzw. aktualisiert das entsprechende XML-Element, sofern es bereits in der Ausgabedatei vorhanden ist.
* Die Software wird grundsätzlich per Kommandozeile bedient, soll aber auch eine GUI erhalten.
* Das Format der CSV-Dateien kann variieren: Z.B. können sich Reihenfolge und Anzahl der Spalten oder das Spaltentrennzeichen ändern. Optimalerweise ist die Software einfach dahingehend anpassbar (durch Implementierung neuer Klassen und Neukompilation), dass sie beliebig strukturierte Textdateien zeilenweise parst und aus den Zeilen XML-Elemente bastelt.
* Das Format der Ausgabedatei (d.h., das Schema, dem die XML-Datei entsprechen muss) ändert sich nie.
* Weitere Rahmenbedingungen (sind eigentlich, da die Aufgabe technisch einfach ist und es mir nur um das Design geht, hier irrelevant): Programmiersprache ist C#, die GUI wird per WinForms erstellt; Windows only.

Um sowas sauber zu designen, muss unbedingt das UI von der Programmlogik entkoppelt werden. Mein Ansatz dazu ist bisher ein Command pattern. Die jeweilige UI ist der Klient, Aufrufer eine Klasse namens "Transformer", mit einer Transform()-Methode. Der Klient erzeugt einen Aufrufer und ein Kommando-Objekt (das eine Execute()-Methode anbietet), übergibt letzteres dem Transformer und ruft Transform() auf. Empfänger gibt es keinen, die Logik ist vollständig im Kommando enthalten. Für jedes Eingabeformat gibt es also ein Kommando, dass das Format kennt und lesen kann. Kommt ein neues Eingabeformat hinzu, so muss ich ein neues Kommando implementieren.

Ein Kommando tut folgendes:
* Es deserialisiert die Ausgabedatei (falls es diese schon gibt) in eine Objekthierarchie.
* Es parst anschließend die Eingabe-Datei(en) und fügt für jede Zeile ein Objekt in die Hierarchie ein bzw. aktualisiert ein bereits bestehendes Objekt.
* Es serialisiert die aktualisierte oder völlig neu erstellte Objekthierarchie in die Ausgabedatei.
* (Falls sich jemand wundert, warum (de)serialisiert wird: Das ist eine sehr elegante Methode, um XML-Dokumente gemäß einem Schema zu erstellen.)

Das ist jetzt alles ganz schön und gut, nur reicht es halt hinten und vorne nicht. Konkret gibts 2 bzw. 3 Probleme.

1. Problem: In den Kommando-Objekten kommt immer irgendwo eine Schleife von ungefähr dieser Form vor:

while ((line = stringReader.GetLine()) != null) {  
  string[] tokens = line.Split(';'); // Zeile parsen  
  // ... Zeile weiter verarbeiten.  
}

Für mich ist es naheliegend, die Schleife in eine Methode public List<MyXmlObject> GetObjects() einzubetten. Das Problem dabei ist: Natürlich will ich sowohl in der CLI- als auch in der GUI-Variante (in einem Textfeld o.ä.) eine Statusausgabe, die mir sagt, wenn eine Zeile nicht geparst werden konnte. Nachdem das auch mit der GUI funktionieren soll, ist nix mit WriteLine("Fehler in Zeile xyz"); in der Schleife.

Eine saubere Lösung dafür fällt mir nicht ein. Wie mache ich das??

2. Problem: Wenn irgendwo in den Transformer- oder Kommando-Objekten Exceptions auftreten, müssen die ebenfalls mit beiden GUI-Varianten sauber ausgegeben werden. Ich muss irgendwo einen Layer in meiner Software definieren, der alle Exceptions abfängt und auf eine Weise an den aufrufenden Layer zurückgibt, die für CLI und GUI passt.

Auch hier stehe ich an: Wo baue ich die catch-Klauseln ein und wie gebe ich die Fehlermeldungen an das UI zurück??

3. Problem (eigentlich kein Problem, nur ein ungutes Gefühl): Alle Kommando-Objekte sind irgendwie "gleich": Klar, die Execute()-Methode unterscheidet sich möglicherweise sehr stark, aber das UI tut immer

transfomer.Command = new MyCommand(string inputFile, string encoding, string outputFile, bool overwrite);

oder

transfomer.Command = new MyOtherCommand(string inputFile, string encoding, string outputFile, bool overwrite);

D.h., die Konstruktorparameter für die Commands sind immer gleich. Ich kann zwar nicht konkret sagen, was damit nicht stimmt, aber irgendwie habe ich kein gutes Gefühl dabei...

Soviel also zu konkreten Problemen. Ist kein ganz kurzer Text, also danke an alle, die ihn durchgelesen haben. Vielleicht hat ja jemand Vorschläge, wie man das schön lösen kann.

Grüße
Bernhard