Software-Engineering Martin Fabiani

Sitemap (Navigation ohne JavaScript)

Impressum: Martin Fabiani, Röderbergweg 104, D-60485 Frankfurt, Tel: +49 (69) 49084808, E-Mail: info (at) fabiani.net, USt-IdNr: DE217298609

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:

  1. 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.
  2. Schleifenkonstrukt im Shellscript-Stil:
    • Syntax ist kompakt und auf das Wesentliche konzentriert.
    • Die Liste @list ist nach Abarbeitung der Schleife leer.
  3. Schleifenkonstrukt in "Perl-Stil":
    • Man erhält den Index des Elementes.
    • Laufvariable $i blockweit deklariert.
  4. Schleifenkonstrukt in "Perl-Stil 2":
    • Konzentriert sich auf das Wesentliche.
    • Man muß wissen, dass $_ die Standard-Laufvariable ist.
  5. 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.


Was die beste Lösung für ein Problem ist, hängt meiner Meinung nach von mehreren Faktoren ab:

Grundsätzlich halte ich folgende Richtlinien für sinnvoll:

  1. Das Programm sollte in sehr gut lesbarer Form vorliegen, mit genügend Kommentaren und einer Dokumentation (z.B. mit POD).
  2. 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.
  3. 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.
  4. 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".
  5. 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).
  6. Entwickeln grundsätzlich mit dem Pragma 'use strict' und mit Anzeigen aller Warnungen (-w)
  7. Die Warnungen im fertigen Produkt abschalten; man muß einen Benutzer nicht mit (hoffentlich) unnötigen Warnungen konfrontieren.
  8. 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.
  9. 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.
  10. 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.
  11. 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....).
  12. 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.
  13. 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.
  14. 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