Donnerstag, 30. September 2010

Das schriftliche Wurzelziehen

Vor ein paar Tagen brachte mein Sohn interessante Rechenaufgaben aus der Schule mit: Der Mathematiklehrer hatte den Kindern das gute alte schriftliche Wurzelziehen beigebracht — ein Verfahren, bei dem die erste binomische Formel ausgenutzt wird, um sukzessive die Stellen der Wurzel zu ermitteln. Das Verfahren ist heute fast völlig in Vergessenheit geraten, wird jedoch an den Waldorfschulen noch in Ehren gehalten — ihrem ausgeprägten Konservatismus sei Dank.

Für mich war es eine nette Erfahrung, das Verfahren in Form von JavaScript-Code nachzubilden — und zwar so, dass es zum Üben benutzt werden kann. Statt die gesamte Rechnung auf einen Schlag anzuzeigen, sollten auf Knopfdruck die einzelnen Schritte ausgeführt werden. So kann man auf dem Papier das Verfahren selbst probieren und sich bei jedem Schritt vergewissern, keinen Fehler gemacht zu haben.

Das Programm soll die Rechnung, wie sie auf dem Papier entsteht, erzeugen, die Schritte sollen mit Kommentaren versehen werden, und in einem eigenen Bereich sollen kleine Nebenrechnungen ausgeführt werden — analog dem Schmierpapier, das man bei der schriftlichen Rechnung braucht.

Ich setzte mir ein Zeitlimit von drei Stunden, das ich gerade so einhalten konnte — in nostalgischer Erinnerung an die guten alten Zeiten, als ich mich noch in Prüfungen bewähren musste: "Sie haben drei Stunden — die Zeit läuft." Allerdings habe ich später noch eine weitere Stunde für ein paar Refaktorisierungen aufgewendet. Die Webseite

http://ruediger-plantiko.net/wurzeln/

zeigt das Ergebnis. Es folgen ein paar Anmerkungen zum Programmentwurf.


  • Die HTML-Seite zeigt nichts anderes als das Seitenlayout. Sie ist vollständig von JavaScript freigehalten. Auch Clickbehandler, onload-Behandler usw. sind in die Scriptdatei ausgelagert.

  • Um lesbares und dennoch kurzes JavaScript zu produzieren, verwende ich das Framework Prototype.

  • Der Unterbruch zwischen den einzelnen Rechenschritten erfordert es leider, dass Zwischenergebnisse in globalen Variablen mitgeführt werden müssen. Wenigstens werden diese globalen Variablen alle an einem Ort deklariert und an einem zweiten Ort, in einer reset()-Funktion wieder gelöscht.



Der Algorithmus besteht aus insgesamt neun Schritten. Die ersten drei Schritte werden nur einmal ausgeführt. Die nachfolgenden sechs Schritte werden so lange ausgeführt, bis keine weiteren Ziffern vom Radikanden mehr heruntergeholt werden können. Dann ist das Verfahren beendet.

Mein Entwurf packt diese neun Schritte als anonyme Funktionen in einen Array execute[], so dass sie dynamisch über einen Index angesprochen und ausgeführt werden können. Die Methode executeNextStep(), die bei Click auf den Weiter-Button aufgerufen wird, kann die Schleife dann ohne umständliche switch...case Konstrukte realisieren, indem der Schrittzähler step kontinuierlich erhöht wird, der Divisionsrest modulo 6 aber als Index für den Zugriff auf den execute-Array fungiert.[1]

Um aus der Schleife auszubrechen, empfiehlt sich eine Exception - im simpelsten Fall, wie hier, einfach in Form eines Stringobjekts, das dann angezeigt wird.

function executeNextStep() {

try {

// Schrittzähler erhöhen
incrementStepCounter( );

// Nächsten Schritt ausführen
if (step <= 2)
// Schritte 0 bis 2 nur einmal
execute[step]();
else
// Ab Schritt 3 wird es periodisch
execute[3+((step-3)%6)]();

} catch (e) {

// Ausnahme (z.B. STOP) melden und Verfahren beenden
commentOhneSchritt( "<b>" + e + "</b>");
verfahrenBeenden();
}
}

// Hier folgen die Schritte:
execute = [
function() {
comment("Zahl nach links in Zweiergruppen aufteilen");
var r = radikand+"",i;
for (i=r.length-2;i>=-1;i-=2) {
if (i>=0)
groups.unshift( r.substring(i,i+2) );
else
groups.unshift( r.substring(0,1) );
}
s = "";
groups.each( function(g) {
s+='<span class="group">' + g + '</span>';
});
write(s);
},
function() {
...
},
...
];


Diese Art, das Problem zu lösen, lässt sich sicher auch in anderen Kontexten einsetzen: Nämlich immer dann, wenn ein Algorithmus stückweise auszuführen ist, etwa wie hier in einer Schleife mit Abbruchbedingung. Der Zugriff auf Funktionen als Arrayelemente ist für eine Abfolge von Schritten naheliegender als der Zugriff über den Funktionsnamen, etwa self["schritt2"](), der ja alternativ auch möglich wäre. Die Lösung ist aber vor allem kompakter als eine switch...case...-Konstruktion, ohne an Lesbarkeit einzubüssen. Im Gegenteil, gerade die mühsam abzuhandelnden case's, die in C-artigen Sprachen (wie JavaScript) auch noch jeden case mit einem break beenden müssen, erschweren nach meiner Ansicht die Lesbarkeit von Code.

Im Rückblick fand ich es unbefriedigend, dass ich für mein geradezu spartanisches UI-Design relativ viel Zeit aufwenden musste. Für die eigentlichen Logik des Wurzelziehens brauchte ich nur knapp die Hälfte der Zeit, die ich mir vorgegeben hatte. Zu erwarten wäre eigentlich, dass man für die Problem Domain die meiste Zeit aufwenden muss, nicht für das User Interface. Das hat bei mir sicher auch damit zu tun, dass ich nicht wie ein hauptberuflicher Webdesigner ständig neue HTML/CSS-Oberflächen kreiere, sondern an einmal geschaffenen Entwürfen sehr lange und sehr genügsam festhalte, denn auch beruflich programmiere ich hauptsächlich Geschäftslogik und modifziere meist nur bestehende Entwürfe von Benutzeroberflächen. Insgesamt muss ich jedoch meine Kritik aus dem bösen Tabellenlayout wiederholen, dass ich CSS in der jetzigen Form für die Gestaltung der Präsentationsschicht einfach noch nicht griffig genug finde.

[1] Auf den Namen execute für den Funktions-Array bin ich übrigens erst nach einigem Versuchen gekommen: Ausdrücke wie execute[step]() gewinnen durch ihn an Lesbarkeit.

Keine Kommentare :