Sonntag, 28. September 2008

Wie man ein Dreirad zum Rennwagen umbaut

Gewisse Leute vergleichen gerne die Softwareentwicklung mit der Automobilproduktion. Dieser Vergleich hinkt auf jedem Bein. Meist wird gefragt, warum sich Software nicht am Fließband herstellen läßt, möglichst von billigen angelernten Kräften. Ein anderer Vergleich dreht sich darum, daß meist fehleranfälligere Software produziert wird wie Automobile (nicht das Automobile nicht fehlerhaft aus der Fabrik kommen würden, ich fahre ein solches Modell).

Bei der Produktion von Software haben wir aber einen Vorteil gegenüber der Automobilproduktion. Wir können mit dem Plan für ein Dreirad anfangen und am Ende doch einen Rennwagen bekommen.

Neue Software

Wenn Software komplett neu erstellt wird, dann werden die Prozesse, die die Software unterstützen soll, bislang vermutlich manuell bewältigt. Man kann jetzt versuchen, eine komplette Lösung, die alle denkbaren Spezialfälle abdeckt, zu entwerfen und umzusetzen. Der Vertrieb wird dafür sein, da so eine Lösung viel Umsatz verspricht. Das Management auf Kundenseite wird möglicherweise auch dafür sein, da anscheinend große Lösungen dem Management besser stehen als pragmatische.

Eine allesumfassende Lösung bedeutet aber eine Reihe von Problemen. Zuerst dauert die Erstellung entsprechend lange. Während dieser Zeit gibt es für die alten Prozesse keinerlei Unterstützung, was Geld kosten dürfte. Die Spezifikation verliert sich oft in den Details, die sich in der Praxis dann oft als irrelevant herausstellen oder ganz anders aussehen. Zuletzt gibt es gewöhnlich auch ein technologisches Risiko. Da sich die Technologie immer noch schnell entwickelt, kann man es sich gewöhnlich nicht leisten, nur auf bewährte und bekannte Komponenten zu setzen. Bei neuen Komponenten ist die Belastbarkeit aber nicht bekannt. Das kann bedeuten, daß die Entwicklung länger als geplant dauert oder daß am Ende die gewünschte Performance und Stabilität nicht zu erreichen ist.

Alternativ kann man das System so konzeptionieren, daß es gut mit manuellen Prozessen zusammenarbeitet. Das empfiehlt sich immer, denn es wird immer Sonderfälle geben, die niemand bedacht hat oder deren Umsetzung in Software einfach zu teuer wäre. Nun kann man sich den Prozeß herauspicken, der bei einer Automatisierung am meisten bringt verglichen zum Implementierungsaufwand und -risiko.

Wenn sich der Ansatz bewährt, kann man so Schritt für Schritt weitermachen. Wenn sich aber unvorhergesehene Probleme auftun, ist es noch nicht zu spät, mit einem anderen Ansatz neu anzufangen.

Die Wahrscheinlichkeit ist hoch, daß man so beginnend von einem ganz einfachen Ansatz (Dreirad) im Laufe der Zeit eine bestens getunete Software bekommt, die Formel 1 Qualitäten besitzt.

Altsysteme

Heutzutage ist es wahrscheinlicher das eine Software erstellt wird, um ein bestehendes System abzulösen anstatt einen manuellen Prozeß. Auch in diesem Fall ist es möglich, schrittweise vorzugehen. Mir fallen da zwei Ansätze ein, wobei das Ergebnis unter Umständen bei beiden Ansätzen ähnlich aussehen kann.

Der erste Ansatz beschäftigt sich mit den Verantwortlichkeiten. Ein System ist gewöhnlich für mehr als eine Sache verantwortlich. Wenn ein kleines Webshopsystem sowohl den Kundenbestand als auch das Lager verwaltet, so läßt sich zuerst eine dieser Verantwortlichkeiten herauslösen. Hierzu muß das Altsystem natürlich soweit angepaßt werden, daß sich die entsprechenden Schnittstellen ergeben.

Der andere Ansatz dreht sich um die Datenhoheit. Daten werden oft in mehreren Systemen gleichzeitig gehalten. Aber eins ist verantwortlich für die Daten. Der Datenbestand in den anderen Systemen stellt im wesentlichen ein Cache dar, um die Zugriffe auf die Originaldaten zu minimieren. Eventuell kommt auch eine geänderte Sichtweise hinzu. Auf jeden Fall lassen sich die Daten aller weiteren System aus denen des Systems mit der Datenhoheit immer wiederherstellen. Was man jetzt tun kann, ist, die Datenhoheit von dem Alt- auf das Neusystem zu verschieben. Dazu muß sichergestellt werden, daß das neue System in jede Datenänderung einbezogen wird. Wenn das neue System über die Daten verfügt, können z.B. alle Schnittstellen, die mit diesen Daten bedient werden schrittweise vom Alt- auf das Neusystem übertragen werden. Zu Beginn hält das Neusystem nur Daten, hat aber noch keine Funktion. Am Ende hat das Altsystem nur noch Daten, die aber dort keiner mehr braucht. Es kann jetzt abgeschaltet werden.

Insbesondere bei dem Ansatz, bei dem die Daten zeitweise doppelt gehalten werden, hat man eine hohe Sicherheit und kann im Prinzip noch lange Zeit den Umstieg wieder rückgängig machen. Das wird erst dann schwierig, wenn sich die Funktionalität im Neusystem fortentwickelt hat und im Altsystem nicht mehr nachgezogen wurde. Zu diesem Zeitpunkt hat sich das Neusystem aber schon bewährt.

Schiefe Metapher

Natürlich ist ein solcher evolutionärer Ansatz nicht in der Softwareindustrie erfunden worden. Wenn man sich z.B. die ersten Automobile anschaut, so erkennt man die Evolution vom Fahrrad und der Kutsche zum Automobil. Das Problem an dem Bild ist, daß die Softwareentwicklung der Entwicklung eines neuen Modells in der Automobilindustrie entspricht. Der Produktionsprozeß in der Automobilindustrie ist am ehesten mit der Installation der fertigen Software zu vergleichen. Das andere Problem ist, daß es sich bei der Software, deren Preis und Risiken diskutiert werden meist um Individualanfertigungen handelt, wo es definitionsgemäß nur wenig Routine gibt.


Sonntag, 14. September 2008

Eines sie alle zu binden

Unternehmensdatenmodelle

Stabsstellen sind doch etwas schönes. Budgets scheinen keine Rolle zu spielen. Und man kann andere so schön ärgern, die von den Ergebnissen abhängig sind, denn das Management steht ja dahinter. Irgendwann vor langer Zeit ist ein kleiner Datenmodellierer auf die Idee gekommen, einem Manager das nahezubringen, was er so tut. Da sich Manager nicht mit kleinen Dingen abgeben, kamen sie auf die Idee, ein allumfassendes Unternehmensdatenmodell zu schaffen. Von nun an sollten sich alle Anwendungen im Unternehmen an dieses eine Datenmodell halten. Alle weitere Datenmodellierung wäre nicht mehr notwendig und alles würde großartig zusammenarbeiten.

Nun wäre ich der letzte, der etwas gegen Datenmodellierung sagen würde. Ein aussagefähiges Datenmodell ist nicht nur die Grundlage eines erfolgreichen Projekts, sondern besitzt in meinen Augen auch so etwas wie Schönheit.

Das Problem mit den ganz großen und alles lösenden Ansätzen ist nur, daß sie im allgemeinen zu scheitern pflegen. Dies liegt im wesentlichen an drei Dingen. Zuerst leben wir in einer Zeit, in der sich Dinge schnell ändern - schneller als man ein ganz großes Datenmodell erstellen kann. Zweitens sind die Anforderungen der einzelnen Nutzer zu individuell. Wenn für einen die Adresse nur ein Stück Text ist, das auf Adreßetiketten gedruckt werden muß, ist für einen anderen Nutzer jedes Detail interessant, da er z.B. darauf aufbauend die Kreditwürdigkeit eines Kunden bestimmen will (wie auch immer man zu diesen Methoden steht, sie sind in vielen Anwendungen implementiert). Ein gutes Datenmodell zeichnet sich dadurch aus, daß der Detailierungsgrad angemessen ist. In dem genannten Beispiel muß aber berücksichtigt werden, daß der Datenaustausch nur in eine Richtung funktioniert, da der andere Partner mit weniger Information auskommt. Der dritte Punkt ist die Beschränktheit der Beteiligten, was Kommunikation und Verständnis betrifft. Die Korrektheit eines Datenmodells beweißt sich erst durch die Benutzung. Auf der grünen Wiese erstellt, wird es sehr instabil sein.

Wenn nun ein Fehler in so einem Datenmodell festgestellt wird, müssen alle Anwendungen, die davon abgeleitet sind, korrigiert werden. Ansonsten bilden sich unterschiedliche Abkömmlinge, die genauso wenig zuverlässig miteinander kommunizieren können, wie es der Fall war vor der Einführung des einheitlichen Datenmodells.

Da die mit einer Änderung des Unternehmensdatenmodells verbundenen Kosten hoch sind, führt das zu einer Trägheit bei der Anpassung der Modelle, die die Grundlage der Anwendungen bilden. Es gibt große Konzerne, die schon früh in solche Modelle investiert haben und heute darunter leiden, daß alle ihre Anwendungen noch die Situation in den 90ern widerspiegeln, obwohl sich der Markt, in dem sich diese Unternehmen befinden, drastisch geändert hat.

SOA

Heutezutage will jeder IT-Leiter seine IT auf SOA (Service Oriented Architecture) aufbauen. Diese Services sollen zweierlei leisten. Zum einen sollen sie unabhängig entwickelt und gepflegt werden können. Zum anderen sollen sie von vielen Anwendungen genutzt werden können. Ich will hier nicht weiter darauf eingehen, inwieweit diese Ansprüche erfüllt werden können. Mich interessiert hier nur die Sicht des Datenmodelliers.

Datenmodellierung findet hier bei den in den Schnittstellen ausgetauschten Daten statt. Statt ER-Modelle für Datenbanken werden jetzt XML-Schemata modelliert, wobei es natürlich eine Frage ist, ob XML immer die beste Lösung ist.

Im Prinzip kann jeder Service sein eigenes Datenmodell entwickeln. Dies bedeutet, daß der Benutzer zuerst die Daten zwischen der Darstellung des Services und seiner internen Darstellung transformieren muß. An dieser Stelle setzt die Handlung von "Die Rückkehr des Unternehmensdatenmodellierers" ein. Der Gedanke ist doch bestechend. Wir bauen ein großes Datenmodell, auf das alle Services aufbauen. Dann entfällt diese lästige Transformiererei. Alle sprechen eine gemeinsame Sprache und die Datentypen passen aufeinander.

Leider haben wir hier wieder alle oben beschriebenen Probleme. Die Services sind hochgradig gekoppelt. Eine unabhängige Entwicklung ist nicht mehr möglich. Wenn das zentrale Datenmodell ausgetauscht wird, funktioniert zuerst einmal nichts mehr, bis alle Services angepaßt sind.

Dies widerspricht grundsätzlich der Idee, die Services zu entkoppeln.

Wenn man doch in diese Richtung gehen will, so sollte man nicht die Datenmodelle durch neue Versionen ersetzen, sondern die Versionen nebeneinander leben lassen. Eine alte Version wird erst entfernt, wenn alle Nutzer auf die neue Version gewechselt sind. Dadurch müssen zwar wieder Transformationen durchgeführt werden, wenn die kommunizierenden Services unterschiedliche Versionen eingesetzt werden, doch der Aufwand dürfte meist überschaubar sein und es ist auch eine Wiederverwendung denkbar.

Allgemein gegen präzise

Ein weiterer Aspekt ist, daß ein allgemeines Schnittstellenmodell nur den kleinsten gemeinsamen Nenner darstellen kann. Das gleiche Element mag für die eine Schnittstelle optional sein, während es für die andere notwendig ist. Die eine Anwendung braucht eine Historisierung der Daten, die andere genau das neueste Element. In einem für alle nutzbarem Modell kann das Element nur optional sein und mehrfach vorkommen dürfen. Validierungen aufgrund des Datenmodells werden dadurch ungenauer. Die individuelle Anforderungen der einzelnen Schnittstellen müssen anderweitig dokumentiert werden, was sich häufig als Fehlerquelle erweist.

Zusätzliche Aspekte, die hier zu berücksichtigen sind, sind Datensicherheit und Vertraulichkeit. Nur bestimmte Systeme dürfen mit personenbezogenen Daten umgehen. Diese sind in einer allgemeinen Schnittstelle, die von allen genutzt werden soll, mit enthalten.

Logisch gegen technisch

Es ist wichtig, daß alle miteinander sprechen können. Daher ist ein - nicht zu detailliertes - logisches Datenmodell für alle sicher sinnvoll. Die technische Umsetzung in eine konkrete Schnittstelle erfolgt aber besser individuell.


Montag, 8. September 2008

Noch mehr Methoden

Gestern habe ich über Attribute und Methoden von Objekten gesprochen. Zwei Punkte möchte ich noch ergänzen.

Hilfsmethoden

Dieser Punkt ist leicht neben dem gestrigen Thema, da die Methoden überhaupt nicht von dem Objekt abhängen. Das Objekt wird der Methode als unsichtbarer (Ausnahme: Python) erster Parameter übergeben. Einige Methoden benutzen diesen Parameter aber nicht. Daher können sie auch, bei entsprechender Deklaration, so daß der Compiler einverstanden ist, ohne das Objekt direkt als Methode der Klasse verwendet werden. In diesem Fall dient die Klasse nur als Schublade für die Methode. Eigentlich handelt es sich bei einer Klasse, die ausschließlich solche Methoden enthält um ein Modul. Derartige Methoden werden häufig als Utility- oder Hilfsmethoden bezeichnet.

Wenn die Methoden Parameter haben, könnten sie auch als Methode eines der Objekte übergeben werden, die als Parameter dienen. Die ist in Ruby immer möglich. In anderen Programmiersprachen kann es aus zwei Gründen scheitern. Erstens ist nicht immer jeder Parameter ein Objekt. Das ist z.B. das Thema einer Mathematik-Bibliothek, wie sie sich u.a. bei Java findet. Zweitens kann es sich um ein Bibliotheksobjekt handeln, das nicht erweitert werden kann, da es final oder sealed ist. C# behilft sich hier mit extension methods, die bei Verwendung ähnlich aussehen als ob die Klasse erweitert worden wäre. In Wirklichkeit wird aber eine separate Klasse mit Hilfsmethoden verwendet. Dies sieht zwar hübsch aus, hat aber seine Tücken. Insbesondere ist das Verhalten bei Vererbung nur zu verstehen, wenn man weiß, was dahinter steht.

Zugehörigkeit von Methoden

Der aufmerksame Leser hat bei den vorherigen Ausführungen vielleicht schon gemerkt, daß es oft ein wenig willkürlich ist, welcher Parameter einer Methode herausgehoben wird, indem man die Methode der Klasse seines Typs zuordnet. Ein Beispiel, über das ich immer wieder stolpere ist die join-Methode, die sich in verschiedenen Bibliotheken findet. Sie dient dazu aus einem Array einen String zu machen, indem sie die Stringrepresentation der Arrayelement mit einem Separatorstring verknüpft. Es gibt also die join-Methode, ein Array und einen Separatorstring. Für mich erschiene es natürlich, diese Methode dem Array zuzuordnen und den Separatorstring als Parameter zu übergeben. Der Autoren etlicher Bibliotheken sehen das aber umgekehrt und machen die join-Mehtode zu einer Methode der String-Klasse. Das ist eine völlig legitime Sichtweise.

Solche Probleme hat man natürlich nicht, wenn die Methoden unabhängig von den Klassen definiert werden, wie es in CLOS der Fall ist.


Sonntag, 7. September 2008

Das Objekt an sich

Während ich mich noch an Zeiten erinnern, als Objekte noch als unheimliche, neue Technologie galten, hat sich der objektorientierten Ansatz in den letzten Jahren weitgehend durchgesetzt. Wenn man aber verschiedene Programmiersprachen vergleicht, so erkennt man, daß das Verständnis, was ein Objekt ist, doch deutlich variiert. Egal was man verwendet, so erscheint es mir doch immer hilfreich, zu verstehen, welche Möglichkeiten es gibt und was die konkrete Implementierung bedeutet.

Im Lehrbuch findet man ungefähr folgendes: ein Objekt besitzt einen Zustand und Methoden. Es ist die Instanz einer Klasse und diese Klassen können Eigenschaften von anderen Klassen durch Vererbung übernehmen. Wenn man eine Klasse implementiert, so definiert man den Zustand gewöhnlich durch Attribute.

Attribute

Zunächst ist es hilfreich zu unterscheiden, daß es zwei Arten von Attributen gibt. Die ersten definieren die Identität des Objekts. Sie sind unveränderlich und werden deshalb bei der Erzeugung des Objekts definiert. Wenn ein Objekt z. B. eine Person repräsentiert, werde das Geburtsdatum ein solches Attribut. In einigen Programmiersprachen werden solche Attribute als konstant oder statisch gekennzeichnet.

Die zweite Art von Attributen ändert sich während der Lebenszeit des Objektes. Man kann die Unterscheidung hier noch weiter treiben, wenn man feststellt, daß sich mit Änderung des Attributs in einigen Fällen das Verhalten des gesamten Objektes ändert. Wenn sich die Temperatur eines Raumes in angemessenem Maße ändert, bleibt die Funktion des Raumes doch erhalten. Wenn sich aber der Zustand eines Autos von fahrbereit in Schrott ändert, werden dieselben Methoden anschließend zu unterschiedlichen Ergebnissen führen.

Wenn alles ein Objekt ist, dann stellen Attribute immer nur Beziehungen zu anderen Objekten dar. Dies würde prinzipiell zu einer unendlichen Rekursion führen. Daher gibt es in den meisten Programmiersprachen primitive Typen wie Zahlen und Wahrheitswerte, die nur ihren Wert repräsentieren. Will man das vermeiden, so führt man Klassen ein, deren Objekte einen bestimmten Wert repräsentieren, diesen aber nicht als Attribut besitzen. Ein derartiges Objekt besitzt nur eine Identität aber keine Attribute.

Eine weit verbreitete Ansicht ist, daß man nei direkt auf Attribute zugreifen, sondern immmer Zugriffsfunktionen nutzen soll. Einige Programmiersprachen erzwingen diese Art der Programmierung, zum Beispiel Ruby und Smalltalk. Welche Gründe gibt es dafür? Die Antwort hängt von der Art des Attributs ab. Attribute die die Identität des Objekts definieren, sollten nur einmal bei der Erzeugung des Objekts gesetzt werden können. Später sollte nur Lesezugriff möglich sein. Attribute die den Zustand eines Objekts beschreiben, sollten die direkt gesetzt werden, sondern ändern sich nur in Folge von Ereignissen. Wenn das Objekt nur ein Container für das Attribut ist, so brauchen wir in der Regel Schreib- und Lesezugriff, aber es kann ein Objekt gegeben, die über die Änderung des Wertes informiert werden wollen, wie zum Beispiel die Java Bean Spezifikation beschreibt.

Ein anderer Grund, Zugriffsfunktionen zu verwenden, ist der, daß man die innere Repräsentation der Werte ändern können will, ohne die Schnittstelle zu beeinflussen. Werte, die man nach außen sichtbar macht, müssen unter Umständen intern gar nicht vorhanden sein. Das Lehrbuchbeispiel hierzu sind kartesische und Winkelkoordinaten, von denen man nur ein Paar abspeichern muß, während sich das andere ergibt.

Methoden

Neben Attributen besitzt ein Objekt auch Methoden. Es empfiehlt sich, streng zu trennen zwischen Methoden, die eine Information über das Objekt zurückgeben und Methoden, die das Objekt ändern. Das ist eine Regel, die von keiner mir bekannten Programmiersprache erzwungen wird, aber die Verständlichkeit der Schnittstelle deutlich erhöht. Niemand wird damit rechnen, daß eine Wiederholung von Abfragen andere Werte zurückgegeben werden.

In einigen Programmiersprachen werden Methoden als Objekte behandelt. Sie können dann in Variablen abgespeichert werden oder als Rückgabewert von Methoden auftauchen. Zum Beispiel bei Python erfolgt der Zugriff auf Methoden völlig identisch wie der Zugriff auf Attribute.

In einigen Programmiersprachen, die auch den direkten Zugriff auf Attribute kennen, gibt es spezielle Methoden, die sich mit der gleichen Syntax aufrufen lassen, mit der auch auf Attribute zugegriffen wird. Dadurch wird es transparent, ob ein Attribut direkt angesprochen wird oder über Zugriffsmethoden. Populärstes Beispiel hierfür sind die Properties von C#.

Programmiersprachen, die keine strikte Typprüfung durchführen, erlauben es in der Regel, eine Methode zu definieren, die immer dann aufgerufen wird, wenn die eigentlich gefragte Methode nicht definiert ist. Das macht unter anderem die Programmierung von Proxies zu einer trivialen Übung.

Wenn eine strikte Typisierung gefordert ist, gibt es häufig die Möglichkeit, daß eine Zusage gemacht wird, daß eine bestimmte Menge von Methoden unterstützt wird. Dies geschieht durch die Interfaces, die man zum Beispiel in Java und C# findet. Man kann dann Objekte benutzen, die nur eine bestimmte Funktionalität zusagen, ohne den genauen Typ zu kennen.

Ausblick

Ich will hier kein Lehrbuch schreiben, sondern nur meine Sicht der objektorientierten Programmierung vorstellen. Einige Aspekte habe ich bislang weggelassen. Der wichtigste ist natürlich die Vererbung. Darauf will ich bei gegebener Zeit zurückkommen.