Flash-Engineering, Teil 1 – wichtige Eigenschaften für Ihr Flash-Projekt

Kein Beitragsbild

Was sind die Ziele eines Flash-Entwicklers innerhalb eines Projekts? Das primäre Ziel ist natürlich, dass die Anwendung die in den Anforderungen definierten Aufgaben erfüllt unter Beachtung aller Randbedingungen – und das innerhalb des Zeitplans und unter Einhaltung des zur Verfügung stehenden Budgets. Das allein ist schon schwer genug, und in vielen Projekten wird nicht mal dieses Ziel komplett erreicht. Projekte werden zu spät fertig oder zusammen gestrichen, damit der Zeitplan eingehalten werden kann, Budgets werden überschritten.

Aber neben diesem primären Ziel gibt es immer auch noch sekundäre Ziele, die für ein Projekt wichtig sein können. Nicht immer sind alle von gleicher Bedeutung, aber zu einem gewissen Anteil spielen sie alle eine Rolle. Die klassischen, zugegebenermaßen sehr allgemein formulierten sekundären Ziele sind:

  • Wartbarkeit,
  • Erweiterbarkeit,
  • Wiederverwendbarkeit und
  • Robustheit.

Letztlich bieten sie eine Sicht auf das, was uns antreibt, wenn wir versuchen, gute Software zu bauen. Die vielen Maßnahmen und Instrumente des Softwareentwurfs wie zum Beispiel Abstrahierung, Kapselung oder Entkopplung entspringen also den oben genannten Zielen.

Dieser Artikel stammt aus dem Buch Flash-Engineering Stundentenausgabe von Sven Busse und ist ein Gastbeitrag des ADDISON-WESLEY-Verlags.

Wartbarkeit

Es gibt sicherlich Projekte, bei denen man weiß, dass ihre Laufzeit sehr begrenzt ist. Eine kleine unterhaltende Anwendung, die auf einer Messe einmalig gezeigt und danach nicht benutzt wird, hat also eine sehr begrenzte Laufzeit. Eine solche Anwendung muss nicht unbedingt besonders wartungsfreundlich sein, denn es handelt sich dabei mehr oder weniger um Wegwerfsoftware.

Längere Lebenszeit bedeutet mehr Wartung und Pflege

Viele andere Anwendungen hingegen haben eine sehr viel längere Laufzeit, mehrere Monate oder sogar viele Jahre. Solche Anwendungen werden unter Umständen sehr intensiv genutzt, vielleicht auch von einer großen Nutzermenge. Bei solchen Anwendungen bleibt es nicht aus, dass entweder immer wieder noch Fehler in der Anwendung zutage treten oder dass die Nutzer Änderungs– oder Erweiterungswünsche haben. Als Entwickler bedeutet dies, man wird solch ein Projekt nicht so schnell los und muss sich immer wieder damit beschäftigen, Teile der Anwendung ändern, Fehler ausmerzen und vieles mehr.

Dazu kommt, dass bei einer längeren Laufzeit einer solchen Anwendung die Chance groß ist, dass die Entwickler, die ursprünglich die Anwendung entwickelt haben, inzwischen nicht mehr im Unternehmen oder in der Agentur arbeiten. Es müssen also immer wieder neue Entwickler eingearbeitet werden. Eventuell ist die Anwendung sogar so konzipiert, dass Dritte die Möglichkeit haben, eigene Erweiterungen oder Veränderungen zu entwickeln, was die Sache noch zusätzlich verkompliziert. Gerade bei Projekten, wo nicht direkt eine Anwendung, sondern vielmehr eine Bibliothek entwickelt wurde, ist dies der Fall.

Kennst du unser E-Book-Bundle? Spare jetzt 6,99 €!

E-Book Bundle von Andreas Hecht

Das Warten und Betreuen bestehender Anwendungen oder Codeteile sind für die meisten Entwickler einfach nur lästig. Niemand möchte sich wirklich mehr mit den alten Kamellen beschäftigen, oft findet man im Nachhinein bestimmte Teile im Code nicht gut gelöst, oder man würde die Struktur mittlerweile ganz anders umsetzen. Sehr oft ist es auch einfach der Fakt, dass die Struktur mit der Zeit gewachsen und inzwischen immer schwieriger zu durchblicken ist.

Mangelnde Dokumentation erschwert die Wartung durch Dritte

All diese Probleme verschlechtern also die Wartbarkeit einer Anwendung oder eines Codeteils. Nun ist die Wartbarkeit aber in den seltensten Fällen eine konkrete Anforderung des Kunden. Es ist vielmehr eine schwierig zu greifende Anforderung, die sich in vielen Details der Struktur und auch der konkreten Implementierung einer Anwendung widerspiegelt. Ich bezeichne es deswegen als eine sekundäre Anforderung.


Alter Anwendungscode kann manchmal schwer zu durchdringen sein.

Merkmale einer gut wartbaren Anwendung

Was macht nun aber eine gut wartbare Anwendung aus, wodurch wird eine Anwendung oder ein Codeteil gut wartbar? Man könnte ja ganz platt sagen, eine Anwendung, die fehlerfrei ist, ist auch automatisch gut wartbar, denn solange keine Änderungswünsche rein- kommen, muss sie gar nicht gewartet werden. Das lassen wir aber nicht gelten, denn man kann nicht einfach eine Eigenschaft herstellen, in dem man sich Störfaktoren wegdenkt. Anwendungen sind nie völlig fehlerfrei. Vielmehr wäre Fehlerfreiheit ein wichtiges Merk- mal, bevor die Anwendung überhaupt veröffentlicht wird. Danach müssen wir einfach davon ausgehen, dass ab und zu noch Fehler auftauchen werden.
Fragen wir uns erst einmal, was für Tätigkeiten wir bei der Wartung und Betreuung von Anwendungen eigentlich ausführen, um das Feld der Wartbarkeit ein wenig einzukreisen.

Lesbarkeit

Zuallererst müssen die für die Betreuung zuständigen Entwickler einmal verstehen, wie die Anwendung eigentlich aufgebaut ist und wie sie funktioniert. Hier ist natürlich klar, dass sie nicht zwingend sofort jedes Detail kennen müssen, aber es muss für sie möglich sein, mit so wenig Aufwand wie möglich die Funktionsweise eines Details der Anwendung kennenzulernen. Es geht hier also um Begriffe wie:

  • Übersichtlichkeit,
  • Verständlichkeit,
  • Lesbarkeit.

Um optimale Wartbarkeit zu erzielen, müssen wir vom schlimmsten Fall ausgehen, also von einem Entwickler, der zuvor noch nichts mit der Anwendung zu tun hatte und sich dem Code zum ersten Mal nähert, bewaffnet nur mit einer groben Vorstellung von der Anwendung, die er vielleicht durch das Ausprobieren, sprich Ausführen der Anwendung erhalten hat, sowie vielleicht einer Handvoll Fachkonzepte und anderer Dokumente, von denen wir hoffen, dass sie wenigstens halbwegs korrekt und aktuell sind.

Eine Methode, eine Aufgabe

Was können die Anwendung, der Code und die Struktur nun tun, um übersichtlich, verständlich und lesbar zu sein? Letztlich zahlen die meisten Praktiken solider Softwareentwicklung nebenbei auch auf eine gute Lesbarkeit ein. Nehmen wir etwa den Vorsatz, dass eine Methode möglichst immer nur eine Aufgabe erfüllen soll und nicht mehrere Dinge implizit hintereinander. Dieser Vorsatz hat vordergründig erst einmal den Sinn, dass sich so eine bessere Entkopplung von Verantwortlichkeiten ergibt und außerdem eine sequenzielle Abhängigkeit innerhalb so einer Methode vermieden wird. Aber der angenehme Nebeneffekt ist auch, dass eine Methode, die einen sprechenden Namen hat und auch wirklich nur eine klar umrissene Aufgabe erfüllt, meist deutlich einfacher zu verstehen ist als eine Methode, die in sich nacheinander oder auch vermischt viele Dinge auf einmal macht.

Praxis-Tipp: Ein guter Hinweis, dass man als Entwickler gerade dabei ist, solch eine Gemischtwarenladen-Methode zu schreiben, ist übrigens, wenn einem dafür partout kein vernünftiger Name einfallen will, der kürzer als 16 Zeichen ist. Grundsätzliche Punkte zum Thema gute Lesbarkeit sind die folgenden:

Struktur, die das Konzept aufgreift: Wenn wir davon ausgehen, dass ein Entwickler von der Anwendung, die er betreuen soll, nur ihre äußerliche Funktionsweise kennt, weil er die Anwendung in Aktion gesehen hat und weil ihm ein fachliches Konzept zur Verfügung steht, dann hat er also auch nur eine fachliche Vorstellung von der Anwendung. Demzu- folge ist es äußerst hilfreich, wenn sich die fachlichen Konzepte in der Struktur der Anwen- dung wiederfinden, denn so kann der Entwickler sofort eine gedankliche Verbindung zwischen den Dingen herstellen, die er aus dem fachlichen Konzept her kennt, und dem Code. Domain Driven Design ist hier einer der Schlüsselbegriffe. Die Anwendung wird also nicht primär nach technischen Gesichtspunkten strukturiert, sondern nach konzeptionellen, nämlich denen aus der Problemdomäne. Wenn es also in der Problem- oder Aufgabendo- mäne um Film, Videos und Ausleihverträge geht – wie in unserem Beispielprojekt FilmRegal –, dann würde es der Lesbarkeit dienen, wenn sich im Code der infrage kommenden Anwendungen diese fachlichen Objekte auch wiederfinden.

Sprechende Struktur, sprechende Namen: Eine einfach lautende Regel, aber oft nicht beherzigt. Paketnamen, Klassen, Interfaces, Methoden und Attribute sollten Namen haben, mit denen man auch etwas anfangen kann. Akronyme, Abkürzungen und dergleichen mögen den initialen Schreibaufwand verringern, aber Steve McConnell bringt es treffend auf den Punkt, wenn er sagt: “Programmcode wird viel öfter gelesen als geschrieben … Sie sparen am falschen Ende, wenn Sie das Schreiben von Code erleichtern, aber das Lesen dadurch schwieriger wird.”
Unter sprechenden Namen und einer sprechenden Struktur versteht man aber auch, dass diese Bezeichner einen wirklichen Aufschluss darüber geben, was die Verantwortlichkeit einer Klasse oder eines Interface ist oder was die Aufgabe einer konkreten Methode. Eine oft anzutreffende Eigenheit bei Flash-Anwendungen ist es zum Beispiel, dass Eventhandler nach dem Event bezeichnet werden, durch das sie aufgerufen werden, anstatt dass sie danach benannt werden, was sie tun. Ein Eventhandler mit dem Namen »onClick« verrät leider rein gar nichts darüber, was er eigentlich macht, sondern lediglich, wann er aufgerufen wird. Ein Entwickler muss nun also den gesamten Code durchforsten, um die Aufgabe der Methode herauszufinden. Zudem enthält die Bezeichnung eine stärkere Kopplung zur Klasse, die das Event wirft, denn wenn das click-Event später mal gegen ein press-Event ausgetauscht werden soll, müsste eigentlich der Name des Eventhandlers geändert werden, obwohl sich eventuell in der Methode gar nichts ändert. Ändert man den Namen nicht, wird die Verwirrung nur noch größer. Eine Lösung kann sein, dass man den Eventhandler neutraler benennt, etwa “onButtonAction”. Zudem kann man im Eventhandler einfach eine weitere Methode aufrufen, die die eigentliche Aufgabe übernimmt. Ein Entwickler kann nun den Zusammenhang schnell erkennen zwischen einem aufgetretenen Event und der daraus erfolgenden Aktion.

Oft sieht man auch Konventionen, die einem Interface ein »I« voranstellen und die eine Klasse, die das Interface implementiert, dann ohne das I schreiben oder eventuell ein »Impl« hintanstellen. In solchen Fällen muss geprüft werden, ob nicht fachlich eine bessere Trennung von Interface und konkreter Klasse angebracht wäre, die eine bessere Benennung ermöglichen würde.

Nehmen wir folgendes Beispiel: Um Videos in Flash abzuspielen, können wir entweder direkt eine FLV-Datei streamen, oder wir können ein Video in eine SWF-Datei einbetten und diese progressiv streamen oder ganz vorladen. Für beide Fälle könnten wir nun jeweils eine Klasse schreiben, die das eine oder das andere kontrolliert. Ein Videoplayer, der nun ein Video abspielen will, könnte nun entweder die eine oder die andere Klasse nutzen. Um die Benutzung zu vereinheitlichen, könnte der Videoplayer ein Interface definieren, in dem er angibt, wie er gerne ein Video benutzen möchte. Und die beiden oben genannten Klassen könnten das Interface imple- mentieren, um dem Videoplayer dann ihre Dienste anbieten zu können. Nehmen wir an, wir bauen nun erst einmal das Interface. Wie sollen wir es nennen: »IVideo«? Und die FLVStream-Variante dann »VideoImpl«? Geht nicht, wie sollten wir sonst die SWF-Variante nennen.

Es besteht in diesem Fall kein Grund, das Interface mit einem “I” zu versehen. Der Videoplayer will Videos spielen, also nennt er sein Interface “Video”. Und die FLV-Variante benötigt kein Impl, sondern sie kann sich zum Beispiel “StreamVideo” nennen und die SWF-Variante dann “SWFVideo”. Nun mögen manche einwenden, dass durch diese Namen nicht klar wird, welches Interface diese Klassen implementieren. Wenn wir aber davon ausgehen, dass es normalerweise immer mehrere Klassen gibt, die ein Interface implementieren (sonst ist eventuell das Interface überflüssig), dann müssten diese Klassen folglich alle gleich heißen, was offensichtlich keine gute Strategie sein kann, denn nun wird einem nicht mehr klar, worin sie sich denn unterscheiden.

Dokumentation: Im Zusammenhang mit der Lesbarkeit von Code ist eine solide Dokumentation unerlässlich. Um die Wartbarkeit von Code zu erhöhen, ist es empfehlenswert, komplexere Bereiche im Code gesondert zu dokumentieren, damit andere Entwickler im Falle von notwendigen Änderungen oder bei der Fehlersuche schneller die Funktionsweise verstehen.

Darüber hinaus ist das Dokumentieren von Code mit JavaDoc-kompatiblen Kommentaren unerlässlich. Gerade zusammen mit heutigen IDEs wie Flex Builder und anderen werden diese während des Tippens dem Entwickler schon präsentiert und erleichtern die Entwicklung ungemein.


Benennung bei Interfaces: “I” ist nicht unbedingt notwendig.

Typisierung steigert die Lesbarkeit
Man kann natürlich auch zu viel dokumentieren. Eine Methode mit dem Namen getXPosition():int verrät im Prinzip alles Wissenswerte schon über ihre Deklaration, hier muss nicht mehr viel dokumentiert werden. In diesem Zusammenhang sei erwähnt, dass auch Typisierung enorm zur Lesbarkeit von Code beiträgt. Im obigen Beispiel verrät der Rückgabewert einiges darüber, was man von der Methode erwarten kann, z. B. dass nur ganze Pixelwerte ausgegeben werden. Würde man den Typ weglassen, würde einiges an Information verloren gehen.

Veränderbarkeit

Neben der Lesbarkeit spielt für die Wartbarkeit auch noch die Veränderbarkeit eine Rolle. Während des Lebenszyklus einer Anwendung wird an ihr immer wieder herumgeschraubt, auch wenn man das eigentlich ja vermeiden will. Der Grundsatz eines Softwareentwicklers heißt ja normalerweise: »Never touch a running system«, oder auch: »Repariere nichts, was nicht kaputt ist«. Aber über die Zeit verändern sich Anforderungen im Detail, die Arbeit mit der Anwendung enthüllt eventuell konzeptionelle Fehler oder auch technische Fehler, und die Folge ist, die Anwendung muss verbessert oder verändert werden.

Die Veränderbarkeit ist nun eine Eigenschaft, die sich in vielen Bereichen des Softwareentwurfs, aber auch der konkreten Implementierung niederschlägt. Im Entwurf ist die Modularität ein starkes Merkmal, das sich auf die Veränderbarkeit auswirkt. Je modularer, sprich stärker voneinander autark einzelne Module und Komponenten in der Anwendung gestaltet sind, desto einfacher kann man eine Änderung innerhalb eines Moduls machen, ohne dass sie sich zwangsläufig auf die anderen Module auswirkt. Und das ist ein hohes Gut bei der Veränderbarkeit: Änderungen machen zu können, ohne dass die ganze Anwendung gleich wie ein Kartenhaus in sich zusammenfällt.

Praxisbeispiel Textparser

Nehmen wir als Beispiel mal einen Parser, der eine Textdatei einlesen und seine Struktur in eine Objektstruktur parsen soll. Dieser Parser, der seinerseits Teil einer Anwendung ist, soll vielleicht optimiert werden, weil man gemerkt hat, dass er mit Textdateien ab einer gewissen Größe nicht mehr schnell genug vorankommt. Wenn nun dieser Parser eine klar definierte Schnittstelle hat, die seine innere Funktionsweise nach außen nicht preisgibt, dann stehen die Chancen gut, dass man seinen inneren Parsing-Algorithmus verändern kann, ohne etwas an der Schnittstelle nach außen ändern zu müssen. Das wiederum würde dazu führen, dass alle anderen Teile der Anwendung, die diesen Parser nutzen, nicht angefasst werden müssen, was ja das Ziel sein muss, um den Aufwand so gering wie möglich zu halten.

Was aber könnte man nun falsch machen, was würde zu einer schlechten Veränderbarkeit führen? Nun, der Parser könnte fälschlicherweise bestimmte Methoden oder Attribute, die Auskunft über seine innere Funktionsweise geben, öffentlich machen. Viele Parser, die Baumstrukturen parsen, verwenden rekursive Funktionen, um den Baum durchzugehen. Dabei übergibt man an die Funktion den aktuellen Knoten im Baum, und als Rückgabewert gibt die Funktion den geparsten Objektknoten zurück. Wenn diese Funktion public ist, besteht die Gefahr, dass sie von anderen Teilen der Anwendung vielleicht direkt verwendet wird, um “mal eben schnell” einen kleinen Teilbaum zu parsen. Dies führt nun dazu, dass es nicht mehr möglich ist, diese rekursive Funktion zu ändern oder gar gegen einen anderen nichtrekursiven Ansatz auszutauschen, weil er ja nun schon verwendet wird. Weiter unten im Abschnitt Kapselung werde ich über das Verstecken innerer Funktionalitäten einer Klasse noch detaillierter sprechen.

Testbarkeit

Einen letzten Bereich möchte ich im Bereich der Wartbarkeit noch ansprechen, die Testbar- keit. Logisch, wenn man eine Anwendung wartet und an ihr arbeitet, Fehler ausbessert, Optimierungen durchführt, muss man hinterher testen, ob auch noch alles funktioniert. Nun mag man sich fragen, was sich hinter Testbarkeit verbirgt. Testbar ist doch jede Anwendung, oder nicht? Mit Testbarkeit ist hier die Feingranularität gemeint, mit der man in einer Anwendung die einzelnen Teile zum einen isoliert voneinander und zum anderen im Zusammenspiel miteinander testen kann.

Bleiben wir beim obigen Beispiel des Textparsers. Wir haben ihn verändert und müssen nun testen, ob die Anwendung auch immer noch genauso funktioniert, wie es gewünscht ist. Auch hier spielt die Modularität und Kapselung wieder eine große Rolle. Je weniger Abhängigkeiten der Parser zu anderen Teilen der Anwendung hat, je einfacher und klarer seine Schnittstellen sind, umso besser kann ich ihn isoliert testen.

Wenn mein Parser beispielsweise noch fünf andere Klassen benötigt, um seine Arbeit zu verrichten, bedeutet dies, dass ich diese fünf anderen Klassen indirekt auch mittesten muss. Nun könnte man argumentieren, wenn man an diesen Klassen nichts verändert hat, können dort ja auch keine Fehler auftauchen. Die Veränderung im Parser kann aber sehr wohl Fehler in den anderen Klassen enthüllen, die früher nur einfach nicht in Erscheinung getreten sind, als der Parser noch anders gearbeitet hat.

Je mehr Mock Objects, desto schlechter die Modularität

Viele Abhängigkeiten zwischen Klassen erschweren das Testen beziehungsweise das Finden von Fehlern, wenn welche auftreten. Klar voneinander getrennte Klassen oder Komponenten mit einfachen Schnittstellen erleichtern es hingegen enorm. Besonders deutlich wird dies, wenn man automatisierte Tests, wie UnitTests, schreibt. Bei Unit-Tests besteht ja immer die Möglichkeit, sogenannte Mock Objects zu schreiben für Klassen, die die zu testende Klasse eigentlich benötigt, die man aber nicht direkt mittesten will. Je mehr Mock Objects man schreiben muss, um eine Klasse oder eine Komponente zu testen, desto eher ist das ein Anzeichen für eine schlechte Modularität. Und der Aufwand steigt natürlich immens. Denn diese Mock-Objekte simulieren andere Klassen. Wenn man aber nun diese anderen echten Klassen verändert, müssen die Mock-Objekte auch geändert werden. Das kann recht schnell in viel Arbeit ausarten.

Im nächsten Teil der Serie geht es um:
  • Erweiterbarkeit
  • Kapselung
  • Ermitteln der erweiterbaren Punkte
(mm),

Sortiert nach:   neueste | älteste | beste Bewertung
Hannah
Gast

Danke! Ich bin ganz am Anfang mit Flash jetzt und habe dein Blogpost sehr nutzbar gefunden!

Manfred
Gast

Auch ich bin noch neu in der Materie aber habe bisher so einige Infos mitnehmen können. Danke, gute Arbeit.

wpDiscuz