SOLIDe Webentwicklung

Bild
Quelltext

Hand aufs Herz, wenn wir uns einmal den Quelltext von hundert aktuellen Webprojekten anschauen würden, würden wir feststellen, dass die Qualität der Software meist optimierungsbedürftig ist. Es gibt ein Qualitätsproblem in der Webentwicklung. Lange Zeit war es nicht möglich, mit Sprachen fernab von Java und C++ professionell zu entwickeln. Dies änderte sich 2005 zwar mit der Einführung von PHP5, mit der es zum ersten Mal möglich wurde wirklich objektorientiert zu entwickeln, jedoch ist die Webentwicklung leider vielfach ein Ort geblieben, an dem professionelle Entwicklungsmethoden nicht in dem Maße eingesetzt werden, wie dies bei anderen Sprachen, z.B. Java, der Fall ist. Dies ist schade, denn oftmals werden hier Chancen vertan und Projekte werden teurer als erwartet. Auch wenn hier manch ein Entwickler mit einem sonderbar niedrigen Stundenlohn lockt, letztendlich wird man von schlecht zu wartendem und fehleranfälligem Code immer wieder eingeholt und muss am Ende ein Mehrfaches dessen zahlen, was ein von Anfang an gut strukturierter Code gekostet hätte. Folgende Abbildung verdeutlicht dieses Problem:

code-qualität

 

Abbildung 1: Relative Kosten für eine Fehlerbehebung 1

Während sich Fehler in der Anforderungs- und Designphase noch sehr günstig beheben lassen, kostet das Beheben eines Fehlers während der Codierungsphase bereits 10 mal so viel. Muss man allerdings Fehler ausbessern, die sich in einem bereits fertig gestellten, aber sowohl schlecht entworfenem als auch schlecht programmierten Projekt befinden, muss man das 150 Fache dessen bezahlen, als wenn man es von Anfang an professionell gemacht hätte. So zahlt man am Ende auch für einen vermeintlich günstig gehaltenen Entwickler, der vielleicht nur 30 EUR die Stunde verlangt am Ende teilweise bis zu 4.500 EUR die Stunde. Ist das Projekt erst einmal in einem solchem Stadium angelangt, ist es ökonomisch nicht mehr sinnvoll auch nur irgend etwas daran zu verändern. Das Projekt ist dann gescheitert.  

Daher gilt: Auch Webanwendungen müssen SOLIDe entwickelt werden. SOLID sind 5 Prinzipien, die zu gutem, objektorientiertem Design führen. Diese möchte ich im Folgendem einmal kurz vorstellen.

Single Responsibility Prinzip

Das Single Responsibility Principle (SRP) 2 besagt, dass eine Klasse nur eine einzige, klar definierte Aufgabe haben sollte und nur solche Methoden beinhalten sollte, die mit dem Erfüllen dieser Aufgabe direkt in Verbindung stehen. Es darf daher niemals mehr als einen Grund geben, eine Klasse zu ändern.

Wenn eine Klasse mehr als eine Aufgabe hätte, würden sich diese Aufgaben miteinander verknüpfen. Veränderungen an einer Aufgabe würden die Möglichkeit der Klasse, die andere Aufgabe zu erfüllen, verschlechtern oder gar ganz unmöglich machen.

Open Closed Principle

Jede Software verändert sich während ihres Lebenszyklus. Dies muss man immer im Hinterkopf behalten, wenn man Software entwickelt, die länger als die erste Version leben soll 3. Wie kann man nun Software entwickeln, die tolerant gegenüber Anforderungsänderungen sind? Bertrand Meyer 4 gab mit seinem berühmten Open Closed Principle einen Lösungsansatz:

Software Entitäten (Klassen, Module etc.) sollten offen für Erweiterung sein, aber geschlossen für Veränderung

Wenn eine einzige Änderung an einem Programm dazu führt, dass eine Reihe von weiteren Änderungen am Programm notwendig werden, damit es noch funktioniert, haben wir einen klaren Beweis für schlechtes Design. Die Software wird zerbrechlich, fehleranfällig und nicht zuletzt teuer im Unterhalt. Das Open Closed Prinzip greift dieses Problem frontal an, indem man Module schreibt, die sich niemals verändern. Ändern sich die Anforderungen, erweitern man das Verhalten dieser Module durch das Hinzufügen von neuem Code, niemals jedoch durch das Abändern von bereits bestehendem Code!

Liskov Substitution Principle

Funktionen, die Referenzen zu Basisklassen benutzen, müssen in der Lage sein, Objekte von deren abgeleiteten Klassen ebenfalls benutzen zu können, ohne dies zu merken 5.Die Wichtigkeit dieses Prinzips wird am deutlichsten, wenn man über die Konsequenzen nachdenkt, wenn man gegen dieses Prinzip verstoßen würde. Dann nämlich müsste eine Funktion, die auf eine abgeleitete Klasse zugreift, alles über jede von der Basisklasse abgeleiteten Klassen wissen! So eine Funktion würde gegen das Open Closed Principle verstoßen, weil man diese Funktion ändern müsste, sobald eine neue abgeleitete Klasse eingeführt wird.

Nur wenn abgeleitete Klassen problemlos ersetzbar für ihre Basisklassen sind, können Funktionen, die diese Basisklassen benutzen, problemlos wiederverwendet werden und die abgeleiteten Klassen können ebenfalls problemlos verändert werden. Dadurch werden Applikationen wartungsfrei, wiederverwendbar und robust.

Interface Segregation Principle

Ein Client sollte niemals dazu gezwungen werden von Interfaces abhängig zu sein, die er nicht benötigt 2. Wenn ein Client gezwungen wird, von einem Interface abhängig zu sein, welches er nicht benötigt, dann wird der Client gezwungen sein sich zu ändern, sobald sich das Interface ändert. Dies führt zu einer unauflösbaren Kopplung zwischen allen Clients. Oder anders gesagt, wenn ein Client dazu gezwungen wird, von einem Interface abhängig zu sein, welches er nicht benötigt, welche andere Clients aber sehr wohl benötigen, wird der Client von Änderungen betroffen sein, die die anderen Clients der Klasse aufzwängen. Solche Kopplungen sollten in jedem Fall vermieden werden.

Durch die Benutzung des Adapter Entwurfmusters 6 durch Delegation (Objektform), können "fette Interfaces" in abstrakte Basisklassen aufgesplittet werden, die die unerwünsche Kopplung zwischen Clients aufbrechen.

Dependancy Inversion Principle

Das Dependancy Inversion Principle besagt, dass Abhängigkeiten im Code auf Abstraktionen beruhen sollten und nicht auf konkrete Implementierungen 2. Nehmen wir z.B. ein Programm, welches eine gewisse Texteingabe vom User erwartet und diese dann auf dem Drucker ausgeben soll. Nach Fertigstellung des Programms kommt noch die Anforderung hinzu, dass dieser Text nun wahlweise auch auf der Festplatte gespeichert werden soll. Sicher könnte man nun in der Klasse, die die Eingabe verarbeitet, einfach eine if-Schleife einbauen und die Verzweigung an die neue Funktion "writeToDisk" übergeben. Dies ist jedoch aus mehreren Gründen fehleranfällig und problematisch. Würde man so vorgehen, hätte man hier eine Abhängigkeit zu einer konkreten Implementierung geschaffen.

Das DIP besagt: Module hoher Ebenen sollten nicht von Modulen niedriger Ebenen abhängen. Beide sollten von Abstraktionen abhängen. Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen. Code, der nach diesem Prinzip entwickelt wurde, ist nicht verwundbar gegenüber Änderungen. Da im Code Abstraktionen und konkrete Implementierungen voneinander getrennt sind, ist der Code leicht zu warten und damit günstig im Unterhalt.

Wenn Sie Fragen haben oder Beratung in allen Bereichen der professionellen Webentwicklung benötigen, dann kontaktieren Sie mich gerne.


1 Barry Boehm, Ricardo Valerdi und Eric Honour: "The ROI of Systems Engineering; Some Quantitive Results for Software-Intensive Systems", Systems Engineering 11, Issue 3 (August 2008). ISSN 1098-1241

2 Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices (Prentice Hall International, 2002, ISBN 978-0-135-97444-5

3 Object Oriented Software Engineering, a Use Case Driven Approach, Ivar Jacobson, Addison Wesley, 1992, Seite 21

4 Object Oriented Software Construction, Bertrand Meyer, Prentice Hall, 1988, Seite 23

5 Barbara Liskov, "Data Abstraction and hierachy", SIGPLAN Notices, 23, 5 (Mai 1988)

6 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Entwurfsmuster: Elemente wieder verwendbarer objektorientierter Software, Addison-Wesley, ISBN 978-3-8273-2824-3