Das ROOT Framework aus softwarearchitektonischer Sicht
Das Fachgebiet der experimentellen Teilchenphysik beschäftigt sich mit der Suche und dem Nachweis neuer Teilchen und der Vermessung ihrer Eigenschaften. In großen Beschleunigern werden stabile Teilchen mit hohen Energien zur Kollision gebracht. Aufgrund der Äquivalenz von Energie und Masse können dabei neue Partikel erzeugt werden, deren Spuren und Energiedepositionen mit geeigneten Detektoren gemessen werden. Aus diesen Messungen lassen sich Parameter wie Impuls, Ladung, Masse etc. ableiten. Die dabei aufgezeichneten Daten müssen von Physikern verknüpft, analysiert und visualisiert werden.
Das Softwareframework ROOT, das dabei zum Einsatz kommt, ist ein Musterbeispiel für misslungene Softwarearchitektur. Die Behauptung, es handle sich um ein System, das objektorientierte Paradigmen konsequent umsetzt, ist nicht haltbar. Betrachten wir als Beispiel die Vererbungshierarchie der Histogrammklassen.

Die erste Frage, die sich stellt, ist, warum das zweidimensionale Histogramm TH2 vom eindimensionalen TH1 erbt. Dies wäre durchaus verständlich, wenn TH2 lediglich gemeinsame Funktionalität von TH1 übernehmen würde. In Wirklichkeit besitzt jedoch schon TH1 eine virtuelle Funktion GetYAxis() und sogar ein GetZAxis(), da in der weiteren Hierarchie auch TH3 von TH2 und damit von TH1 erbt.
Viel interessanter als dieser (schon recht verwunderliche) Umstand ist jedoch die Tatsache, dass alle Histogrammklassen direkt oder indirekt von den “Attributklassen” TAttLine, TAttFill und TAttMarker erben. Diese beschreiben Eigenschaften von Linien, Flächen oder Symbolen wie Strichstärke, Farbe, Muster o.Ä. Hier wird die eigentlich einer Vererbung zugrunde liegende is-a-Relation ad absurdum geführt. Ist ein Histogramm eine Linie? Ist es eine Füllfläche? Oder ist es vielleicht ein Marker? Nein, es besitzt vielmehr einen Satz von Linien-, Flächen- und Markereigenschaften. Die korrekte Umsetzung dieser Relation würde darin bestehen, den Histogrammklassen Membervariablen vom Typ TAttLine, TAttFill und TAttMarker zu geben. Um Schreibarbeit bei der späteren Verwendung der Objekte zu sparen, wurde jedoch kurzerhand die Vererbung gewählt. Was die Konsequenzen davon sind, lässt sich leicht an dem folgenden Vererbungsdiagramm aus der ROOT Dokumentation erkennen:

Man beachte insbesondere die Anmerkung “…and more”. Dieser Vererbungswahnsinn ist nicht nur unpraktisch, da er das Einbinden der entsprechenden Header in jeder der erbenden Klassendefinitionen erfordert (und im Übrigen auch die Verwendung von Pimpl unmöglich macht), sondern sogar gefährlich, da er die starke Typisierung von C++ durch übertriebenen Polymorphismus aufweicht. Dies ist jedoch nur ein willkürlich herausgegriffenes Beispiel. Im gesamten Framework wird konsequent vererbt, wo Vererbung nicht notwendig und teilweise sogar unerwünscht ist.
Ein weiterer sträflich vernachlässigter Punkt ist die Trennung von Daten und Darstellung. Warum muss TH1 überhaupt Informationen über Strichstärke und Füllfarbe speichern, wenn seine eigentliche Aufgabe die Speicherung der Histogrammdaten ist? Diese beiden disjunkten Aufgabenbereiche in einer Klasse zu kombinieren mag vielleicht den Vorteil haben, nur mit einem Objekt interagieren zu müssen. Schon bei der Serialisierung stellt sich aber die Frage, ob man wirklich zu jedem Histogramm vorgegebene Darstellungsattribute abspeichern will. Sinnvoller wären Painter Objekte, die die Darstellungsinformationen enthalten und beliebige Histogrammdaten zeichnen können. Auch diese Objekte könnte man für eine spätere Wiederverwendung serialisieren und nötigenfalls Histogramme mit zugehörigen Paintern verknüpfen.
Es gibt jedoch auch positive Aspekte des ROOT Frameworks, die hervorgehoben werden sollten. Für die Serialisierung von Objekten bietet ROOT einen relativ komplexen Mechanismus, der auf der Generierung von Klassen-Metadaten mit Hilfe eines speziellen Codeprozessors basiert. Die generierten Klassen beschreiben Membervariablen und Methoden der zu serialisierenden Klassen und erlauben so eine automatische Persistierung bzw. Erstellung von Objekten aus persistierten Daten. Gleichzeitig erlaubt dieser Mechanismus auch den Zugriff auf die Klasseninformationen, um Benutzerinteraktion über einen speziellen C++ Interpreter zu ermöglichen. Alle Klassen, für die Metadaten generiert wurden, können so im Interpreter instanziiert und verwendet werden.
