Aus Linux-Magazin 03/2010

Praxisbericht: Sanierung grafischer Benutzeroberflächen richtig planen

Auch Software kommt in die Jahre, mindestens ihr Äußeres. Fortschritte in der GUI-Entwicklung hinterlassen Spuren. Was für die Grundsanierung nötig ist, berichtet Matthias Kalle Dalheimer, KDE-Geburtshelfer, Buchautor und Chef des Migrationsspezialisten KDAB.

Wenn Unternehmen beschließen alte Software zu modernisieren, dann sind das oft Windows-Programme, die nun in die Unix-Welt kommen sollen – oder umgekehrt. Ein weiterer Grund ist, dass niemand im Unternehmen mehr die Programmiersprachen, Bibliotheken oder Entwicklungswerkzeuge benutzt: Die alten Entwickler sind inzwischen pensioniert, ins Management befördert oder haben das Unternehmen verlassen. Und schließlich hat natürlich eine Softwarelösung mit dem Look & Feel der 1980er Jahre weniger Marktchancen – egal wie aktuell ihre zugrunde liegenden Algorithmen auch sind.

Solche Migrationen bergen einige Risiken. Um sie kontrollierbar zu halten, bietet sich ein von Prinzipien geleiteter Prozess an, denn Softwareprojekte dieser Art nehmen schnell Größenordnungen jenseits von 20 Mannmonaten an. Eines der Basisprinzipien einer solchen Migration ist, nie an zu vielen Schrauben gleichzeitig zu drehen. Der Austausch einer Bibliothek für grafische Oberflächen wie etwa Motif oder MFC durch Qt kommt einer Herztransplantation gleich, und dabei sollte es in diesem Projekt auch bleiben. Wer im Laufe eines solchen Migrationsprojekts neue Features implementiert, läuft Gefahr, das Projektrisiko untragbar zu erhöhen.

Keine Fehler beheben

Auftraggeber überrascht gelegentlich, dass auch das Beheben von Fehlern Risiken für das Projekt birgt. Offenbar war der Fehler ja für die Benutzer der Originalversion erträglich, ansonsten hätten sie längst darauf bestanden, ihn korrigieren zu lassen. Also ertragen sie ihn auch noch ein paar weitere Monate, bis die Migration abgeschlossen ist. Dann erst sollten die Entwickler wieder an das Beheben von Fehlern denken und neue Features implementieren.

Das heißt natürlich nicht, dass Software-Restaurateure blind für die Fehler und Mängel der Software durch ihre Migration schreiten. Aber statt der Versuchung zu widerstehen, gleich einzugreifen, sollten sie alle festgestellten Mängel und Verbesserungsvorschläge systematisch festhalten und dann nach abgeschlossener Migration angehen. Ein Issue Tracking System wie Bugzilla oder Jira erweist sich dabei als nützlicher Helfer.

Betroffene Softwarebesitzer fragen oft, ob sie an dieser Stelle moderne UI-Konzepte wie Werkzeugleisten, Baumansichten oder Tooltips einführen sollten. Alte Bibliotheken wie Motif oder MFC kennen einige dieser UI-Elemente nicht. Wer der Versuchung widersteht, seine Oberfläche gleich mit diesen Neuerungen aufzupolieren, bleibt auf der sicheren Seite, andernfalls erhöht sich das Projektrisiko auf ungebührliche Weise. Benutzer sind jahrzehntelang ohne farbige Icons in den Menüs ausgekommen und können es noch ein paar weitere Monate. Natürlich gilt auch hier: Alle Ideen, die das UI verbessern, sollten die Projektbeteiligten gewissenhaft festhalten, hinterher systematisch durchsehen und gegebenenfalls umsetzen.

Die oft gehegte Hoffnung, einen möglichst großen Teil der Entwicklungsarbeit durch automatische Migrationswerkzeuge zu ersetzen, hat sich als trügerisch erwiesen. Jedes Werkzeug dieser Art deckt lediglich etwa 60 Prozent aller vorkommenden Fälle ab, wenn die Entwickler nicht prohibitiv viel Aufwand in die Entwicklung des Werkzeugs stecken. Die Fehlersuche in den verbleibenden 40 Prozent dauert erheblich länger, als wenn qualifizierte Entwickler manuell, systematisch und sequenziell – kurz und mit einem wohldefinierten Prozess – die Migration vornehmen.

Um eine Softwaresanierung trotz des aus dieser Erkenntnis folgenden höheren Personalaufwands sinnvoll durchzuführen, hilft es, den Entwicklern möglichst produktivitätssteigernde Hilfen an die Hand zu geben. Oft machen ganz einfache Mittel den Unterschied: Etwa das Wechseln auf Knopfdruck zwischen der alten Version, der Baseline, und der neuen, an der sie gerade arbeiten. Editormakros (siehe Kasten “Arbeiten mit Makros”) schreiben oft vorkommende Konstrukte, etwa eine »XtAddCallback()«-Methode in Motif, durch einen »QObject::connect()«-Aufruf in Qt um.

Arbeiten mit Makros

Richtig verfasst, funktionieren gute Makros sowohl mit reinem Qt als auch mit KDE. Sie wissen gar nichts davon, ob die Anwendung etwa ein »QLineEdit« oder eine Subklasse davon einsetzt. Sie leiten lediglich aus dem Namen der Motif-Ressource ab, um was für ein Widget es sich handelt. Können sie das nicht selbst entscheiden, fragen sie nach. Will etwa ein Programmierer einen Knopf von Motif nach Qt wandeln, wandelt er eine Zeile der Form

XtVaGetValues(RecordBtn,XmNset,&on,NULL);

durch sein Makro in

on = ui->RecordBtn->isChecked();

um. Ein Motif-Eingabefeld

XmTextGetString(_nameText);

wandelt entsprechend das Makro in den Code

ui->_nameText->text();

um. In allen Fällen ist »ui« ein Zeiger auf eine Instanz einer von Qt Designer generierten Klasse. Die mit einem Unterstrich beginnenden Variablen überträgt das Makro. Bei einem Listenelement ist Motifs API mächtiger, aus

XmListSelectItem(_lstAvailableRibs,
                 xmstr, true);

werden diese drei Anweisungen:

QList<QListWidgetItem*> items =
 ui->_lstAvailableRibs->findItems(
                xmstr, Qt::MatchExactly);
Q_ASSERT(!items.isEmpty());
items[0]->setSelected(true);

Das Beispiel ist gleichzeitig eine Warnung, den erzeugten Code intensiv zu kontrollieren und zu testen, weil hier die Semantik von Motif- und Qt-Listwidgets weit auseinandergeht.

Makros sollen nie automatisch oder in einer Schleife ablaufen, sondern einzeln vom Programmierer aufgerufen werden. Sobald er in einem Codeschnipsel ein Muster erkennt, wendet er dort das Makro an und prüft unmittelbar, ob die Konvertierung korrekt war.

Erfahrene Entwickler lassen den Code nicht automatisch konvertieren, sondern führen die Makros unter ihren Augen aus. Sie analysieren den Code Zeile für Zeile und entscheiden, welchen Code sie übernehmen und welchen nicht. Die Makros dienen nur dazu, immer wiederkehrende Tastenkombinationen durch einen einzigen Tastendruck zu ersetzen. Welche Entwicklungsumgebung diese Hilfsmittel bereitstellt, ist weitgehend Geschmackssache. Ich nutze meist Xemacs-Makros, aber es sprechen auch keine technischen Gründe dagegen, sie in Eclipse, Qt Creator oder Kdevelop zu implementieren.

Gute Werkzeuge

Es hat sich übrigens anhand von Datenerhebungen herausgestellt, dass tiefe Kenntnisse der Entwickler über die Ausgangsplattform eher zweitrangig sind: Was einzelne Aufrufe oder Konstrukte bedeuten, lesen sie gegebenenfalls in der Dokumentation nach. Wer den Quellcode studiert, parallel dazu die Anwendung ausführt und ihr Verhalten beobachtet, erschließt sich schnell die Bedeutung eines unbekannten Funktionsaufrufs.

Als wichtiger erweist sich die Kenntnis der neuen Umgebung. Aber auch diese Fertigkeit tritt hinter das Wissen um Migrationstechniken und -prozesse der Zielumgebung zurück. Die Produktivität und die Codequalität unterscheiden sich weniger zwischen einem Entwickler ohne Qt-Kenntnisse und einem Entwickler mit Qt-Kenntnissen als zwischen einem Entwickler mit Qt-Kenntnissen, aber ohne Migrationserfahrung mit Qt als Zielplattform, und einem Entwickler mit Qt-Kenntnissen, der eben diese Migrationserfahrung besitzt. Der Ausspruch “It’s not a tools problem!” fasst zusammen: Das Wissen um den Prozess ist wichtiger als das Wissen um das Werkzeug.

Ein weitere zentrale Frage einer jeden Migration ist die, wie man mit dem alten Code umgeht. Hier hat der Entwickler drei Strategien zur Auswahl: Er kann völlig von vorn anfangen und passende Schnipsel aus dem alten Code heraussuchen, die er in der neuen Codebasis weiterverwenden möchte. Zudem hat er die Option, mit Brückentechnologien zu arbeiten, die den Einsatz mehrerer, eigentlich konkurrierender Ansätze ermöglichen. Solche Integrationen existieren beispielsweise für Motif und Qt [1] sowie für MFC und Qt [2]. Und drittens hat er die Wahl, den alten Code zunächst komplett zu übernehmen und ihn Stück für Stück zu migrieren, indem er unpassende Codestücke entfernt und durch neue ersetzt.

Die erste Technik kommt einem völligen Neuschreiben sehr nahe, einer Strategie, vor der Joel Spolsky in seiner Kolumne “Joel on Software” eindringlich warnt. Als Beispiel führt er an, dass Netscape seinerzeit durch Neuschreiben des Navigators viel Zeit verlor und damit gewaltige Marktanteile an den Internet Explorer abgab [3]. Diese Technik ist zudem fehleranfällig, denn Entwickler werfen damit auch solchen Code weg, der eigentlich auch in der neuen Umgebung noch verwendbar wäre – etwa weil er gar nichts mit der Oberfläche zu tun hat und von vornherein auch auf dem neuen Zielsystem liefe.

Brücken schlagen

Auch die zweite Technik, der Einsatz von Brückentechnologien, birgt ihre Risiken. Diese Technologien sind alles andere als stabil. Das lässt sich ihren Entwicklern nicht vorwerfen, denn sie versuchen zusammenzubringen, was nicht zusammengehört. Programmierer in Migrationsprojekten kämpfen oftmals mehr mit den Brückentechnologien als mit der eigentlichen Migration.

Ich bevorzuge die dritte Lösung. Das Team fängt mit einer Kopie der Baseline an und arbeitet sie Stück für Stück um. Gegenüber der ersten Technik hat das den Vorteil, dass die Anwendung schnell wieder läuft – zumindest in Teilen. Das ermöglicht frühes Testen und reduziert das Projektrisiko: Fehler sind umso billiger zu beheben, je früher Tester sie im Entwicklungsprozess finden.

Parallel testet der Entwickler alte und neue Versionen. Vor dem Beginn eines Projekts ist ein umfassender Testplan nötig. Liegt der nicht vor, lehne ich sogar Projekte ab und dränge darauf, ihn in der ersten Projektphase zusammen mit Domänenexperten aufzustellen. Er darf ruhig einfach sein: Eine Tabelle mit Einträgen wie “Klicke hier, klicke da, erwarte diesen Wert in jenem Feld” reicht aus, solange sie den Funktionsumfang vollständig abdeckt.

Ein anderer Ansatz ist, von Domänenexperten und Benutzern eine Sammlung von Videos anfertigen zu lassen, die Anwendungsfälle zeigen. Lassen sich in der erneuerten Software alle Beispiele nachstellen, ist die Migration abgeschlossen. Automatisierte Testskripte mit Squish oder Expect ersparen manuelle Arbeit, wenn sie sowohl in der alten wie der neuen Umgebung laufen.

Wenn ich eine Migration beginne, versuche ich zunächst die Baseline auf einem feindlichen System zu übersetzen. Das wäre beispielsweise Linux für eine ursprüngliche Windows-Software. Im Notfall kompiliere ich auf einem System, auf dem die zu ersetzenden Bibliotheken und Tools nicht installiert sind – und überzeuge mich vorher davon.

Alles, was nun diesen ersten Brachialtest nicht übersteht, nimmt der Entwickler vorübergehend aus der Kompilation heraus, etwa durch das Entfernen von Dateien aus Makefiles, Auskommentieren mit Hilfe von Präprozessormakros oder ähnlichen Sprach- oder Umgebungsmitteln. Die Kommentarfunktionen der Zielsprache eignen sich dazu weniger. Das ergibt eine Version, die in der neuen Umgebung kompiliert, selbst wenn sie funktionslos ist. Es ist nicht ungewöhnlich, zu diesem Zeitpunkt mit nicht mehr als einem leeren »main()« dazustehen. Gleichwohl schließt das die erste Phase des Migrationsprojektes ab.

Fallstudie:
Rosegarden

Das Musikprogramm Rosegarden hat einige Entwicklungsschritte durchgemacht: Die Software, mit der Musiker ihre Werke erfassen, setzen, arrangieren und abspielen, hat seit ihrer Gründung 1993 mehrere Wandlungen durchlaufen. Weil die Entwickler das Programm für eine Unix-Umgebung programmierten, hatten sie nicht viel Auswahl an grafischen Toolkits. Die Xlib, Teil des Fenstersystems X11, bietet als kleinsten gemeinsamen Nenner nur einfachste Primitive wie Linien, Boxen und Texte. Die Erweiterung Athena-Widgets brachte zumindest rudimentäre Elemente wie Knöpfe und Regler (Abbildung 1). Auf dieser Basis implementierten die Entwickler rund um Ursprungsautor Chris Cannam eine Reihe eigener Widgets, um beispielsweise Noten darzustellen (Abbildung 3).

Das hatte seine Grenzen und viele Entwickler waren damit unzufrieden. So startete der Franzose Guillaume Laurent nach einer Reihe von Versuchen einen Rewrite zu KDE und Qt 2. “Wir waren uns sicher, dass wir von vorne beginnen mussten”, erinnert sich Laurent und warnt im Rückblick: “Wer eine Bibliothek erst patchen muss, bevor sie sich einsetzen lässt, sollte das am besten gleich lassen.”

Als sich sieben Jahre später der Wechsel zu KDE 4 hinzog (Abbildung 2), entschied sich das Projekt unter D. Michael McIntyre nach Abwägungen dazu, die KDE-spezifischen Teile komplett zu entfernen: “Das ist sauberer. Steht irgendwann eine Portierung zu Qt 5 an, müssen wir uns nur an einen Porting-Guide halten.”

Von Athena über KDE zu Qt 4

Binnen zweier Jahre arbeiteten elf Entwickler daran, zunächst alte Abhängigkeiten zu entfernen und die neue Version zum Übersetzen zu bewegen. “Anfangs sahen wir uns knapp 2 Millionen Compilerfehlern gegenüber”, erschauert McIntyre heute noch bei dem Gedanken an die ersten Schritte der Migration. Sein Team setzte zwar einige Werkzeuge ein, stellte aber schnell fest, dass die nur für einen ersten Schritt taugen. Kompatibilitätsbibliotheken zu Qt 3 mied das Projekt, um sich nicht in neue Abhängigkeiten zu begeben.

Viel Zeit kostete die Portierung jener KDE-Infrastruktur, die keine Qt-4-Entsprechungen hat. McIntyre nennt etwa das »KPartGUI«-Framework für Menüs und Toolbars. Nach rund einem Jahr, 90 000 Zeilen geändertem Code und 200 neuen Dateien ließ sich das Projekt wieder bauen (Abbildung 4). “Danach lief vieles einfacher, weil wir so mehr Entwickler anzogen”, erinnert sich der Projektmanager.

Nicht alle Komponenten ließen sich gleich einfach portieren, die Notendarstellung von »Q3Canvas« zu »QGraphicsView« etwa erforderte effektiv ein Rewrite. “Entwickler sollten sich nicht zu hastigen Entscheidungen hinreißen lassen”, warnt McIntyre. “Als wir nur noch wenige Fehler hatten, bauten wir weitreichende Bugs ein, die teuer zu beheben waren. Im Februar 2010 erwarte ich jedoch die vollständige Release von Rosegarden 10.”

Claims abstecken

Wichtig in diesem Zusammenhang ist es, alle solchen Entfernungen überdeutlich sichtbar zu machen. Gut eignen sich Präprozessormakros oder spezielle Kommentare. Bei C/C++-Migrationsprojekten verwende ich das Präprozessormakro »KDAB_TEMPORARILY_REMOVED«. Das ist fett, hässlich, sticht ins Auge und ich übersehe es später nicht. Noch wichtiger ist, dass sich das Makro nicht mit legitimen Kommentaren in der Baseline verwechseln lässt. Es kommt vermutlich nicht in der Baseline vor, aber um sicher zu gehen, suche ich zu Beginn des Projekts per rekursivem Grep danach.

Sobald sich Entwickler das erste Feature aus dem Testplan vornehmen, studieren sie die Baseline, um herauszufinden, welche Pfade der Code durchlaufen muss, um dieses Feature auszuführen. In vielen Anwendungen migrieren sie dazu zunächst das Hauptfenster. Diese Aufgabe lässt sich oft eher im Bereich von Manntagen bewerkstelligen. Wer bereits weiß, welchen – möglicherweise herausgenommenen – Code er zunächst benötigt, implementiert ihn Stück für Stück neu, bis das Feature in der Zielumgebung funktioniert. Verhält sich das Feature ebenso wie in der kompilierten Baseline, nimmt sich der Entwickler das nächste aus dem Testplan vor.

Diese Technik hat zwei Vorteile: Bereiche im Code, die gar keine Bearbeitung benötigen und die es fast immer gibt, machen keine Arbeit. Niemand versucht lauffähigen Code zu verändern. Das ist unnötig und erhöht das Risiko, Fehler einzuführen. Zum anderen bleibt am Ende der Migration meist noch herausgenommener Code übrig, wenn alle im Testplan beschriebenen Features auch in der neuen Version funktionieren.

Das hat zwei Gründe: Entweder war der Testplan nicht vollständig oder der restliche Code war bereits tot. Ersteres lässt sich nur feststellen, indem der Programmierer den Code analysiert und herausfindet, welche Interaktionen zu seinem Aufruf führen. Toter Code hingegen kommt oft vor: Selten beträgt sein Anteil unter 10 Prozent, mehr als 40 Prozent sind in realen Projekten durchaus möglich.

Im Kleinen beginnen

Werkzeuge, die Metriken über den herausgenommenen Code erheben, helfen den Projektaufwand abschätzen. Wer beispielsweise 500 000 herausgenommene Codezeilen, Klassen oder Dateien in 50 Wochen zu bearbeiten hat, berechnet so sein wöchentliches Pensum zur Wiederbelebung. Ganz linear wird der Fortschritt gleichwohl nicht sein: Am Anfang, beim langsamen Einlesen in die Baseline, und am Ende, beim Beheben von Fehlern, schreitet das Projekt langsamer voran als in der Mitte des Projekts, wenn die Entwickler aus allen Editoren feuern. So ist aber das Ende der Renovierungsarbeiten absehbar. (mg)

Infos

[1] Qt-MFC-Libs: [http://bit.ly/4ytMic]

[2] Qt-Motif-Libs: [http://bit.ly/8PM2TC]

[3] Joel Spolsky, “Things You Should Never Do, Part I”: Joel on Software:[http://www.joelonsoftware.com/articles/fog0000000069.html]

Der Autor

Matthias Kalle Dalheimer ist Gründer von Klarälvdalens Datakonsult AB. Das schwedische Unternehmen, das sich auf Training und Entwicklung von primär Qt-Software spezialisiert, hat schon 30 Million Zeilen Code migriert. Kalle hat bei vielen KDE-Projekten mitgearbeitet.

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben