XSL-FO / DocBook / Konvertierung einer XML-Datei nach HTML, FO/PDF und Docbook 5.0 / Längeres Beispiel für das Inputdokument
![]() |
![]() |
➪ Hier finden Sie ein Beispiel, um eine XML-Datei nach HTML, FO/PDF und Docbook 5.0 zu konvertieren.
<Artikelliste>
<Artikel titel="Objektorientierte Programmierung in JavaScript" Datum="15.04.2012" jahr="2012"
publish="true" id="javascript1">
<Listing id="1" Thema="Funktions - Überladung I">
<code id="">function funcwithparam() {</code>
<remark idcode="1">möglicher Aufruf: funcwithparam("A", new Array("1", "2", "Hallo",
57), "B", "C"); // null als Parameter ruft Fehler hervor.</remark>
<code id="">writelog("funcwithparam: Anzahl Parameter: " + arguments.length);</code>
<remark idcode="1">gibt die Anzahl der übergebenen Parameter aus, ergibt bei o.a. Aufruf
die Anzahl 4 (eines davon ist ein Array)</remark>
<code id="">for (var y = 0; y < arguments.length; y++) {</code>
<remark idcode="1">Schleife über alle übergebenen Parameter</remark>
<code id="">if (arguments[y].length > 0) {</code>
<remark idcode="1">prüft, ob eins der Parameter ein Array ist. Dabei wird auch ein
String als Array erkannt.</remark>
<code id="">var arr = arguments[y];</code>
<remark idcode="1">spricht diesen Parameter als Array an. </remark>
<code id="">for (var yy = 0; yy < arr.length; yy++)
writelog(arr[yy].valueOf());</code>
<remark idcode="1">gibt jedes Element des Arrays aus. Ein String wird in einzelne
Characters zerlegt.</remark>
<code id="">}</code>
<remark idcode="1"/>
<code id="">else writelog(arguments[y].valueOf());}}</code>
<remark idcode="1">gibt Parameter aus, die keine Arrays sind</remark>
</Listing>
<Listing id="2" Thema="Typdeklaration mit this und prototype">
<code id="1">var Person = new Function("name", "this.Nachname=name;");</code>
<remark idcode="1">Hier wird Person als instanziierbarer Typ deklariert. Alternative
Deklaration: "var Person = function (name) { this.Nachname=name; };" Nachname
definiert ein öffentlich verfügbares Property des aufrufenden Objekts.</remark>
<code id="2">Person.prototype.getName = function () { return this.Nachname; };</code>
<remark idcode="2">getName() gibt den Inhalt von this.Nachname zurück. Hier (bewusst
ausserhalb der Typdeklaration Mensch) wird Person über prototype eine neue
parameterlose Function getName zugewiesen.</remark>
<code/>
<remark/>
<code id="3">var m = new Person("Sagblos");</code>
<remark idcode="3">Hier wird das erste Objekt über den Konstruktor mit dem
Übergabeparameter "name" und dessen Wert "Sagblos" initialisiert.</remark>
<code id="4">writelog("Die Person heisst: " + m.getName());</code>
<remark idcode="4">Dieses Objekt m gibt über getName() den Inhalt von this.Nachname
zurück.</remark>
<code id="5">writelog("Direkter Zugriff auf Nachname: " + m.Nachname);</code>
<remark idcode="5">Da Nachname über this. ein öffentlich verfügbares Property für den
Typ Person darstellt, kann das Objekt m direkt auf Nachname zugreifen. Das heisst,
mit m.Nachname = "Nixlos"; könnte der Wert geändert werden.</remark>
</Listing>
<Listing id="3" Thema="Datenkapselung">
<code id="1">var myhiddeninteger = function () {</code>
<remark idcode="1">deklariert myhiddeninteger als instanziierbaren Typ.</remark>
<code id="2">var verstecktesfeld;</code>
<remark idcode="2"> verstecktesfeld ist eine Variable, die ausschliesslich innerhalb des
Typs angesprochen werden kann; ausserhalb (Zeile 8) jedoch nicht. </remark>
<code id="3">this.get = function () {return verstecktesfeld;};</code>
<remark idcode="3">deklariert eine Lesemethode, die verstecktesfeld zurückgibt.</remark>
<code id="4">this.set = function (val) {verstecktesfeld = parseInt( val);};</code>
<remark idcode="4">deklariert eine Schreibmethode, mit der man verstecktesfeld einen
Integerwert zuweisen kann.</remark>
<code id="5">};</code>
<remark idcode="5">Ende der Typdeklaration myhiddeninteger</remark>
<code id="6">var hhh = new myhiddeninteger();</code>
<remark idcode="6">hhh ist eine erste Instanz von myhiddeninteger</remark>
<code id="7">hhh.set(95);</code>
<remark idcode="7">hhh verwendet die Schreibmethode set, um einen Integerwert auf
verstecktesfeld zu schreiben.</remark>
<code id="8">//writelog(hhh.verstecktesfeld);</code>
<remark idcode="8">die Zeile ist auskommentiert, weil der direkte Zugriff des Objekts
hhh auf verstecktesfeld nicht möglich ist und "undefined" als Rückgabe erzeugen
würde</remark>
<code id="9">writelog("myhidden: " + hhh.get());</code>
<remark idcode="9">hhh verwendet die Lesemethode get, um den gesetzten Wert 95
zurückzugeben.</remark>
</Listing>
<Listing id="4" Thema="Bruchrechnung">
<code id="1">var Bruch = function () {</code>
<remark idcode="1">Deklaration der "Klasse" Bruch</remark>
<code id="2">this.ZAEHLER = new myhiddeninteger();</code>
<remark idcode="1">Implementierung der Datenkapselung für ZAEHLER über separaten Typ
myhiddeninteger</remark>
<code id="3">this.NENNER = new myhiddeninteger();</code>
<remark idcode="1">Implementierung der Datenkapselung für NENNER über separaten Typ
myhiddeninteger</remark>
<code/>
<remark/>
<code id="1">this.toString = function () {</code>
<remark idcode="1">Überschreibt die toString-Methode für Bruch</remark>
<code id="1">this.vorzeichenwechsel();</code>
<remark idcode="1">ruft die später deklarierte Methode vorzeichenwechsel für das
aktuelle Objekt auf</remark>
<code id="1">if (this.NENNER.get() === 0) return "Nenner ist 0, NICHT DEFINIERT";</code>
<remark idcode="1">Fallbehandlung für NENNER = 0</remark>
<code id="1">else if (this.ZAEHLER.get() === 0 || this.NENNER.get() === 1) return
this.ZAEHLER.get();</code>
<remark idcode="1">Fallbehandlung für ZAEHLER=0 oder NENNER=1</remark>
<code id="1">else return this.ZAEHLER.get() + " / " + this.NENNER.get();};</code>
<remark idcode="1">alle anderen Fälle.</remark>
<code id="1">this.vorzeichenwechsel = function () {</code>
<remark idcode="1">Deklaration der Methode Vorzeichenwechsel für das aktuelle
Objekt</remark>
<code id="1">if (this.NENNER.get() < 0) {</code>
<remark idcode="1">Fallbehandlung für NENNER < 0</remark>
<code id="1">this.ZAEHLER.set(this.ZAEHLER.get() * (-1));</code>
<remark idcode="1">Dann Vorzeichenwechsel für ZAEHLER</remark>
<code id="1">this.NENNER.set(this.NENNER.get() * (-1));</code>
<remark idcode="1">und Vorzeichenwechsel für ZAEHLER</remark>
<code id="1">};};};</code>
<remark idcode="1">Ende if, Ende Methode Vorzeichenwechsel, Ende Deklaration für
Bruch</remark>
<code/>
<remark/>
<code id="1">Bruch.prototype.kuerzen = function () {</code>
<remark idcode="1">kuerzt zaehler und nenner des aufrufenden Objekts durch Ermittlung
des grössten gemeinsamen Teilers. Aus didaktischen Gründen steht kuerzen ausserhalb
von Bruch-Deklaration; sie hätte genausogut innerhalb mit this implementiert werden
können. </remark>
<code id="1">var z = Math.abs(this.ZAEHLER.get());</code>
<remark idcode="1">kopiert aktuellen Absolutwert von ZAEHLER in temporären Wert
z</remark>
<code id="1">var n = Math.abs(this.NENNER.get());</code>
<remark idcode="1">kopiert aktuellen Absolutwert von NENNER in temporären Wert
n</remark>
<code id="1">if (z > 0 && n > 0) {</code>
<remark idcode="1">prüft, ob temporäre Variablen z und n in jedem Fall > 0 sind;
wichtig für die folgende While-Schleife</remark>
<code id="1">while (z !== n) {</code>
<remark idcode="1">Die Schleife subtrahiert z und n so lange gegeneinander, bis beide
gleich sind. Dafür müssem z und n zwingend > 0 sein.</remark>
<code id="1">if (z > n) z = z - n;</code>
<remark idcode="1">wenn z > n: subtrahiere n von z.</remark>
<code id="1">else n = n - z; }</code>
<remark idcode="1">sonst subtrahiere z von n. Ende While-Schleife.</remark>
<code id="1">this.ZAEHLER.set(this.ZAEHLER.get() / z);</code>
<remark idcode="1">Der größte gemeinsame Teiler ist in z gefunden, jetzt wird der
aktuelle ZAEHLER-Wert durch z dividiert und das Ergebnis auf ZAEHLER
zurückgeschrieben.</remark>
<code id="1">this.NENNER.set(this.NENNER.get() / z);</code>
<remark idcode="1">Analog für NENNER</remark>
<code id="1">}};</code>
<remark idcode="1">Ende der Kürzroutine</remark>
<code/>
<remark/>
<code id="1">Bruch.prototype.subtrahiere = function (b) {</code>
<remark idcode="1">Subtrahiert ein Bruch-Objekt vom aufrufenden Objekt, liefert als
Ergebnis ein neues Objekt zurück (ObjectFactory), kuerzt dabei alle Objekte. Die
Methode ist ein Beispiel für diverse andere Gestaltungsmöglichkeiten.</remark>
<code id="1">var erg = null;</code>
<remark idcode="1">erg wird später gefüllt</remark>
<code id="1">if ((b instanceof Bruch)) {</code>
<remark idcode="1">prüft, ob Parameter b eine Instanz von Bruch ist</remark>
<code id="1">b.kuerzen();</code>
<remark idcode="1">kürzt ZAEHLER und NENNER von b</remark>
<code id="1">this.kuerzen();</code>
<remark idcode="1">kürzt ZAEHLER und NENNER des aufrufenden Objekts</remark>
<code id="1">erg = new Bruch();</code>
<remark idcode="1">weist erg den Typ Bruch zu</remark>
<code id="1">erg.ZAEHLER.set(this.ZAEHLER.get() * b.NENNER.get() - b.ZAEHLER.get() *
this.NENNER.get());</code>
<remark idcode="1">setzt ZAEHLER von erg</remark>
<code id="1">erg.NENNER.set(this.NENNER.get() * b.NENNER.get());</code>
<remark idcode="1">setzt NENNER von erg</remark>
<code id="1">erg.kuerzen();}</code>
<remark idcode="1">kürzt ZAEHLER und NENNER von erg</remark>
<code id="1">return erg;};</code>
<remark idcode="1">gibt erg als Objekt zurück; Ende Methode subtrahiere</remark>
<code/>
<remark/>
<code id="1">// Objekte instanziieren</code>
<remark idcode="1"/>
<code id="1">var aa = new Bruch();</code>
<remark idcode="1">instanziiere aa als neuen Bruch</remark>
<code id="1">var bb = new Bruch();</code>
<remark idcode="1">instanziiere bb als neuen Bruch</remark>
<code id="1">aa.ZAEHLER.set(-57);</code>
<remark idcode="1">verwende ZAEHLER-Schreibmethoden für aa</remark>
<code id="1">aa.NENNER.set(-95);</code>
<remark idcode="1">verwende NENNER-Schreibmethoden für aa. Die negativen Vorzeichen
werden über Vorzeichenwechsel automatisch korrigiert.</remark>
<code id="1">bb.ZAEHLER.set(33);</code>
<remark idcode="1">verwende ZAEHLER-Schreibmethoden für bb</remark>
<code id="1">bb.NENNER.set(55);</code>
<remark idcode="1">verwende NENNER-Schreibmethoden für bb</remark>
<code id="1">var dd = aa.subtrahiere(bb);</code>
<remark idcode="1">hier weist aa über die Methode subtrahiere dem bislang nicht
verwendeten Objekt dd seinen Typ und dessen Ergebniswerte zu. aa, bb und dd werden
dabei automatisch gekürzt.</remark>
<code id="1">writelog(aa.toString() + " - " + bb.toString() + " = " +
dd.toString());</code>
<remark idcode="1">Aufruf des Ergebnisses: "3 / 5 - 3 / 5 = 0"</remark>
</Listing>
<Listing id="5" Thema="Vererbung, Polymorphie">
<code id="">var Fahrzeug = new Function("this.fahren = function () { return \"Jedes
Fahrzeug faehrt\"; }; this.ausgabe = function () { return \"Ich bin ein Fahrzeug\";
}; ");</code>
<remark idcode="1">erzeugt Fahrzeug als Typ mit den Properties fahren und
ausgabe</remark>
<code id="">Fahrzeug.Auto = function () { };</code>
<remark idcode="1">erzeugt Auto als Typ, analog zu "Fahrzeug.Auto = new
Function();"</remark>
<code id="">Fahrzeug.Auto.prototype = new Fahrzeug();</code>
<remark idcode="1">weist Auto den Typ Fahrzeug zu</remark>
<code id="">Fahrzeug.Auto.prototype.fahren = function () { return "Automobil faehrt auf
der Strasse"; };</code>
<remark idcode="1">überschreibt das Property fahren</remark>
<code id="">Fahrzeug.Boot = function () { };</code>
<remark idcode="1">erzeugt Boot als Typ, analog zu "Fahrzeug.Boot = new
Function();"</remark>
<code id="">Fahrzeug.Boot.prototype = new Fahrzeug();</code>
<remark idcode="1">weist Boot den Typ Fahrzeug zu: Boot "erbt" von Fahrzeug</remark>
<code id="">Fahrzeug.Boot.prototype.fahren = function () { return "Boot faehrt auf dem
Wasser"; };</code>
<remark idcode="1">überschreibt die Methode fahren für Fahrzeug.Boot</remark>
<code/>
<remark/>
<code id="">var f = new Fahrzeug();</code>
<remark idcode="1">instanziiert ein Fahrzeug-Objekt </remark>
<code id="">var aaa = new Fahrzeug.Auto();</code>
<remark idcode="1">instanziiert ein Fahrzeug.Auto - Objekt</remark>
<code id="">var bbb = new Fahrzeug.Boot();</code>
<remark idcode="1">instanziiert ein Fahrzeug.Boot - Objekt</remark>
<code id="">writelog(f.ausgabe() + "; " + f.fahren());</code>
<remark idcode="1">Ausgabe: "Ich bin ein Fahrzeug; Jedes Fahrzeug faehrt"</remark>
<code id="">writelog(aaa.ausgabe() + "; " + aaa.fahren());</code>
<remark idcode="1">Ausgabe: "Ich bin ein Fahrzeug; Automobil faehrt auf der
Strasse"</remark>
<code id="">writelog(bbb.ausgabe() + "; " + bbb.fahren());</code>
<remark idcode="1">Ausgabe: "Ich bin ein Fahrzeug; Boot faehrt auf dem Wasser"</remark>
<code/>
<remark/>
<code id="">writelog("bbb ist ein Object: " + (bbb instanceof Object));</code>
<remark idcode="1">ergibt true</remark>
<code id="">writelog("bbb ist ein Fahrzeug: " + (bbb instanceof Fahrzeug));</code>
<remark idcode="1">ergibt true</remark>
<code id="">writelog("bbb ist ein Fahrzeug.Boot: " + (bbb instanceof
Fahrzeug.Boot));</code>
<remark idcode="1">ergibt true</remark>
<code id="">writelog("bbb ist ein Fahrzeug.Auto: " + (bbb instanceof
Fahrzeug.Auto));</code>
<remark idcode="1">ergibt false</remark>
</Listing>
<Listing id="6" Thema="Reflection">
<code id="1">for (var p in aa) {</code>
<remark idcode="1">Schleife über alle Properties des Bruch-Objekts aa</remark>
<code id="1">if (aa.hasOwnProperty(p)) {</code>
<remark idcode="1">prüft auf hasOwnProperty; nur solche Properties, die innerhalb des
Typs z.B.(?) mit this. angelegt werden, werden hier angesprochen.</remark>
<code id="1">writelog("OwnProperty: " + p);</code>
<remark idcode="1">gibt den Namen des Properties aus</remark>
<code id="1">if (p === 'ZAEHLER' || p === 'NENNER') {</code>
<remark idcode="1">wenn der Propertyname ZAHELER oder NENNER ist</remark>
<code id="1">writelog(" Aktueller Wert: " + aa[p].get());}</code>
<remark idcode="1">rufe dessen get-Function auf</remark>
<code id="1">else if (p === 'toString') {</code>
<remark idcode="1">wenn das aktuelle Property die toString-Function ist</remark>
<code id="1">writelog("toString: " + p + "; Result: " + aa[p].call(aa, ""));}}</code>
<remark idcode="1">rufe diese Function auf: im übertragenen Sinn sieht es so aus, als
würde die Methode das referenzierende Objekt aufrufen.</remark>
<code id="1">else writelog("NOT OwnProperty: " + p);}</code>
<remark idcode="1">listet alle Properties auf, die ausserhalb der Bruch-Deklaration mit
prototype angelegt wurden</remark>
</Listing>
<Listing id="7" Thema="Der Object-Konstruktor">
<code id="1">var einmensch = new Object();</code>
<remark idcode="5">Hier wird ein neues Objekt "einmensch" erstellt; synonym zu "var
einmensch = {};"</remark>
<code id="1">einmensch.vorname = "Lotte";</code>
<remark idcode="5">Dem Objekt werden zur Laufzeit Properties zugewiesen und mit Werten
befüllt, hier: vorname</remark>
<code id="1">einmensch.nachname = "Rielos";</code>
<remark idcode="5">wie vor, hier nachname</remark>
<code id="1">einmensch.toString = function () { return this.vorname + " " +
this.nachname };</code>
<remark idcode="5">überschreibe das toString-Property unter Aufruf von this</remark>
<code id="1">writelog(einmensch.toString());</code>
<remark idcode="5">Ausgabe: "Lotte Rielos"</remark>
<code/>
<remark/>
<code id="1">for (var einmprop in einmensch){</code>
<remark idcode="5">Auch aus diesem Objekt lassen sich zur Laufzeit alle Properties
ansprechen (sie liessen sich auch über (einmensch.hasOwnProperty(einmprop))
erreichen).</remark>
<code id="1">if (einmprop === 'toString') writelog(einmprop + "; Result: " +
einmensch[einmprop].call(einmensch, ""));</code>
<remark idcode="5">Resultat: "toString; Result: Lotte Rielos"</remark>
<code id="1">else writelog(einmprop + ": " + einmensch[einmprop]); }</code>
<remark idcode="5">Resultate: "vorname: Lotte" bzw. "nachname: Rielos"</remark>
</Listing>
<Listing id="8" Thema="JSON, Datei: mensch.js">
<code id="1">{"mensch":[</code>
<remark idcode="5">deklariert eine Datenstruktur "mensch" analog eines Arrays</remark>
<code id="1">{"nachname":"Holzflos","vorname":"Hugo"},</code>
<remark idcode="5">bildet zwei Wertepaare, die in <k>JavaScript</k> in Properties
"nachname" und "vorname" eingelesen und einem Objekt zugewiesen werden
können.</remark>
<code id="1">{"nachname":"Nixlos","vorname":"Nicole"}</code>
<remark idcode="5">Zweiter Property-Satz für das zweite <k>JavaScript</k>-Objekt
"mensch". </remark>
<code id="1">]}</code>
<remark idcode="5">Ende der Datenstruktur</remark>
</Listing>
<Listing id="9" Thema="JSON-basierte, automatisch generierte und befüllte Objektliste">
<code id="1">var results = aj.getResponse("mensch.js");</code>
<remark idcode="1">holt Werte per AJAX aus JSON</remark>
<code id="1">var resultate = results.mensch;</code>
<remark idcode="1">definiert die relevante JSON-Liste</remark>
<code id="1">var arr = new Array();</code>
<remark idcode="1">erzeugt ein Array, das bei Auswertung der JSON-Liste automatisch mit
Objekten gefüllt wird</remark>
<code id="1">for (var pkat in resultate) {</code>
<remark idcode="1">Schleife über alls mensch-Einträge in der Liste</remark>
<code id="1">if (resultate.hasOwnProperty(pkat)) {</code>
<remark idcode="1"/>
<code id="1">var myobj = new Object();</code>
<remark idcode="1">erzeuge ein neues Objekt, dem im weiteren Verlauf Properties
zugewiesen werden</remark>
<code id="1">for (var pp in resultate[pkat])</code>
<remark idcode="1">Schleife über alle Properties des jeweiligen Listeintrags</remark>
<code id="1">if (resultate[pkat].hasOwnProperty(pp))</code>
<remark idcode="1"/>
<code id="1">myobj[pp] = resultate[pkat][pp];</code>
<remark idcode="1">deklariere Property-Name (z.B. vorname) und Inhalt (z.B. Nicole) für
das aktuelle Objekt</remark>
<code id="1">arr.push(myobj);}}</code>
<remark idcode="1">Füge das gebildete Objekt in das Array</remark>
<code/>
<remark/>
<code id="1">writelog("arr.length: " + arr.length);</code>
<remark idcode="1">gibt die Objektanzahl im Array aus</remark>
<code/>
<remark/>
<code id="1">writelog("EINZELZUGRIFF: " + arr[2]["nachname"]);</code>
<remark idcode="1">greift auf ein konkretes Objekt im Array zu, gibt Inhalt des
Propertys nachname aus </remark>
<code id="1">writelog("EINZELZUGRIFF: " + arr[2].nachname);</code>
<remark idcode="1">wie vorher, andere Schreibweise</remark>
<code/>
<remark/>
<code id="1">for (var yy = 0; yy < arr.length; yy++) {</code>
<remark idcode="1">Schleife über jedes Objekt im Array</remark>
<code id="1">for (var pr in arr[yy]) writelog(pr + ": " + arr[yy][pr]);}</code>
<remark idcode="1">gibt alle Properties und deren Inhalte des aktuellen Objekts
aus.</remark>
</Listing>
<top titel="Zusammenfassung" id="">
<utop>Dieser Beitrag zeigt anhand diverser Beispiele, wie die Ansätze der klassischen
objektorientierten Programmierung (Datenkapselung, Vererbung, Polymorphie,
Reflection) im Wesentlichen und im übertragenen Sinn auch mit <k>JavaScript</k>
umsetzbar sind.</utop>
</top>
<top titel="Einführung" id="">
<utop><k>JavaScript</k> ist seit etlichen Jahren eine browserseitige Programmiersprache,
die diverse Programmteile auf den Client verlagert - mit unterschiedlicher
Unterstützung der Sprache durch die Browseranbieter. In vielen Fällen reduziert sich
die Anwendung auf die clientseitige Formularvalidierung, bevor ein Request zum
Server abgeschickt wird. Oder der <k>JavaScript</k>-Einsatz konzentriert sich auf
eines der zahlreichen Frameworks, deren Einsatz bequemer erscheinen mag als die
Anwendung der Standardsprache. </utop>
<utop>Spätestens mit AJAX hat sich die Bedeutung der Sprache verstärkt. In
wirkungsvollem Zusammenspiel mit (X)HTML CSS - auch und gerade in deren neueren
Standards - bietet <k>JavaScript</k> äußerst effiziente Möglichkeiten, u.a. per
DOM-Programmierung beträchtliche Programm- bzw. Businesslogiken auf den Browser zu
übertragen. </utop>
<utop>Etliche Webseiten setzen sehr nachhaltig auf clientseitiges <k>JavaScript</k>
(wobei auch vielversprechende Ansätze zu einer serverseitigen Anwendung von
<k>JavaScript</k> vorhanden sind). Der Trend zeichnet sich ab, dass die Server sich
in Webanwendungen verstärkt auf die notwendigsten Aufgaben des qualifizierten
Datenaustausches mit den Clients konzentrieren.</utop>
<utop>Die Bedeutung der Sprache steigt, obwohl ihr Ruf lange Zeit nicht der beste war.
Zu Unrecht? Möglicherweise war ihre enorme Flexibilität zu wenig bekannt. Darüber
kann man spekulieren. Was die objektorientierte Programmierung betrifft, lassen
viele Developer sich möglicherweise durch die Tatsache abschrecken, dass es keine
klassenbasierte Typdeklaration gibt, wie sie es aus Java, C#.NET, PHP oder C++
gewohnt sind. Da erschien es bequemer, mit GWT Java zu schreiben und damit
<k>JavaScript</k> zu erzeugen.</utop>
<utop>Ein anderer Punkt mag sein, dass der Programmierer in <k>JavaScript</k> nicht
grundsätzlich zu typsauberem Coding gezwungen wird. Das verlockt dazu, etwaige
Datenkonvertierungen weitgehend dem System zu überlassen, das im Regelfall
hoffentlich schon das Richtige tun wird. </utop>
<utop>Umso mehr, als in <k>JavaScript</k> praktisch alles ein Objekt ist (von primitiven
Typen einmal abgesehen). Dabei treibt das System mitunter merkwürdige Blüten. So
werden Leerstrings "" auch mal als 0 erkannt - was sich durch Anwendung typsauberer
Operatoren wie ===, <==, >==, !== wieder ausbügeln lässt.</utop>
<utop>Bei der Deklaration vieler instanziierbarer Typen spielen Functions eine
wesentliche Rolle (auch wenn es Wege gibt, darauf zu verzichten). Bereits hier gibt
es unterschiedliche Wege zur effizienten Deklaration. Die
<k>function tuwas() {
alert('Hallo'); }</k> lässt sich ebenso
definieren als
<k>var tuwas = function()
{ alert('Hallo'); };</k> oder als
<k>var tuwas = new
Function("alert('Hallo');");</k>. Keiner
dieser Varianten lässt sich jedoch im Sinn "var m = new tuwas();" als Objekt
instanziieren, es fehlt die innere Zuweisung von Properties per <k>this</k> bzw.
<k>prototype</k>.</utop>
<utop>Auch die Funktionsüberladung erscheint weit flexibler (wenn auch nicht ebenso
typsauber) umsetzbar wie etwa in Java. Zwar macht eine mehrfache Deklaration einer
Function wenig Sinn: eine zweite Function desselben Namens würde die erste
überschreiben. Daher implementiert man die Function nur einmal, auch völlig ohne
Parameter: dennoch ist sie mit beliebig vielen Parametern aufrufbar, die über
"arguments" einzeln ansprechbar sind.</utop>
<utop>Das funktioniert auch dann, wenn eines oder mehrere Parameter aus kompletten
Arrays oder komplexen Objekten irgendwelcher Typen ("Klassen") besteht.
Weitergehende Typprüfungen mit typeof, instanceof, parseInt können dann innerhalb
der Function implementiert werden. - Ist das flexibel genug? Vermutlich ja.
Jedenfalls bieten sich hier ideale Bedingungen, Functions für alles mögliche zu
implementieren. Ob die dann noch wartbar sind, bleibt abzuwarten.</utop>
</top>
<top titel="Typdeklaration mit this und prototype" id="">
<utop>Ähnlich flexibel lassen sich auch die Ansätze der objektorientierten
Programmierung mit <k>JavaScript</k> umsetzen. Wenn man von der aus C++, Java, oder
C# gewohnten klassenbasierten Definition abstrahiert, findet man im Function-Ansatz
mit <k>prototype</k> und <k>this</k> ein flexibles, vielleicht erfrischendes
Gegenstück. Zumal die Instanziierung mit 'var m = new Person("Sagblos");' und die
weitere Nutzung durch den "Methodenaufruf" im Wesentlichen geläufig erscheinen
dürfte.</utop>
<Listing ref="2"/>
<utop>Mit "this" (im Konstruktoraufruf) und "prototype" (ausserhalb des Konstruktors)
können öffentlich verfügbare Properties definiert werden, die jeweils
unterschiedliche Eigenschaften (Array, Function, komplexer oder primitiver Typ, ...)
aufweisen und daher ihrerseits recht komplex werden mögen. Diese Properties können
auch vererbt und dabei überschrieben werden. </utop>
<utop>So flexibel das auch erscheinen mag: Der Schutzmechanismus der Datenkapselung
fehlt noch, der für objektorientierte Programmierer von zentraler Bedeutung ist.
Dabei geht es im Kern darum, dafür zu sorgen, dass auf bestimmten Feldern nur klar
definierte Werte zulässig sind. Etwa, dass Zahlenwerte nicht negativ sein dürfen
oder dass Strings einem bestimmten Pattern entsprechen müssen. </utop>
<utop>Zu diesem Zweck werden in anderen OO-Sprachen Zugriffsmodifizierer definiert, auf
die via Getter lesend und via Setter schreibend zugegriffen wird. Im Setter wird
üblicherweise der Schutzmechanismus implementiert. </utop>
</top>
<top titel="Datenkapselung" id="">
<utop>Leider gibt es in <k>JavaScript</k> keine Zugriffsmodifizierer wie private /
protected / public. Felder, die mit prototype oder this. deklariert werden, stehen
allgemein zur Verfügung (public). Um dennoch den Schutzmechanismus der
Datenkapselung aufrecht zu erhalten, muss man sich anderer Verfahren bedienen. Es
reicht definitiv nicht aus, die zu schützenden Felder mit var statt this / prototype
zu definieren, da dieses Verfahren nicht instanzsicher ist.</utop>
<utop>Clojures sind ein möglicherweise umständliches, dafür aber probates Mittel, um das
Problem der Datenkapselung zu lösen (Listing 3). Freilich wird es recht aufwendig,
für mehrere geschützte Felder einer "Klasse" jeweils ein Clojure mit Getter und
Setter anzulegen. </utop>
<Listing ref="3"/>
<utop>Da erscheint es weit effizienter, die Datenkapselung für jedes geschützte Feld mit
allen getter- und Setter-Methoden in einen separaten Typ auszulagern, der eine
spezielle Clojure implementiert (z.B. myhiddeninteger). Dort kann das geschützte
Feld einmalig gekapselt und via get und set verfügbar gemacht werden. Nebenbei kann
man sich auf diese Weise die mühsame Schreibarbeit sparen, für jedes geschützte Feld
jeweils ein getter und setter anzulegen. Im eigentlichen Property wird der
Clojure-Typ instanziiert; damit kann Getter- bzw. Setter des Clojures gezielt
aufgerufen werden. </utop>
<utop>Listing 4 stellt einen etwas komplexeren Typ zur Bruchrechnung dar, der die beiden
Properties ZAEHLER und NENNER mithilfe von myhiddeninteger kapselt. Um nochmals die
Flexibilität der Typdefinition zu demonstrieren, wurden zwei weitere Properties
(toString, vorzeichenwechsel) ebenfalls mithilfe von this innerhalb des Typs
definiert. Andere Properties wie kuerzen und subtrahiere wurden bewusst ausserhalb
mit prototype definiert. Dennoch rufen die dahinter liegenden Functions sich
gegenseitig auf. Subtrahiere ist zudem so designt, dass hier ein weiteres
Bruch-Objekt generiert wird.</utop>
<Listing ref="4"/>
</top>
<top titel="Vererbung, Polymorphie" id="">
<utop>Listing 5 definiert den Typ "Fahrzeug" mit den Properties "fahren" und "ausgabe".
In den "erbenden" Typen "Fahrzeug.Auto" und "Fahrzeug.Boot" wird "fahren"
überschrieben, "ausgabe" aber beibehalten. Nun wird für jeden Typ eine eigene
Instanz erzeugt, die jeweils beide Properties aufruft. Während "ausgabe" für jedes
Objekt unverändert bleibt, wird "fahren" polymorph aufgerufen, abhängig von der
typabhängigen Implementierung.</utop>
<Listing ref="5"/>
<utop>Interessant ist auch der Aufruf von "instanceof": ein als Fahrzeug.Boot
deklariertes Objekt bbb wird zur Laufzeit sowohl als Object, als Fahrzeug sowie als
Fahrzeug.Boot erkannt - nicht jedoch als "Boot", und selbstredend auch nicht als
"Fahrzeug.Auto.". Dagegen gibt "typeof(bbb)" lediglich "object" zurück.</utop>
<utop>Dieser Ansatz erlaubt, selbst definierte Typen in Kategorien zusammenzufassen, die
fachlich und sachlich zusammen gehören. Wenn die objektorientierte Programmierung in
<k>JavaScript</k> - wie erwartet - weitere zunehmende Bedeutung erlangt, scheint
eine bessere Strukturierung der Codes im Sinn der Java-Packages, der Namespaces
unter .NET bzw. XML sehr empfehlenswert.</utop>
</top>
<top titel="Reflection" id="">
<utop>Nützlich habe ich gefunden, dass sich alle Properties eines Objekts zur Laufzeit
einzeln ansprechen und auch bedingungsgesteuert aufrufen lassen (Listing 6). Mit
einiger Fantasie kann man das aufrufende Objekt vielleicht als Array aus Properties
betrachten, deren Elemente in einer Schleife angesprochen werden können. Das ist
brauchbar beim Abarbeiten von Objektlisten (Arrays), deren Einzelobjekte
verschiedenen Typs sind, bei denen bestimmte Methoden polymorph aufgerufen werden
sollen.</utop>
<Listing ref="6"/>
<utop>Ebenso brauchbar ist "instanceof", um eine Vererbungshierarchie zu überprüfen: bbb
ist sowohl ein Object als auch ein Fahrzeug als auch ein Fahrzeug.Boot (vgl. Listing
5). Zudem lässt sich über "hasOwnProperty" prüfen, ob das jeweilige Property geerbt
wurde. Für die Aufrufbarkeit des Propertys ist es ohne Bedeutung.</utop>
</top>
<top titel="Objekt- Deserialisierung" id="">
<utop>Freilich gibt es grundsätzlich noch einfachere Wege, objektorientiert zu arbeiten.
Es ist gar nicht erforderlich, zuerst aufwendig einen komplexen Typ im Sinn einer
Klasse zu deklarieren und dann über var x = new Type(); zu instanziieren. Objekte
lassen sich einfach mit dem Object-Konstruktor instanziieren, und ebenso einfach
lassen sich diesem Objekt zur Laufzeit diverse (public-) Properties zuweisen.
Selbstverständlich gilt das auch für Functions wie der toString-Function. Es handelt
sich dabei um vollwertige Objekte, deren Properties zur Laufzeit angesprochen bzw. -
als functions- aufgerufen werden können.</utop>
<Listing ref="7"/>
<utop>Dieses Prinzip lässt sich effizient bei JSON- basiertem AJAX verwenden (Listing
8). Die in JSON definierte Datenstruktur beinhaltet sämtliche Informationen, die man
zur automatischen Instanziierung von Objekten und deren Properties (sowie deren
inhaltlicher Zuweisung) benötigt. Listing 9 demonstriert das Vorgehen. Für jeden
mensch-Eintrag in JSON wird ein neues Objekt generiert; aus jedem relevanten
JSON-Wertepaar wird in diesem Objekt ein Property gebildet und inhaltlich befüllt.
Schliesslich wird das Objekt in ein vorher definiertes Array eingefügt, aus dem es
dann einzeln (direkt per Index oder allgemein per Schleife) angesprochen werden
kann.</utop>
<Listing ref="8"/>
</top>
<top titel="Ein paar Worte zu JSON" id="">
<utop>Es scheint überflüssig, daran zu erinnern, dass die <k>JavaScript</k> Object
Notation (JSON) im Kern <k>JavaScript</k>-Objekte definiert. Das gültige
JSON-Dokument muss per eval() auslesbar sein. Solange eval() keine Fehler aufwirft,
scheint demnach alles in Ordnung zu sein. Hier sollte die Frage erlaubt sein, wie
flexibel bzw. tolerant eval() bei der Interpretation von JSON-Dokumenten ist.</utop>
<utop>Solange die JSON-Felder im Grunde genommen Datensätze sind, handelt es sich
vermutlich um recht einfache Datenstrukturen, bei denen keine ernsthaften Fehler zu
erwarten sind. Da JSON aber auch in anderen Sprachen eingesetzt wird und zum
Datenaustausch zwischen Anwendungen dient, sind die Techniken zur Generierung der
(komplexeren) Dokumente vielfältig. Da können Programmierfehler nicht ausgeschlossen
werden.</utop>
<utop>Beispielsweise könnte es vorkommen, dass ein Listing mehrerer gleichnamiger Felder
nicht als Array ausgegeben, sondern sequenziell hintereinander weggeschrieben wird.
Etwa so: {"nachname":"Nixlos", "vorname":"Nicole", "vorname":"Nina"}. In eval()
werden keine Fehler gemeldet, also fällt das gar nicht auf. eval() "schluckt" den
ersten Vornamen "Nicole" und reduziert die Vornamenliste auf "Nina". JSONLint
(http://jsonlint.com/) verfährt analog und attestiert abschließend "Valid JSON". Der
JSON Formatter & Validator (http://jsonformatter.curiousconcept.com/) erkennt
dagegen sogar beide Vornamen "Nicole" und "Nina" und bestätigt mit "Validation
Result: Valid". Ein JSON-Schema (analog XSD), das die Dokumentstruktur überprüfen
würde, gibt es nicht. Und wenn das JSON-Dokument mehrere Megabyte umfasst, dann wird
kaum ein Developer im Dokument auf Fehlersuche gehen.</utop>
<utop>Wenn - wie bei Listing 9 - die weitere Logik darin besteht, das JSON-Dokument in
mehrere Objekte zu überführen, deren Properties zur Laufzeit aus den JSON-Feldnamen
generiert und aus deren Inhalten befüllt wird, dann wäre das Ergebnis "Nina Nixlos";
"Nicole" würde überschrieben - ohne jegliche Fehlermeldung. Vermutlich würde der
Fehler erst gefunden, wenn "Nicole Nina" sich über ihren unvollständigen Vornamen
aufregt.</utop>
<Listing ref="9"/>
<utop>Damit betreten wir das weite Feld mangelnder Datenqualität, die übrigens auch
durch Validierungen des Inputs (analog XML-Schema-Validierung) nicht restlos
überprüft werden kann (sofern davon bei JSON überhaupt eine Rede sein kann).
Angenommen, ein <k>JavaScript</k>-Objekt hat ein MUSS-Property "Alter", das seine
Daten aus dem JSON-Dokument (Feld: jsAlter) bezieht. Wenn "jsAlter" aber im
JSON-Schema des Inputstreams nur optional definiert wäre, würde sein Fehlen nicht
auffallen. Eine leistungsfähiges Objekt-Mapping müsste diesem Umstand Rechnung
tragen. </utop>
<utop>Ein anderes Beispiel, das den konzeptionellen Unterschied zwischen JSON und XML
verdeutlicht: XML ist reihenfolgeorientiert, JSON ist Array-orientiert. JSON ist
höchstens im einfachsten Fall gleichzusetzen mit einem "schlanken" XML ohne spitze
Klammern und schliessende Tags, dafür mit {}, [], Kommata und ":".
Standard-XSLT-Stylesheets, die XML nach JSON transformieren, mögen aus optional
alternierenden Elementen zwei JSON-Arrays generieren; der sachliche Zusammenhang der
Information ginge jedoch verloren (Listing 10). </utop>
<utop>Wie es aussieht, ist die Function eval() ebenso hochflexibel wie <k>JavaScript</k>
selber. Daher taugt sie auch nur bedingt als Prüfkriterium für eine saubere
Datenqualität von JSON-Dokumenten. JSON ist auch kein Webservice, der durch eine
Description eine Kontrolle des Dokuments und der Datenstruktur ermöglicht. Das
sollte man berücksichtigen, bevor man sich auf ein vermeintlich problemloses
Objekt-Mapping zwischen unterschiedlichen Anwendungen via JSON freut.</utop>
</top>
<top titel="Fazit" id="">
<utop>Wer <k>objektorientierte</k> gleichsetzt mit <k>klassenbasierter</k>
Programmierung, mag in <k>JavaScript</k> keine vollwertige Alternative zur
klassenbasierten Deklaration in Java, C#.NET, C++, ... zu erkennen. Die dort
entwickelten Konzepte sind weit ausgereifter und vor allem typsicherer als in
<k>JavaScript</k>. Zudem ist <k>JavaScript</k> mit einer Vielzahl globaler
Variablen, Objekte, Functions etc. fast überladen, die mindestens ebenso
problematisch wie flexibel sind. Denn globale Variablen lassen sich leicht -
unbeabsichtigt oder nicht - überschreiben, und das stellt durchaus ein
Sicherheitsrisiko dar (das sich andererseits auch wieder mit
<k>JavaScript</k>-Bordmitteln objektorientiert ausschalten lässt).</utop>
<utop>Aber die beträchtliche Flexibilität der Sprache <k>JavaScript</k>, die sich in
unterschiedlichen Browsern bewähren muss (deren Hersteller die
<k>JavaScript</k>-Standards nicht einheitlich unterstützen), rechtfertigt bereits
ihre Existenz. Und dass diese Flexibilität erlaubt, die wesentlichen Konzepte der
Objektorientierung nachzubilden, ohne dass der Entwickler sich allzusehr verbiegen
muss, ist eine respektable Leistung. </utop>
<utop>Neben herkömmlichen Browsern gibt es zusätzliche, neue Anforderungen im
Webdevelopment. Auch für die Vielzahl mobiler Clients stellt sich die Frage nach
einer leistungsfähigen Sprache, die einen Teil der Businesslogik übernehmen kann. Da
<k>JavaScript</k> Performance und Flexibilität bereits hinreichend bewiesen hat, ist
sie ein sehr ernstzunehmender Kandidat für weitere Implementierungen. </utop>
</top>
</Artikel>
<Artikel titel="Software - Qualität" Datum="16.03.2014" jahr="2014" publish="true"
id="softwarequalitaet_grupe_training">
<top titel="Zusammenfassung">
<utop>Kaum ein Informatiker würde ernsthaft behaupten wollen, dass Software generell
einwandfrei arbeitet. Zu groß sind die wirtschaftlichen Schäden, die jedes Jahr auf
fehlerhafte Software zurückgehen. </utop>
<utop>Trotz etlicher Normen und allgemein anerkannter Verfahren ist fehlerfreie Software
ein Ideal, das längst nicht erreicht ist. Dieser Beitrag listet einige bekannte
Stolpersteine auf, an denen Projekte scheitern können.</utop>
</top>
<top titel="Planungsqualität">
<utop>Eine solide Planung ist Grundvoraussetzung für das Gelingen jedes Vorhabens, nicht
nur in der Softwareentwicklung. Daher gehört neben einer detaillierten Analyse der
Problemstellung auch ein sinnvolles und wohldurchdachtes Konzept, wie die gestellte
Aufgabe gelöst werden soll. </utop>
<utop>Das gilt ebenso für einmalige Projekte wie für wiederkehrende, gleichartige
Aufgaben, die zwar kundenindividuell, aber dennoch nach einem Basiskonzept umgesetzt
werden müssen. Ohne behaupten zu wollen, dass man sich bei einmaligen Aufgaben mehr
Fehler erlauben dürfte als bei wiederkehrenden, gleichartigen, sieht es doch so aus,
dass die Pflicht zur peinlichen Sorgfalt bei wiederkehrenden Aufgaben deutlicher zu
Tage tritt. </utop>
<utop>Einer der zahlreichen Aspekte für den wirtschaftlichen Erfolg des Projekts ist die
exakte Schätzung des Zeitaufwands. Zwar stehen ausgefeilte Netzplantechniken bereit,
um alle erforderlichen Vorgänge und deren Abhängigkeiten, kritischer Wege,
Meilensteine und den Bedarf an Personal und Ressourcen detailliert zu planen.
Theoretisch ist es also möglich, auf der Basis der Planung exakte
Fertigstellungstermine zu berechnen. </utop>
<utop>Freilich hält sich die Realität nicht immer an jene Annahmen. Der tatsächliche
Zeitbedarf einzelner Vorgänge kann deutlich größer ausfallen als geplant. Aufgrund
von Vorgangsverknüpfungen kann der Liefertermin gefährdet sein. Um diesen dennoch
halten zu können, werden ursprünglich fest eingeplante Leistungsphasen verkürzt.
Nacheinander fallen die <k>Dokumentation</k> und Teile des Testings dem Zeit- bzw.
Kostendruck zum Opfer. </utop>
<utop>Beispiele, in denen Projekte in einem Desaster endeten, sind überreich vorhanden.
Unzureichend analysierte Problemstellungen, untaugliche oder unklar formulierte
Lösungsansätze, fehlerhafte Aufwands-, Zeit- und Kostenschätzungen durchziehen
manches Projekt folgenreich bis zum Ende. Kleine Planungsfehler entfalten mitunter
verheerende Wirkungen.</utop>
<utop>Auch auf der Kundenseite bestehen nicht immer klare Vorstellungen, was genau
implementiert werden soll. Dominante Persönlichkeiten bringen mitunter Ideen ein,
die bei näherer Betrachtung verbesserungsfähig sind. Nachträgliche Umplanungen im
laufenden Projekt können auf suboptimalen Kompromissen beruhen, die die
Planungsqualität kaum erhöhen.</utop>
</top>
<top titel="Qualität der Dokumentation">
<utop>Wichtig für die Nutzung, aber auch für die Wartbarkeit einer Software ist ihre
<k>Dokumentation</k>. Sie dient ebenso zu Nachweiszwecken über erbrachte Leistungen
wie auch dem Know-How-Transfer zwischen den diversen Beteiligten eines Projekts,
etwa wenn unterschiedliche Developer den Quellcode warten oder
weiterentwickeln.</utop>
<utop>Eine gute <k>Dokumentation</k> besteht einerseits aus separaten Dokumenten wie
Spezifikation, Testdokumentation oder Glossar. Anderersites besteht sie aus
Kommentaren im Sourcecode, die die Bedeutung bestimmter Variablen erläutern, oder
die erklären, warum eine bestimmte Logik angewendet wurde. Obwohl es für den Aufbau
von Software-<k>Dokumentationen</k> Normen gibt, ist die Praxis teilweise
ernüchternd.</utop>
<utop>Es beginnt damit, dass nicht alle Fachplaner in der Lage sind, eindeutig und
unmißverständlich darzulegen, was genau das Ziel der Anwendung ist und welche
Features erforderlich sind. Diesbezügliche Rückfragen der Entwickler mögen wohl
Klärung schaffen; jene Zusatzinformationen werden jedoch nicht immer konsequent in
der <k>Dokumentation</k> festgehalten. Das ist spätestens dann problematisch, wenn
die Informationen nur im Kopf des Entwicklers stehen und dieser nach einiger Zeit
das Unternehmen verlässt.</utop>
<utop>So stimmen nach Fertigstellung eines Projekts die <k>Dokumentation</k> und der
Sourcecode nicht immer überein. Für einen neuen Entwickler, der in ein bestehendes
Projekt einsteigen soll, kommt viel darauf an, sich schnell zurecht zu finden. Wenn
die Arbeit jedoch - wegen fehlender oder qualitativ schlechter <k>Dokumentation</k>
- mit einer zeitintensiven Forschungstätigkeit beginnt, kann der Liefertermin in
Gefahr geraten.</utop>
<utop>Aber auch übernehmende Entwickler dokumentieren ihre aktuellen
Recherche-Ergebnisse keineswegs immer. Und da der Liefertermin drängt, ist für eine
ausführliche <k>Dokumentation</k> sowieso keine Zeit. Die Abweichungen Code /
<k>Dokumentation</k> werden nicht behoben, sondern verschärfen sich möglicherweise
noch.</utop>
<utop>Selbst wenn die <k>Dokumentation</k> erfolgt, ist sie nicht immer hilfreich.
Teilweise wird sie in einem Umfang geführt, die ein intensives Studium der weit
verzweigten Erläuterungen, Links und Hinweise erfordern. Wohl dem, der sofort weiss,
wo er mit seiner Arbeit ansetzen muss!</utop>
<utop>Andere <k>Dokumentationen</k> sind nicht hilfreich, weil sie nur erklären, was aus
der nächsten Quelltextzeile ohnehin klar hervorgeht ("// Loop über ...") oder wenn
der Developer sich in Nebensächlichkeiten verliert, die Erläuterung von
Grundgedanken und Leitideen jedoch unterlässt.</utop>
<utop>Wieder andere <k>Dokumentationen</k> fallen durch Kürze und Knappheit auf. In
möglichst nur drei Worten wird die gesamte Komplexität von mehreren Tausend
Quelltextzeilen ausgedrückt. Diese drei Worte mögen zwar präzise sein; hilfreich
sind sie nicht, wenn die Komplexität der Problemstellung unbekannt ist. - Hilfreich
ist auch nicht, Variablen, Programme oder Aliasse nicht mit sprechenden Namen zu
benennen, sondern mit "a", "x" oder "hugo" - natürlich ohne jegliche Erläuterung. </utop>
<utop>Bedenklich ist der Ansatz, zuerst alles zu implementieren und die
<k>Dokumentation</k> hinterher zu machen. Zwar ist eine systematische Syntax
hilfreich, die erlaubt, dass Tools wie Javadoc aus dem Quellcode automatisch ein
übersichtliches Dokument erzeugen. Dieses ersetzt aber weder eine solide,
überschaubare Spezifikation noch fehlende Kommentare im Sourcecode.</utop>
<utop>Überproportional teuer wird eine fehlende oder qualitativ schlechte
<k>Dokumentation</k> bei hoher Fluktuationsrate der Mitarbeiter. Während ein
Entwickler, der seine Gedanken und Konzepte schriftlich niederlegt, dazu wenige
Minuten braucht, benötigt jemand, der sich ohne diese Hilfe in die Thematik einlesen
muss, dazu mehrere Stunden bis Tage. Mit Hilfe einer guten <k>Dokumentation</k> kann
er das in einem Bruchteil der Zeit schaffen. Damit liegt der Zeitvorteil einer guten
<k>Dokumentation</k> bei einem Vielfachen jener kurzen Dauer, die nötig ist, sie zu
erstellen.</utop>
<utop>Beim Entwickeln in großen Teams ist hilfreich, sämtliche separaten Dokumente mit
Hilfe eines leistungsfähigen Dokument-Managementsystems zentral abzuspeichern. Die
Leistungsfähigkeit jener Software gehört zur Dokumentationsqualität. Systeme, bei
denen wesentliche Informationen gelöscht werden müssen, um eine bestimmte Systematik
einzuhalten, sind hier wenig geeignet.</utop>
</top>
<top titel="Testen">
<utop>Professionelles <k>Testen</k> ist wichtig, um Fehler zu finden, bevor der Kunde
sie findet und moniert. Ob das allgemein große Vertrauen in automatisierte Testings
auch gerechtfertigt ist, sei dahin gestellt. </utop>
<utop>Entscheidend für den Testerfolg ist eine hinreichende Anzahl qualitativ
unterschiedlicher Testszenarien und Sonderfälle. Deren sorgfältige Planung und
Auswahl hängt weitgehend von dem jeweiligen Entwickler ab. Beauftragt man mehrere
Tester, so erhält man ggf. mehrere unterschiedliche Ergebnisse. Die Übersicht der
Anforderungen für die Gesamtstrategie der Tests wird mit steigender Projektgröße
zusätzlich vernebelt.</utop>
<utop>Nicht immer ist eindeutig klar, wie viele qualitativ unterschiedliche
Testszenarien nötig sind, um sicher zu sein, dass die Software ausreichend getestet
wurde. Wiederholungen desselben Tests sind überflüssig. Es macht wenig Sinn,
hundertmal denselben Testfall durchzuspielen und hinterher zu behaupten, man habe
ausreichend getestet. </utop>
<utop>Zwar lassen sich Testdaten und Testfälle automatisch generieren. Derlei Verfahren
sind unabdingbar, etwa um sicherstellen zu können, dass ein Prozessor grundsätzlich
exakt arbeitet - und nicht in äußerst seltenen Fällen für ganz bestimmte Input-Daten
fehlerhafte Ergebnisse berechnet. </utop>
<utop>Die Qualität der generierten Testdaten hängt wesentlich von jener Software ab, die
sie erzeugen. Wenn der Algorithmus ganz seltene Problemfälle nicht generiert, dann
wird der Fehler nicht durch <k>Testen</k> entdeckt, sondern bereitet dafür später
Probleme im Produktivbetrieb.</utop>
<utop>So sind <k>Performance-</k>, <k>Last-</k>, <k>Regressions-</k> und
<k>Komponententests</k> weitgehend automatisierbar. Was nicht heisst, dass sie in
der Praxis auch durchgeführt werden. Nicht selten verzichtet man auf
Komponententests; dadurch werden Fehler in den Einzelkomponenten (und deren
Folgefehler) schwer auffindbar. </utop>
<utop>Dagegen werden <k>Funktionstests</k> häufig manuell durchgeführt - mitunter ganz
am Schluss, nachdem alles fertig programmiert wurde. Zudem kommt es vor, dass die
Testumgebung nicht der späteren Produktivumgebung entspricht, was die Qualität der
Testergebnisse generell in Frage stellt.</utop>
<utop><k>Penetrationstests</k>, die zur Aufdeckung von Sicherheitsmängeln in der
erstellten Software sowie im gesamten System dienen, werden bei einem beträchtlichen
Anteil aller Projekte überhaupt nicht durchgeführt. Überhaupt sind
Sicherheitsaspekte kein integraler Bestandteil bei etlichen Tests.</utop>
</top>
<top titel="Qualität der Implementierung">
<utop>Auf den ersten Blick sind Qualitätsprobleme bei der Implementierung gar nicht
vorstellbar. Syntaxfehler werden spätestens vom Interpreter oder Compiler gemeldet.
Logische Fehler fallen spätestens beim Test auf. Profiler kommen bei der
Performanceanalyse zum Einsatz. Versionierungssysteme unterstützen bei der
Entwicklung im Team. </utop>
<utop>Und dennoch: unterschiedliche Zugriffsrechte auf Datenbanken oder aktuelle
Software schränken effizientes Arbeiten ein. Nur wenige Mitarbeiter haben Zugriff
auf das gesamte benötigte Equipment. Nur diese Wenigen beherrschen das System so
weit, dass sie in kritischen Situationen eingreifen können. Auch unterschiedliche
Wissensstände (Programmierer mit geringer Erfahrung kosten deutlich weniger) tragen
nicht dazu bei, angestrebte Qualitätsstandards einzuhalten. </utop>
<utop>Je näher der Abgabetermin, desto größer die Hektik und desto geringer die
Sorgfalt. Dokumentiert wird nicht mehr, Tests fallen aus - keine Zeit. Die
Deckungsgleichheit zwischen Spezifikation und Implementierung geht zunehmend
verloren. Nicht selten werden Programmteile, an denen wochenlang getüftelt wurde, in
letzter Minute auf den Server geschoben.</utop>
<utop>Features, die bei hektisch durchgeführten Abschlusstests immer noch Probleme
verursachen, werden kurzerhand gelöscht. Wenn das auch nichts mehr hilft, verzichtet
man auf störende Tests. Somit wissen weder die Kunden noch die Entwickler genau,
welche Codeteile im Produktivbetrieb tatsächlich wirksam sind.</utop>
</top>
<top titel="Datenqualität">
<utop>Eine wichtige Grundlage für die Funktionstüchtigkeit einer Software ist die
Qualität der Daten, mit denen sie arbeitet. Diese Daten können entweder bereits
vorliegen, sie können neu eingehen, etwa durch B2B-Interaktion, oder sie können
durch Programme generiert werden. </utop>
<utop>Um effizient damit arbeiten zu können, müssen jene Daten korrekt und verfügbar
sein. Sehen wir mal von Vorfällen ab, in denen eine Datenbank oder ein Server
vorübergehend den Dienst aufgekündigt hat, so ist die Bereitstellung etlicher
Terabytes an Daten technisch kein Problem. </utop>
<utop>Korrekte Daten müssen aktuell und vollständig sein. Sie sind eine wesentliche
Grundlage für effizientes Arbeiten. Kontaktadressen zum Kunden, zuverlässige
Stammdaten sollten selbstverständlich sein. Nur auf der Basis einer aktuellen und
vollständigen Datenlage lassen sich komplexe Geschäftsmodelle implementieren oder
Managemententscheidungen von weittragender Bedeutung treffen.</utop>
<utop>Ein zentraler Qualitätsmaßstab für die Korrektheit der Daten sind exakt definierte
Datenstrukturen, die den Zeichensatz, den Datentyp der jeweiligen Felder sowie
etwaige gegenseitige (referenzielle) Abhängigkeiten zwischen ihnen umfassen (so muss
die Rechnungsadresse gefüllt sein, um eine Rechnung als Art der Zahlung eintragen zu
können). Hier kommen Regeln für die Prüfung der Konsistenz, der Plausibilität und
der Integrität zum Einsatz.</utop>
<utop>Das setzt jedoch voraus, dass die betreffenden Datenstrukturen einwandfrei
definiert wurden. In nicht wenigen Fällen werden jedoch nur die Datentypen der
Felder deklariert, und auch das recht lax: da wird eine 5stellige Zahl aus Gründen
der Bequemlichkeit als 20stelliger String benannt. Auf gegenseitige Abhängigkeiten
zwischen den Feldern wird gleich ganz verzichtet (zur Vermeidung von "Spannungen").
Derlei Nachlässigkeiten schränken die Brauchbarkeit der Datenstrukturen als
Qualitätsmaßstab ein. Die Validierung von Daten mithilfe fragwürdiger Strukturen
ist selbst auch fragwürdig - sofern überhaupt validiert wird.</utop>
<utop>Hinzu kommt, dass es schwierig ist, Datenstrukturen zu Beginn eines
Softwareprojektes einmal für alle Zeiten unveränderlich zu definieren. Einige
Anforderungen bzw. Abhängigkeiten verfallen, andere kommen neu hinzu. (Wer immer nur
nach Plan arbeitet, kriegt bestenfalls, was der Plan vorsieht, aber nicht, was er
braucht.) Wenn die Datenstrukturen (und die darauf aufbauende Anwendung) nicht
"mitwachsen", dann stehen zahlreiche Anwender der Software bald vor dem Problem,
dass zusätzliche Informationen nicht in die vorgegebene Struktur passen. </utop>
<utop>Wichtige Zusatzinformationen werden dann häufig außerhalb der eigentlichen
Anwendung in unsystematischer Form abgespeichert. Sie liegen in unterschiedlichen,
nicht miteinander verknüpften Datentöpfen, die getrennt voneinander gepflegt werden,
von sehr unterschiedlicher Aktualität sind und damit ein ideales Arbeitsfeld für
Hobby-Archäologen darstellen. (Eines ihrer Forschungsergebnisse dürfte die
Feststellung sein, dass zahlreiche Dokumente aus Microsofts Office-Familie
stammen.)</utop>
<utop>Gravierender ist, dass die "Schattendaten" sich einer systematischen
Auswertbarkeit durch die eigentliche Anwendung entziehen. Sie stehen für die komplex
definierten Geschäftsmodelle oder für Managemententscheidungen oft nicht zur
Verfügung. Fehlentscheidungen sind vorprogrammiert. Auch ist die doppelte Pflege der
ursprünglichen Datenstrukturen und der sie ergänzenden "Schattendaten"
kostenintensiv. </utop>
<utop>Fehlende, fehlerhafte oder veraltete Daten sind IT-Kostentreiber von
herausragender Bedeutung. Allein der Kostenaufwand für die Recherche und Korrektur
der Daten ist beträchtlich. Weit gravierender können Folgekosten werden: die
wirtschaftlichen Schäden, die durch fehlerhafte Software / mangelhafte Datenqualität
verursacht werden, umfassen jährlich stolze Milliardenbeträge. </utop>
<utop>Tools für die Messung von Datenqualität sind auf dem Markt ebenso verfügbar wie
strategische Ansätze, diese zu verbessern. Aber der bloße Einsatz von Tools hilft
wenig, wenn der nachhaltige Wille fehlt. </utop>
</top>
<top titel="Sicherheit">
<utop>Ein weiterer Kernaspekt für die Geschäftsinteressen jedes Softwareproduzenten und
seiner Kunden ist die Sicherheit der Applikationen. Selbstverständlich soll
Unbefugten verwehrt bleiben, Daten bzw. die Businesslogik ausspionieren oder gar
beeinflussen zu können. Selbstverständlich ist Datenschutz von elementarer
Bedeutung. </utop>
<utop>In der Praxis beschränkt sich das teilweise auf den Einsatz handelsüblicher Tools,
die zwar vor Malware und Viren schützen, aber nicht die selbst geschriebene Software
absichern. Passwortgeschützte Web-Logins sind nicht hinreichend, wenn das darunter
liegende System offen ist wie ein Scheunentor. Vergebene Adminrechte werden nicht
durchweg konsequent wieder zurück genommen, wenn die für die Vergabe vorgesehenen
Aufgaben beendet wurden.</utop>
</top>
<top titel="Fazit">
<utop>Zwar ist das allgemeine Bewusstsein für mangelnde Softwarequalität durchaus
vorhanden, aber konkrete Aktivitäten sind auf breiter Front nicht erkennbar.
Möglicherweise sind verstärkte gesetzliche Schutzvorschriften nötig, um den Vertrieb
und Einsatz von grob fahrlässig erstellter Software einzudämmen.</utop>
</top>
</Artikel>
</Artikelliste>
wg / 25. Oktober 2020
Fragen? Anmerkungen? Tipps?
Bitte nehmen Sie Kontakt zu mir auf.
V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn
☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de