| Perl-Basics | Perl-Enhanced | ||
|---|---|---|---|
Tips und Tricks zu Perl
Styleguide: Einführung
Jeder Programmierer bekommt im Laufe der Zeit seinen eigenen Programmierstil, der auf den (oft langjährigen) Erfahrungen mit der Sprache basiert, aber vielleicht auch auf Erfahrungen mit anderen Sprachen. Gerade in einer so mächtigen und variablen Programmiersprache wie Perl bilden sich dadurch unzählige Stilrichtungen heraus.
Als Beispiel möchte ich einige verschiedene Schleifenkonstruktionen für die Ausgabe einer Liste verwenden:
1. C-Stil:
my $i;
my $max = 50;
for ($i=0; $i<=$max; $i++){
printf ("%s\n", $list[$i]);
}
2. Shellscript-Stil:
while ($line = shift(@list)){
print "$line\n";
}
3. Wenn jemand schon länger mit Perl programiert, wird er (oder sie) vielleicht eher zu Schleifenkonstruktionen wie den folgenden greifen:
for my $i (0..$#list){
print "$list[$i]\n";
}
oder 4.:
print "$_\n" foreach @list;oder vielleicht sogar 5.:
print map "$_\n", @list;
Diese Beispiele machen im Grunde alle das gleiche: sie geben jedes Element der Liste @list in einer Zeile aus. Dabei haben die Lösungen folgende Vor- und Nachteile:
- Schleifenkonstruktion im C-Stil:
- Für einen Programmierer, der C kennt, sehr leicht zu lesen. Für jemanden, der diese spezielle Schleifenkonstruktion nicht kennt, ist es wohl ziemlich schwierig, deren Funktionsweise zu eruieren.
- Man erhält den Index eines Elements.
- Wenn die Liste größer als 50 Elemente wird, muß man die Variable $max ändern. Da in Perl Listen eine dynamische Angelegenheit sind, kann dies ein gravierender Nachteil sein. Den kann man aber dadurch leicht umgehen, daß man anstelle von ($i=0; $i<=$max; $i++) folgendes schreibt: ($i=0; $i<=$#list; $i++).
- Die Laufvariable $i ist auch außerhalb der Schleife bekannt.
- Schleifenkonstrukt im Shellscript-Stil:
- Syntax ist kompakt und auf das Wesentliche konzentriert.
- Die Liste @list ist nach Abarbeitung der Schleife leer.
- Schleifenkonstrukt in "Perl-Stil":
- Man erhält den Index des Elementes.
- Laufvariable $i blockweit deklariert.
- Schleifenkonstrukt in "Perl-Stil 2":
- Konzentriert sich auf das Wesentliche.
- Man muß wissen, dass $_ die Standard-Laufvariable ist.
- Schleifen-"Ersatz": map:
- Die "flotte" map-Funktion wird verwendet.
- Man muß die map-Funktion kennen. Da diese wohl etwas erweitertes "Vokabular" zu sein scheint, dürfte dies wohl nicht bei jedem, der sich ein Perl-Script ansieht, der Fall sein.
Welche Konstruktion ist am besten?
Diese Frage kann ich allgemein nicht beantworten, denn das hängt immer von den Umständen und noch viel mehr vom Publikum ab.
- Wenn es sich um ein Perl-Einsteiger-Tutorium für C-Programmierer handelt, dürfte wohl die erste Variante am besten sein. Da kann der Programmierer, der sich viel Neues merken muß, auf Altbekanntem aufbauen.
- Wenn es sich um eine Liste mit nur wenigen, kurzen Einträgen handelt (und überdies die Zielgruppe, die das Script eines Tages mal lesen oder bearbeiten soll, keine Anfänger sind), tendiere ich zur Lösung 4.
- Wenn ich jemandem die map-Funktion zeigen will, verwende ich die Lösung 5.
Was die beste Lösung für ein Problem ist, hängt meiner Meinung nach von mehreren Faktoren ab:
- Handelt es sich um ein "Wegwerf-Script" oder wird es wiederholt verwendet?
- Welche Konstruktion beherrsche ich und habe ich schon ausführlich getestet?
- Wer soll dieses Script eines Tages verstehen?
- Wie gut sind komplexere Funktionen und Datenstrukturen dokumentiert?
- Wie klar ist die Strukturierung des Skriptes?
- Wie sprechend sind die Variablen- und Funktionsnamen?
Grundsätzlich halte ich folgende Richtlinien für sinnvoll:
- Das Programm sollte in sehr gut lesbarer Form vorliegen, mit genügend Kommentaren und einer Dokumentation (z.B. mit POD).
- Lösungsschritte, die logisch zusammengehören, sollen auch optisch zusammengehören. Wenn mehrere Codezeilen eine logische Einheit bilden, sollte man überlegen, sie in einer Funktion/Prozedur/Methode zusammenzufassen. Und bei mehreren Funktionen, die man zu einer Gruppe zusammenfassen kann, sollte man sich überlegen, ob man sie nicht in einem eigenen Modul auslagert. Dies erhöht auch die Wiederverwertbarkeit.
- Man soll zu Beginn lieber etwas mehr Zeit in die Wahl von Funktions-/Prozedur-/Package- und Variablennamen stecken. Eine gute Wahl kann die Lesbarkeit eines Programmes um einiges erhöhen.
- Die Sprachmittel, die man wählt, sollten so einfach wie möglich und nur so komplex wie nötig sein. Wobei eine bessere Lesbarkeit auch etwas komplexere Konstruktionen rechtfertigen kann (wenn sie gut dokumentiert sind). Dabei sollte man darauf achten, daß der Gedankengang möglichst in den Vordergrund gerückt wird, und man nicht mit vielen einfachen, dafür aber zahlreichen Schritten "den Wald vor lauter Bäumen nicht mehr sieht".
- Wenn eine Funktion einen return-Code liefert, sollte man ihn auswerten, wenn nur im entferntesten die Möglichkeit besteht, daß bei Abarbeitung der Funktion ein Fehler auftritt (also so gut wie immer).
- Entwickeln grundsätzlich mit dem Pragma 'use strict' und mit Anzeigen aller Warnungen (-w)
- Die Warnungen im fertigen Produkt abschalten; man muß einen Benutzer nicht mit (hoffentlich) unnötigen Warnungen konfrontieren.
- Optimierungen in Laufzeit und Arbeitsspeicherverbrauch erst dann machen, wenn sie wirklich nötig sind. Wenn man zu früh damit beginnt, optimiert man meistens an der falschen Stelle, und macht sich dabei auch meistens den Code unnötig komplex, unflexibel und unleserlich.
- Wenn eine bestimmte Funktionalität benötigt wird, ist es meistens von Vorteil, wenn man mal bei http://www.cpan.org/ reinschaut, ob es nicht schon ein Modul gibt, das die benötigte Funktionaltät zur Verfügung stellt. Wenn man das Rad jedesmal neu erfindet, lernt man zwar häufig einiges, es besteht jedoch auch die Gefahr, daß man zu viel Zeit braucht oder daß sich irgendwelche Fehler einschleichen. Fehler in CPAN-Modulen werden häufig viel früher entdeckt, weil sie meistens viel mehr Leute testen und verwenden.
- Man sollte logische Einheiten auch optisch zusammenfassen, sei es durch eine Subroutine oder auch nur durch Einfügen je einer Leerzeile vor und nach dem Block.
- Bevor man zu coden beginnt, ist man maximal flexibel; dies ändert sich
leider mit jedem Schritt, den man festlegt, mit jeder Zeile Quellcode. Um
eine möglichst viel von der ursprünglichen großen Flexibilität
beibehalten zu können, sollte man sich genau überlegen, was das
Programm leisten können soll (idealerweise auch zukünftige Anforderungen
vorhersehen), und danach die Problemstellung in immer kleinere Bruchstücke
mit möglichst allgemeinen Schnittstellen aufzuteilen, bis sich die einzelnen
Teile lösen lassen. Dann diese kleinen Teile z.B. auf einem Blatt Papier
oder im Kopf mit Testdaten durchspielen. So erkennt man eventuell Denkfehler
schon möglichst früh.
Meistens macht es Sinn, wenn man diese Vorgehensweise schriftlich formuliert und dem Auftragsgeber vorlegt (z.B. in Form eines Pflichtenheftes), um die Gefahr von Mißverständnissen möglichst gering zu halten (Ich erinnere mich an ein Projekt für ein Krankenhaus während meiner Studienzeit, wo wir etwa zwei Monate gebraucht haben herauszufinden, was die Ärzte überhaupt wollen, weil sie viele Begriffe anders verwendeten als wir....). - Klare Schnittstellen schaffen. Wenn z.B. an 100 verschiedenen Stellen ein Eintrag in eine Logdatei gemacht wird und nun die Logs in eine Datenbank geschrieben werden sollen, ist der Änderungsaufwand wahrscheinlich höher als wenn man nur eine Funktion hat, die Einträge in die Logdatei schreibt.
- Möglichst wenig mit globalen Variablen arbeiten. Überprüfen, ob man nicht anstelle von globalen Variablen Konstanten verwenden kann. Und die Schreibweise globaler Variablen möglichst so machen, daß sie nicht mit lokalen Variablen verwechselt werden.
- Klare Schnittstellen zwischen Funktionen schaffen: alles, was drinnen verwendet wird, muß als Parameter übergeben werden, und alles, was raus geht, muß explizit via return zurückgegeben werden.
Beispiele:
Was ist leicher lesbar?
unless (open (CONFIG, $myConfigFile)){
die "Error in opening file $myConfigFile: $!\n";}
else { foreach (<CONFIG>){
chomp; (! $_ || /^\#/) && next;
my @columns = split /\t/;
$column[4]++; push @lines, \@columns;
} close (CONFIG);}
oder dies:
unless (open (CONFIG, $myConfigFile)){
die ("Error in opening file $myConfigFile: $!\n");
} # unless
else {
foreach (<CONFIG>){
chomp; # remove \n
next if /^\s*$/; # skip empty lines
next if /^\s*\#/; # skip comments
my @columns = split( /\t/, $_);
$column[4]--; # column 4 starts at 1, but we need 0 as startingpoint
push( @lines, \@columns );
} # foreach
close (CONFIG);
} # else
Worunter kann man sich auf den ersten Blick mehr vorstellen?
my @f = &RF($a);
sub RF {
open (F, $_[0]) or die $!;
my @f = <F>;
close (F);
return @f;
}
oder:
my @configLines = &ReadConfigFile($myConfigFile);
sub ReadConfigFile {
my ($configFile) = shift;
open (CONFIG, $configFile) or die ("Error in reading config $configFile: $!\n");
my @configFiles = <CONFIG>;
close (CONFIG);
return (@configLines);
} # ReadConfigFile
Wo scheint der Programmablauf klarer?
open (CONFIGFILE, $configFile) or die $!; my @config = <CONFIGFILE>; close (CONFIGFILE); open (INFILE, $infile) or die $!; my @data = <INFILE>; close (INFILE); chomp(@data); chomp(@config);oder:
my @config = &ReadFile($configFile); # read configfile
my @data = &ReadFile($infile); # read datafile
sub ReadFile {
my ($file) = shift;
open (FILE, $file) or die $!;
my @lines = <FILE>;
close (FILE);
return (@lines);
} # ReadFile
Welche Konstruktionen sind leichter durchschaubar?
print "@list\n";oder:
print join(" ", @list), "\n";
$string = reverse $string
oder:
$string = join("", reverse split(//, $string));
Will be continued...
Letztes Update dieser Seite: Tuesday, 27-Jun-2006 20:37:00 CEST