Einfacherer Code mit Autovivification

| 3 Kommentare | Keine TrackBacks

Perl-Programmierer sind es gewohnt, sich die Arbeit durch viele schöne Konstrukte stark zu erleichtern. Manche davon wenden viele einfach an, ohne sich Gedanken darüber zu machen. Eines davon ist die Autovivification – zu Deutsch etwa „automatische Belebung“, da Referenzen automatisch zum Leben erweckt werden. Und die kann auch noch einiges mehr, als man glaubt.

Häufig muss man mit mehr oder minder vollständigen Eingabedaten in komplexen Datenstrukturen arbeiten, will aber dennoch sicherstellen, dass alles korrekt funktioniert bzw. keine Warnungen über undefinierte Variablen erhalten. Größere if-Blöcke sind da dann die Folge. Bei Perl sind diese aber oft unnötig. Denn Perl bietet – im Gegensatz zu beispielsweise Python, PHP, Ruby, Java oder C – mit der Autovivification die Möglichkeit, beim Zugriff auf komplexe Datenstrukturen viel Code zu sparen, den Code damit kompakter, übersichtlicher und robuster zu gestalten. Wer Bullshit-Bingo spielen will, darf es auch „Win-Win-Situation“ nennen.

 

Einfaches Beispiel (noch ohne Autovivification):

 my $application_name;                    
 my $server_name;
 
 $application_name = $params->{appname} if exists $params->{appname};
 $server_name      = $params->{server}  if exists $params->{server}; 

Hier werden die beiden Variablen nur gesetzt, wenn im $params-Hashref auch der passende Wert steht. Ansonsten bleibt die Variable auf undef.

Diese Abfrage kann man sich aber komplett sparen! Denn wenn der entsprechende Hash-Key nicht existiert, ist dessen Wert sowieso undef, der Code kann also auch so geschrieben werden:

 my $application_name = $params->{appname};                    
 my $server_name      = $params->{server};

Das ist nur noch die Hälfte der Zeilen und ca. ein Drittel der Zeichen. Der Code ist übersichtlicher, klarer und weniger fehleranfällig, da weniger Möglichkeiten für Flüchtigkeitsfehler vorhanden sind.

Wenn dann der Grund für die Variablenzuweisung nur ist, diese dann später in einen Hash packen zu können, dann geht es mit einer direkten Zuweisung dort noch übersichtlicher:

 my $data = 
    {
    application_name => $params->{appname},
    server_name      => $params->{server},
    flag             => 1,
    # weitere Werte   
    };

Und wenn in der $params-Hashref nur die Werte stehen, die auch später übernommen werden sollen, lässt sich auch das noch einfacher schreiben:

my $data =
   {
   flag      => 1,
   %$params,
   };

 

Dabei lassen sich auch Default-Werte (wie hier das flag) setzen, die evtl. mit den Parametern aus der Hashref $params überschrieben werden.

 

Komplexeres Beispiel (mit Autovivification):

Bei der Durchsicht des Codes bei einem Kunden ist mir folgender (hier leicht vereinfachter und angepasster) Code aufgefallen: 

 if ( exists $params->{conf} )
    {
    if ( exists $params->{conf}{server} )
       {
       if ( defined $params->{conf}{server} )
          {
          $data->{level}       = "server";
          $data->{server_name} = defined $params->{conf}{server};    
          }
       }
    }

Zum einen ist hier durch die Unübersichtlichkeit der Verschachtelung (im Original ist es noch etwas umfangreicher) höchstwahrscheinlich ein Fehler reingerutscht: $data->{server_name} soll sicherlich mit dem Server-Name und nicht mit 1 (wenn einer existiert) oder einem Leerstring (wenn keiner existiert) belegt werden. Aber genau dies macht das letzte defined in der Zuweisung.

Zum anderen ist die Abfrage auf existierende Hash-Keys gar nicht nötig: Perl fügt diese selbst ein, wenn auf ein Unterelement zugegriffen wird. Dieses Verhalten nennt sich Autovivification. Dadurch reicht folgender Code:

 if ( defined $params->{conf}{server} )
    {
    $data->{level}       = "server";
    $data->{server_name} = $params->{conf}{server};
    }

Dadurch wird der Code übersichtlicher und damit wartbarer und robuster, es schleichen sich weniger Fehler.

Häufig ist auch eine Abfrage auf einen wahren Wert gewünscht oder ausreichend (wenn der Server aus einem Leerstring oder einer 0 besteht wird er als nicht vorhanden interpretiert), dann kann bzw. sollte man das defined weglassen.

Das gleiche funktioniert natürlich auch bei tiefer verschachtelten Konstrukten oder mit der optionalen Zuweisung von Default-Werten oder dem Abbruch bei nicht vorhandenen (wahren) Werten:

 $name = $params->{conf}{server}{name} || "";
$name = $params->{conf}{server}{name} or die "kein Servername!";

Mit dem defined or-Operator von Perl 5.10 wird dann nicht nur auf einen wahren Wert getestet, sondern darauf ob denn überhaupt ein definierter vorhanden ist:

 $name = $params->{conf}{server}{name} // "default";

Allerdings sollte man beachten, dass die Autovivification hier beim Lesen neue Hash-Keys einfügt und anonyme Hashes anlegt. Meistens ist das egal, wenn man aber später alleine aufgrund der Existenz eines Hashkeys etwas ableitet, kann das zu Problemen führen. In der Praxis kommen Probleme damit  aber nur selten vor.

Nehmen wir an, $params wäre vor obigem Code undef oder eine leere Hash-Referenz. Nach dem Beispiel würde ein Dump von $params so aussehen:

 $params = 
    {
    conf => 
       {
       server => {}
       }
    };

 

MIt Array-Zugriffen funktioniert dies analog, so dass entsprechend passend eben anonyme Arrays angelegt werden (siehe Beispiel unten).

 

Schreibzugriffe

Auch beim Schreiben einer komplexen Datenstruktur kann man sich die Autovivification zu Nutze machen: man muss nicht alle Einzelelemente einer Datenstruktur anlegen sondern kann diese gleich befüllen:

 my $data;
 $data->{conf}{server}[0]{name} = "testserver"; 

 

Hierbei gibt es auch keine unerwünschten Nebenwirkungen, da es ja gewünscht ist die entsprechenden Hashreferenzen anzulegen.

Ein weiteres Beispiel, das ich auch bis vor nicht allzu langer Zeit häufig viel zu kompliziert geschrieben habe, sind Zähler oder andere Additionen auf Datenstrukturen, hier ein viel zu komplizierter Wortzähler:

 my @obst = qw(apfel birne kirsche apfel);
 my %obst_zaehler;

 foreach my $obst (@obst)
    {
    # Schon initialisiert? 
    if ( $obst_zaehler{$obst} )
       {
       # Ja: Addieren!
       $obst_zaehler{$obst}++;
       }
     else
       {
       # Nein: mit 1 initialisieren
       $obst_zaehler{$obst} = 1;
       }
    }

Viel einfacher und damit robuster und wartbarer geht es folgendermaßen:

 my @obst = qw(apfel birne kirsche apfel);
 my %obst_zaehler;
 
 foreach my $obst (@obst)
    {
    $obst_zaehler{$obst}++;
    }

 

Und wer es auf Kosten der Übersichtlichkeit noch knapper haben mag, kann es auch so schreiben:

 my %obst_zaehler;
 $obst_zaehler{$_}++ foreach qw(apfel birne kirsche apfel);

 

Die if-Abfrage, ob der entsprechende Hash-Key überhaupt existiert, kann man sich hier auch bei (den natürlich immer) aktivierten Warnungen sparen, da Perl bei Autovivification diese Warnung selbst unterdrückt.

 

Autovivification verhindern

Aber was macht man, wenn man Autovivification aus spezielle Gründen nicht gewünscht ist? Mit lock_hash aus Hash::Util lässt sich verhindern, dass ein Hash neue oder unerwünschte Keys erhält, und damit wird dann natürlich auch Autovivification verhindert. Hash::Util ist ein bei Perl mitgeliefertes Standardmodul seit Perl 5.8.0. (Dank für den Hinweis an Moritz via irc.perl.de#perlde).

Update: Und, wie Moritz in den Kommentaren schreibt, mit Data::Diver geht das auch. 

Keine TrackBacks

TrackBack-URL: http://www.perl-blog.de/mt/mt-tb.cgi/128

3 Kommentare

Man kann auch einfach das tolle CPAN-Modul Data::Diver benutzen, das macht auch garantiert nie Autovivification.

Das war mal hilfreich!!

In Python würde ich da einfach die Methode get() des Dicts nehmen oder gleich das mitgelieferte defaultdict, beides Varianten, die einem bequem Defaults für nicht gesetzte Keys spezifizieren lassen.

Jetzt kommentieren

Aktuelle Kommentare

  • Niels Dettenbach: ...schade eigentlich, das es PyPerl nicht mehr wirklich gibt. Zwar weiter lesen
  • Alvar Freude: Kannte ich noch nicht, danke für den Hinweis; allerdings ist weiter lesen
  • Ben Sieverts: Ich vermisse noch folgendes Buch auf der List: Effective Perl weiter lesen
  • Alex: Ich schlage einfach mal ganz unverschämt bei diesem Beitrag die weiter lesen
  • Marcel: Oke, danke für den Tipp. Schade natürlich. Wird euer Buch weiter lesen
  • Alvar: Nein, leider ist das noch nicht fertig. :-( Es gibt weiter lesen
  • Marcel : Hallo! gibt es dein Buch zu Perl6 schon? Wo kann weiter lesen
  • air max 2009: Nimm ein Paradigma deiner Wahl (z.b. MVC) und lerne Applikationscode weiter lesen
  • vTasker: Was ist das denn für ein MIST? Der Artikel ist weiter lesen
  • Virenschutz-Test: Das ist ja lustig hihi. Der Admin ist wohl nicht weiter lesen

Über diese Seite

Diese Seite enthält einen einen einzelnen Eintrag von Alvar Freude vom 30.06.08 15:55.

Probleme mit der CPAN-Shell und die Lösung ist der vorherige Eintrag in diesem Blog.

Tickets für YAPC::Europe, die Europäische Perl-Konferenz ist der nächste Eintrag in diesem Blog.

Aktuelle Einträge finden Sie auf der Startseite, alle Einträge in den Archiven.