Codephilosophisches – Here Programmierparadigmen, objektorientiert oder strukturiert – die Tücken der Abstraktion und wiederverwendbaren Code zu schreiben an einem fast schon trivialen Beispiel

Die Objektorientierung umgibt der Softwareentwicklung immer so eine gewisse Aura. Warum eigentlich? Die aller meisten Bücher steigen in die objektorientierte Programmierung mit der Klärung der Begriffe Objekt, Klasse, Instanz, Vererbung, Methode ein. Das ist erst einmal völlig in Ordnung da sie ja tatsächlich den wesentlichen begrifflichen Hintergrund der objektorientierten Programmierung abdecken. Allerdings sind die meisten dieser Begriffe auch schon ausserhalb des Programmierkontextes so etwas von geläufig, das sie nicht wirklich einer Erklärung bedürfen. Das ist ja gerade das vermweintlich schöne an der Objektorientierung das man sich nicht erst einmal in ein abstraktes Denkmodell hineindenken muss. Andererseits sollte man aber deshalb auch nicht erwarten das allein mit dem Verständnis dieser Begriffe das Paradigma der objektorientierten Programmierung schon wirklich erschlossen sei. Es ist in etwa so wie mit der Einsteinschen Relativitätstheorie. „Alles ist irgenwie relativ“! Diese Aussage ist auch hinsichtlich der Theorie Einsteins nicht falsch, deckt aber wohl den wissenschaftlichen Wert des Werkes Einsteins nicht annähernd ab. Deshalb ist dem Einsteiger in die objektorientierte Programmierung nicht wirklich geholfen wenn entsprechender Autor je nach persönlichem Gusto mal den Stammbau der Biologie bemüht, oder sich den Gegenständen des Alltags und ihrer Hierachisierbarkeit widmet um zu erklären was Objekte einer Klasse sind. Deshalb will ich hier mal einen anderen Ansatz fahren um die Schwierigkeiten zu erläutern die sich aus dem strukturierten Programmierparadigma ergeben, dessen Probleme mit dem objektorientierten Paradigma vermeintlich überwunden wurde.

Meiner Meinung nach muss man Programmierung von der Prozedualität her denken. Die Hauptaufgabe eines Programms ist nun einmal das in ihm etwas passiert, Ob dies nun in Rahmen einer Funktion (strukturiert) oder einer Methode (objektorientiert) stattfindet ist erst einmal relativ egal. Der Grundgedanke in der strukturierten wie in der objektorientierten Programmierung ist der selbe. Man möchte wiederverwendbaren Code schreiben. Der primäre Gedanke der Objektorientierung ist es diese Prinzip auf die Spitze zu treiben. Orthodox betrachtet versucht die Objektorietierung die ganze Welt in Klassen, spezifisch abgeleiteten Klassen und ihre Objekte einzuteilen. Die ganze Welt als ein deterministisches Modell! Und wie weit sind die Softwareentwichler diesem Anspruch gerecht geworden? Man möchte meinen nach mehreren Jahrzehnten selbst der objektorientierten Softwareentwicklung sei jede Klasse und ihre Einordnung in eine Klassenhierachie erfunden, jede Methode definiert, und Softwareentwickler führten nun die Arbeitslosenstatistik der Bundesagentur für Arbeit an. Hinsichtlich der Berufaussichten jedenfalls kann man wohl aber erst einmal Entwarnung geben. Was allerdings im Umkehrschluss bedeutet das sich die hochfliegenden Pläne der objektorientierten Softwareentwicklung wohl so auch noch nicht realisiert haben.

Vorhin sagte ich das man Softwareentwicklung vom Standpunkt der Prozedualität her denken muss. Ja gut dann tun wir das doch einmal. Eines meiner Lieblingsbeispiele ist die Programmierung einer Funktion oder Methode zu Berechnung des Zinseszins. Hartgesottene Programmierer werden wahscheinlich einwenden, das diese Beispiel nicht geeignet ist den paradigmatischen Unterschied zwischen objektorientierter und strukturierter Programmierung herauszuarbeiten. Da mögen sie sogar recht haben. Aber es geht mir auch nicht nur darum, sondern ich möchte ganz allgemein die Tuecken aufzeigen, die sich ergeben wenn man versucht wiederverwendbaren Code zu schreiben. Und dafuer ist die Zinseszins Funktion ein kleines aber feines Beispiel. Diese Beispiel hat den Vorteil das einerseits das zu lösende Problem nahezu jedem geläufig sein dürfte, es andererseits aber völliug ausreichend ist um meine zukünftigen Betrachtungen in allen Facetten darauf anwenden zu können. An sich ist die Zinseszinsberechnung so trivial das sich die Frage stellt ob dafür überhaupt eine Funktion (Methode) sich zu bilden lohnt.

Allerdings könnte man statt der klassischen Berechnungsformel:  Kn = K0(1 +  p)t
100

bei der insbesondere die Exponentenfunktion den Kern der Berechung darstellt, eine Schleife zur Zinsesverzinsung des angesparten Kapitals nutzen. Spätestens beim beschreiten des 2. Weges mittels einer Schleife also dem Einsatz einer programmiertechnischen Kontrollstruktur, die Verzinsung zu berechnen, rechtfertig vielleicht die Funktionalisierung bzw. die Methodisierung der Aufgabe. Wie müsste nun eine solche Funktion vollständig parametrisiert aussehen. Naheliegend wäre es die Funktion mit 3 Übergabeparametern auszustatten die da wären:

Anfangskapital, Zinssatz und den Anlagezeitraum. → zinsesZins(anfangskapital, zinssatz, anlagezeitraum) Vom Definititionsbereich her wären die beiden ersten Parameter wohl als Gleitkomma Datentypen einzurichten während für die Definition des Zeitraumes auch ein Ganzzahl Datentyp in Frage kommen könnte. Die Funktion als ganzes könnte den Namen „zinzesZins bekommen“, Wir unterschlagen hier einmal die Suche nach einer angelsächischen Namensgebung die unsere Funktion international verwendbar machen würde. Unterschlagen ist hinsichtlich einer Funktion die eine Berechnungsfunktion der Finanzmathematik darstellt, auch ein sehr schöner Begriff. Der sich ergebende Kontext der Funktion, nämlich einem aus der Finanzmathematik ergäbe sich in der strukturierten Programmierung nur aus ihrem Eigennamen „zinseszins“ und der Namen der formalen Übergabeparameter: Anfangskapital, Zinssatz, Anlagezeitraum. In der objektorientierten Programmierung hätte man sie als Methode einer Klasse zuordnen müssen. Der Name der Methode wäre eventüll erst einmal der selbe, eben „zinsesZins“, eingeordnet vielleicht in einer Klasse „Finanzierung“. In dieser Klasse könnten dann ebenfalls weitere Methoden der Finanzkalkulation zusammengefasst sein, z.B. die einfache Zinsformel für Verzinsungszeiträume unterhalb eines Jahres bei denen also kein Zinsesverzinsungseffekt auftritt, oder eine weitere zur Berechnung eines Tilgungsplans zur Finanzierung bei Schuldtiteln. Die Klasse „Finanzierung“ wiederum könnte eine abgeleitete Klasse der Basisklasse „Geldpolitik“ sein, usw. Worin liegt nun eigentlich der Unterschied der Einordnung der Methode „zinsesZins“ in einer festen Klassenhierachie, gegenüber derer einer losen Funktionsdefinition in der strukturierten Programmierung. Die Objektorientierung erscheint hier sogar er hinderlich da sie unsere Funktion in einen engen Kontext zwingt, und die Wiederverwendung in einem anderen daher eher erschwert. Warum also unbedingt objektorientiert programmieren? Dazu müssen wir uns mit dem Wesen unserer Zinsfunktion noch einmal tiefergehend auseinandersetzen. Welche Art von Problemlösung beschreibt sie eigentlich, oder anders gefragt geht es wirklich nur um Probleme aus einem Finanzierungkontext, wie die Namensfindung unserere definierten Funkton und die Namen der formalen Parameter suggerieren. Es sieht doch erst einmal so aus das unsere Funktion ganz allgemein ein Wachstumsverhalten beschreibt.

Wir haben eine Ausgangsbedingung die einen Wachtumsprozess anstösst. Aus dieser Ausgangsbedingung ergibt sich nach einem bestimmten Zeitraum ein Ertrag. Dieser Ertrag wird nach Ablauf dieses Zeitraums wieder der Ausgangsbedingung zugeschlagen und es beginnt ein neuer Wachtumsprozess. Da die Ausgangsbedingung sich jetzt vergrössert hat und wir annehmen das der Wachtumscharackter selbst sich proportioanl zur grösse derAusgangsbedingung verhält, bekommen wir im Laufe der Zeit einen exponentiellen Wachstumsverlauf.

So abstrakt formuliert – kann man jetzt noch erkennen das wir lediglich einen spezifischen Sachverhalt aus der Finanzmathematik beschreiben? Diese Wachtumsverhalten ist doch beispielsweise auch sehr ähnlich dem mit der die Populationsentwicklung einer Spezies in der Biologie beschrieben werden könnte, oder?

Das Anfangskapital ist die aktuelle Grösse einer Population, der Zinsatz die Geburtenrate und der Anlagezeitraum die Anzahl der Generationen.

Ursprungskontextaktueller Konztext
zinseszins(anfangskapital, zinssatz, anlagezeitraum)    →    zinseszins(populationsgroesse, geburtenrate, n_generationen)

Sehen sie jetzt noch die enge Bindung der „ZinsseZins“ Funktion an den der Finanzmathematik? Wir haben offensichtlich eine Funktion entwickelt die auch in anderen Kontexten anwendbar erscheint. Das Paradigma der Wiederverwendung von einmal geschriebenen Code scheint optimal erfüllt. Warum also den Kontext durch Objektorientierung künstlich einschränken?

In der strukturierten Programmierung könnte man dies Funktion also ohne weiteres verwenden. Der Programmierer muss nur wissen wie die Funktion eigentlich arbeitet, und den Ursprungskontext durch geeignete Wahl der Namen der aktuellen Übergabeparameter überwinden. Allerdings kann der Name der Funktion und der der formalen Uebergabeparameter den Zweck für den sie ursprünglich einmal geschrieben worden war nicht verbergen.

Der Compiler akzeptiert den Einsatz der Funktion in einem aktuellem Kontext solange wie die Übergabe der Parameter nicht zu syntaktischen Irritationen führt. Es dürfen also lediglich genau 3 Werte an die Funktion übergeben werden, bei denen die ersten beiden Gleitkommanatur sein müssen und der 3. einem Ganzzahlwert entsprechen solte. Diese Hürde ist allerdings ziemlich gering und entspricht sicherlich keiner aussagenfähigen Kontextprüfung. Das ist aber eben auch nicht Aufgabe eines Compilers. Allerdings funktioniert die Kontextunterscheidung überladener Methoden aber gleichen Namens in der Objektorientierung genau nach diesen Kriterien.

Wir unterstellen jetzt mal einen etwas konstruierten Fall wie unsere Funktion missverständlich eingesetzt werden könnte. Ein Medizininformatiker beispielsweise mit dem Spezialgebiet Herz-Kreislauf Erkrankungen moechte eine neue Software entwickeln, und könnte motviert sein die Zinseszins Funktion in einem weiteren Kontext zu verwenden. Der Einbau einer Funktion wie der unseren scheint ihm vielleicht aus irgend einem Grund plausibel. Als die 3 Übergabeparameter könnten der obere und der untere Blutdruckwert sowie der Puls herangezogen werden. Auch die Datentypen passen anscheinend mehr oder weniger z&ueinander. Die beiden Blutdruckwerte könnten Gleitkomma – Natur sein und der des Pulses ist bekanntlich ein ganzzahliger. Wir verzinsen jetzt sozusagen unseren Blutkreislauf. Eine solche Funktionsanwendung scheint ziemlich kurios und ist wahrscheinlich auch eher unsinnig. D.h. aber im Umkehrschluss, das ein Problemkontext der eher zufällig technische Grössen aufweisst die zu der Parameterübergabeliste unserer Funktion passen, eben noch lange nicht bedeutet, das sich daraus ein passender Kontext ableiten liesse. Im Falle unserer Zinsfunktion ist ein Verwendungsfehler vielleicht noch ziemlich einfach identifizierbar. Grundsätlich können Prozeduren ihrem Funktionsnamen und der Namen der Uebergabeparameter nach aber in ihrem Wirkungsverständnis durchaus mehrdeutig sein, und man muss ihre Wirkungsweise und den Ursprungskontext genau verstanden haben, um zu entscheiden ob sie auch in aktuellem Zusammenhang sinnvoll einsetzbar sein koennten.

Die moegliche Mehrdeutigkeit von Methodennamen wird im Verständnis der Objektorientierung durch das Paradigma der Polyformie sogar bewusst kultiviert. Im Prinzip gibt es 2 Richtungen wie sich das Verständnis von der Wiederverwendbarkeit einer Funktion entwickeln kann. Der 1. ist der obenstehend beschriebene Fall. Eine Funktion wurde aus einem bestimmten Kontext heraus entwickelt. Nach der Implementation der Funktion stellt sich heraus das man eigentlich ein wesentlich generativeres Programmierproblem gelöst hat, und die Routine einen Lösungsansatz für einen wesentlich grösseren Problembereich darstellen könnte. Der andere Fall sind Methoden die nur bezüglich ihres Namens gleich sind. Sie beschreiben aber 1. verschiedene Prozedualitäten und lassen sich 2. durch den unterschiedlichen Charakter ihrer Parameterlisten auseinander halten. Im 2. beschrieben Fall spricht man von überladenen Funktionen und bezeichnet das Prinzip als Polyformismus und nutzt ihn in der Objektorientierung aus.

Was bedeutet es jetzt aber für die Lesbar- und die Verständlichkeit von Quellcode wenn es einem begabten Softwareentwickler gelingt ein Grossteil seines Projektes mit wiederverwendetem Code aufzubauen. Ist nun zu befürchten das der Programmcode zu einer Art Patchworkarbeit mutiert dem man die Fragmente seiner ursprünglichen Kontexte noch ansieht für die die Funktionen einmal geschrieben worden waren. Ist der Quellcode quasi in einer Art Kackophonie der Kontexte verauscht. Und wenn ja wie liesse sich das vermeiden.

Funktionen in der strukturierten Programmierung werden nicht in einem Klassenkontext eingeordnet, um die Einschränkung ihrer kontextuellen Verwendbarkeit bewusst zu vermeiden. Allerdings ist dies ein Paradigma der diffusen Universalität denn natürlich wird jede Funktion im Moment ihrer Entstehung in einem konkreten Kontext gedacht, und dieser Urpsrungkontext läest sich durch die Namen der Funktion und der Namen der formalen Übergabeparameter auch nicht wirklich verbergen. Stellt man also wie in unserem oben geschilderten Fall der zinseszins – Funktion fest, das man in Wirklichkeit gar keinen nur auf den Finanzkontext reduzierbaren bestimmten Wachstumsprozess beschreibt, muss sich diese neue Erkenntnis der stärkeren Abstrahierbarkeit eigentlich in einer Überarbeitung des Namens der Funktion und ihrer Parameter niederschlagen.

Die Objektorientierung versucht gar nicht erst kontextlosen Programmcode zu entwickeln. Stellt man irgendwann fest das der ursprüngliche Kontext zu eng gefasst war, müssen der Name der Methode und deren formalen Übergabeparameter bewusst überarbeitet, und die Methode eventuell auch in ein neues Klassenmodell überführt werden. Vielleicht ist sogar das gesamte Klassenmodell neu anzupassen. Diese Überarbeitung liessse sich nur vermeiden wenn es von Anfang an gelänge Funktionen und Methoden ausreichend zu abstrahieren. Dafür gibt es aber kein eindeutiges Bewertungsinstrument. Vielleich greifen Softwareentwickler deshalb manchmal gerne auf die Wissenschafttsdiziplin zurück mit der die Menscheit die grösste Erfahrung hinsichtlich der Abstraktion von Problemen hat. Der Disziplin der Mathematik. Dies ist vielleicht auch ein Grund dafür warum viele Entwickler statt der klassischen Programmiersprachen manchmal Entwicklungswerkzeuge mit schwerpunktmässig mathematischen Hintergrund wie Matlab und anderes einsetzen.

Oben wurde übrigens bewusst von einem „bestimmten Wachstumsprozess“ gesprochen, denn auch der Wachtumprozess unserer Zinsfunktion ist kein universeller. Regelungstechnisch ausgedrückt beschreiben wir einen positiven Rückkopplungsprozess bei der eine Ausgangsgrösse exponentiell wächst. Allerdings geschieht der Prozess der Rückkopplung nicht fliessend sonder zu bestimmten diskreten Rückkopplungszeitpunkten. Ein „Finanzoptimierer“würde uns hinsichlich unserer Geldanalgestrategie vielleich folgendermassen beraten. Statt in ein Finanzprodukt mit jählicher Zinsauschüttung zu investieren wie beispielsweise in Finanzierungsschätze des Bundes, würde er uns zu einer kurzfristigeren Anlage vielleicht in Festgeld mit vierteljählicher oder sogar monatlicher Ausschüttung raten. Dieses Geld wird samt des jeweiligen Etrages dann sofort wieder neu angelegt. Zwar ist der konkrete prozentualle Betrag in Relation zur Anlagesumme dann geringer weil wir das Geld unterhalb eines Jahres anlegen, da wir aber die Zeitabstände der Rückkopplungsmomente verkürzen verstärken wir den Zinseszinscharakter unserer Geldanalage. Verkürzen wir die unsere Zeiträume der Geldneuanlage mal rein theoretisch ins infinitesimal kleine hätten wir zwar eine optimierte Verzinsung, aber vermutlich auch den Bankberater in den Wahnsinn getrieben. Ich rate allerdings von dieser Anlagestrategie eher ab, da bei unserer Anlagstrategie neben dem Zinsertrag wahrscheinlich auch die Verwaltungskosten mit explodieren. Eine solche Funktion die die Wachstumsprozesse mit infinitesimal kurzer Rückkopplung beschreibt hat übrigens in der Mathematik einen bestimmten Namen. Sie wird e-Funktion genannt.

Wir sehen hier also das unserer Wachstumsprozess auch nicht wirklich universell sondern relativ speziell ist. Glücklicherweise haben wir z.B. unseren 3.Überbabeparameter der Zinsseszins – Funktion „Anlagezeitraum“ und nicht etwa „Jahre“ genannt. Mit „Jahre“ hätten wir selbst innerhalb des Zinseszinskontextes den Begriff des Übergabeparameters zu eng gefasst. Denn wie wir am Beispiel der Anlagestrategie in monatlichem oder vierteljährlichem Festgeld gesehen haben, muss der Anlagezeitraum keinesfalls immer genau einem Jahr entsprechen. Mann kann hieran sehen wie sorgfältig man bei der Formulierung von Begrifflichkeiten in der Programmierung sein muss. Das gilt für die strukturierte wie die der objektorientierten Programmierung.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.