Weiter Zurück Inhalt

5. Programmierstil, oder TIMTOWTDI

Eine der auf Neulinge gelegentlich abschreckend wirkenden Eigenschaften von Perl ist neben den kryptischen eingebauten Variablen die große formale und syntaktische Freiheit in der Quellcodegestaltung. Diese Freiheit ermöglicht es fortgeschrittenen Programmierern, erstaunlich kompakten Code zu schreiben. Außerdem führen in Perl viele Wege zum Ziel. Dies ist in der Abkürzung TIMTOWTDI zusammengefaßt: There Is More Than One Way To Do It. Wie viele Wege es wirklich gibt, auch einfache Dinge in Perl auf überzeugende Weise zu komplizieren, wird in der JAPH-Sammlung dokumentiert. Die dortige Aufgabe ist, den Text Just Another Perl Hacker auszugeben. Die bizarrsten Ideen werden in einer eigenen Datei namens JAPH (Just Another Perl Hacker, zu finden auf CPAN:/misc/) gesammelt. Diese Sammlung eignet sich nicht nur zum Studium von Verfahren, die man besser nicht anwendet, sondern enthält auch ein paar wirklich überzeugende Beispiele bestimmter Funktionen und Programmiertechniken.

In den folgenden Beispielprogrammen werden im Gegensatz zu JAPH nur praktikable und nachvollziehbare Lösungen angeboten. Es kann allerdings durchaus sein, daß mehrere Wege nach Rom führen, oder sich mehrere Lösungsansätze anbieten. Dies kann unerheblich sein, so lange nur Vorlieben und Abneigungen des Programmierers eine Rolle spielen, kann aber auch ernsthafte Konsequenzen im Laufzeitverhalten oder der grundsätzlichen Logik des Programmablaufs mit sich bringen.

5.1 Laufzeitverhalten von Stilvarianten

Es ist besonders hervorzuheben, daß Perl ein deutlich unterschiedliches Laufzeitverhalten für verschiedene Operatoren und Operationen aufweist. Es empfiehlt sich immer, nach einem für Perl idiomatischen Weg zu suchen, da dieser meistens signifikant schneller abgearbeitet wird. Das folgende einfache Beispiel benutzt entweder eine konventionelle Schreibung der Addition zweier Zeichenketten oder aber den Perl-typischen Operator.


#!/usr/bin/perl

# Ein Laufzeittest

for ($i=0; $i<=50000; $i++) {
        # Konventioneller Weg, zwei Strings zu verknüpfen
        # $zeichenkette=$zeichenkette."a"
        # Perl-idiomatischer Weg zur Lösung der gleichen Aufgabe
        $zeichenkette.="a"
}

Hier sehen wir Abarbeitungszeit und Systemauslastung des Programms, wenn der konventionelle Weg beschritten wird:

$ time ./run.pl
67.85user 0.07system 1:09.43elapsed 97%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (197major+59minor)pagefaults 0swaps

Hier sehen wir Abarbeitungszeit und Systemauslastung des gleichen Programms, wobei allerdings hier der idiomatischere Weg beschritten wird:

$ time ./run.pl
0.54user 0.01system 0:01.14elapsed 48%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (197major+37minor)pagefaults 0swaps

Der Unterschied (eine Sekunde statt einer Minute Laufzeit bei halber statt voller CPU-Auslastung) ist erstaunlich groß, wobei ja in der ganzen Laufzeit von einer Sekunde bereits der Aufruf des Perl-Interpreters und die Laufzeitkompilation enthalten sind. Es kann durchaus sein, daß der konventionelle Programmierstil bei kleinen Datenmengen, wie sie typischerweise zur Validation eines Programms benutzt werden, zu scheinbar vorzeigbaren Ergebnissen führt, da diese ja korrekt sind. Mit kleinen Datenmengen läßt sich aber keine Aussage zum Laufzeitverhalten machen. Erst mit ,,echten'' Datenmengen stellt man dann fest, daß das Programm Antwortzeiten produziert, die es für den vorgesehenen Zweck völlig unbrauchbar machen können. Im obigen Beispiel wächst die Laufzeitdifferenz exponentiell mit der Datenmenge; wenn für $i ein kleiner Maximalwert (z. B. 1000) eingesetzt wird, ist kaum ein Unterschied zu spüren. Wird aber statt der 50000 unseres Beispiels ein noch größerer Wert eingesetzt, so wachsen die Antwortzeiten ins Uferlose.

5.2 Logisches Programmgerüst

Wenn sich das Problem stellt, aus einer lateinischen Transliteration beispielsweise einen kyrillischen Text zu generieren, so kommt man nicht umhin, der Behandlung von Digraphen besondere Aufmerksamkeit zu widmen. Während für einfache 1:1-Ersetzungen die Funktion tr völlig ausreicht, kann man mit ihr keine Texte übersetzen, in denen mehrere Buchstaben des Transliterats für einen Buchstaben des Ziels stehen. Man kann aber mit einer Reihe von Substitutionen arbeiten. Wenn im lateinischen Text s und sh auftreten, die für distinkte kyrillische Buchstaben stehen, so könnte ein Ansatz so aussehen:


s/sh/<Kode für sh>/g;
s/s/<Kode für s>/g;

wobei sinngemäß die anderen Buchstaben auch zu substituieren sind. Es kommt allerdings auf die Reihenfolge an. Würden beide Zeilen miteinander vertauscht, würde das Skript kein sh mehr im Quelltext finden und der Zieltext wäre verfälscht.

Ein weiteres Problem dieses Ansatzes ist es, daß die Datenräume von Quelle und Ziel nicht voneinander getrennt sind. In dem Maße, wie die Substitutionen für jeden einzelnen Buchstaben des Alphabetes vollzogen werden, verschwindet der Quelltext aus der Variablen, bis er schließlich nicht mehr vorhanden ist. Er könnte zwar vor der Substitutionsoperation in einer anderen Variablen gesichert werden, aber prinzipiell wirft der hier vorgestellte Ansatz Probleme auf, die bei komplexen Programmen zu unübersehbaren Schwierigkeiten führen können.

Sinnvoller ist es daher, Funktionen zu schreiben, die den Quelltext unangetastet lassen und einen neuen Wert zurückgeben:


$neu=transliteriert($alt);

sub transliteriert {                            # Funktion wird definiert
        my $eingabe=$_[0];                      # Parameter liegen in @_
        # Kommandos                             # Hier wird gearbeitet...
$transliteriert=$irgendein_lokaler_wert;        # Rückgabe
}

Das angegebene Fragment ist natürlich nur als Gerüst zu verstehen, da die eigentliche Transliterationsroutine ja sehr komplex sein kann und von Fall zu Fall neu zu schreiben ist.


Weiter Zurück Inhalt