Schwächen von Perl 5
Perl 5 ist eine tolle Programmiersprache, und ich benutze sie für die meisten Programme die ich schreibe. Doch bei aller Begeisterung komme ich nicht umhin ein paar Schwächen zu bemerken.
Dies soll keine FUD-Sammlung á la "Perl ist langsam, Perl Code ist unlesbar" sein, sondern soll einige Schwächen wohlbegründet darlegen.
Ansonsten bleibt mir noch auf Jonathan Rockways Blog zu verweisen, der schreibt warum er bei Perl bleibt, obwohl er dessen Nachteile kennt (Englisch), dem kann ich nur zustimmen.
Zu viele globale Variablen
In Perl Version 1 gab es nur globale Variablen, später kamen dann Variablen mit dynamischem Scope (local) und lexikalische Variablen (my) dazu.
Viele der eingebauten, speziellen Variablen wie $_
,
$!
(Fehlermeldungen), $@
(eval
-Fehler) und $/
(Input Record Separator) sind
jedoch globale Variablen geblieben.
Das kann zu subtilen, sehr schwer zu findenden Bugs führen. Zum Beispiel
führen Objekte, wenn sie nirgendwo mehr referenziert sind, ihre
DESTROY
-Methode aus, und dabei kann aus Versehen eine
Fehlervariable zurückgesetzt werden:
use strict; use warnings; sub DESTROY { eval { 1 }; } eval { my $x = bless {}; die "Death\n"; }; print $@ if $@; # keine Ausgabe hier
Obwohl in dem eval
-Block einmal die
ausgeführt
wurde, gibt die folgende Zeile nichts aus. Das liegt daran, dass das
eval
in der DESTROY
-Methode keinen Fehler
produziert hat und damit $@
zurückgesetzt wurde.
Im allgemeinen können viele Objekte aus dem Scope gehen, und wenn es ein Objekt aus einem fremden Modul ist, fällt es sehr schwer herauszufinden was passiert ist.
Die korrekte Lösung wäre, local $@
im
DESTROY
-Block zu verwenden. Das Problem ist, dass die wenigsten
Entwickler an so etwas denken, und eigentlich sollten sie auch gar nicht daran
denken müssen. Ein besseres Scoping der eingebauten Variablen könnte dieses
Problem vermeiden.
Ein weiteres Problem der globalen Variablen ist, dass man als Modul-Autor
genau wissen muss, welche Variablen man implizit verwendet. So benutzt zum
Beispiel print
implizit $\
, and bei mehr als einem
Argument auch noch $,
. Wenn man ein Array in einen String
interpoliert benutzt man implizit $"
.
Als Modul-Autor muss man alle diese Variablen local
isieren
um immer das Ergebnis zu bekommen das man will, auch wenn der Benutzer diese
Variablen vorher auf andere Werte gesetzt hat.
Das impliziert auch, dass man beim Schreiben der Module sehr aufmerksam sein muss und sich beliebige Listen von Variablen merken muss.
Objektorientierung
Perls Objektsystem ist recht allgemein und flexibel (auch wenn es nicht immer so wahrgenommen wird), aber es hat auch einige Probleme.
So kennt perl keinen Unterschied zwischen Subroutinen und Methoden, was es quasi unmöglich macht eine Liste aller Methoden zu ermitteln, die man auf einem Objekt aufrufen kann. (Ausser man benutzt ein extra Objektframework wie Moose).
Auch wünscht man sich heute eine kürzere Syntax für Methodenaufrufe,
->
ist suboptimal im Vergleich zu einem einzelnen Zeichen.
Regexes
Reguläre Ausdrücke, kurz Regexes, gehörten traditionell zu Perls Stärken.
Allerdings gibt es auch hier einige Schwächen. So sollten verschiedene Dinge verschieden aussehen, was jedoch nicht der Fall ist:
(?: ) # non-capturing group (?= ) # positive look-ahead (?! ) # negative look-ahead (?<= ) # positive look-behind (?<! ) # negative look-behind (?> ) # non-backtracking pattern
Diese Gruppierungen machen zum Teil fundamental verschiedene Dinge, sehen aber sehr ähnlich aus, und sind relativ schwer zu lesen.
Auch wird die (?: ... )
-Gruppe relativ häufig gebraucht, und
dafür recht lange und schwer zu tippen.
Der Zugriff auf die Matchvariablen $1, $2, $3, ...
ist nicht
ohne Umwege über mit Array-Semantik möglich (auch wenn man sich mit
@+
, @-
und substr
so etwas basteln
kann), und es ist nicht möglich, die Nummern der capturing groups dynamisch
in der Regex festzulegen.
Die ungewöhnliche Handhabung von Unicode-Strings erschwert die Arbeit an der Regex-Engine und führt regelmäßig zu Bugs.
Unicode-Handling
Wenn man das Unicode-Modell von perl verstanden hat ist es dafür, dass es erst im Nachhinein in die Sprache integriert wurde sehr gut. Es versucht, die Art der internen Speicherung zu verbergen (perl speichert strings intern als Latin-1 oder UTF-8 ab, je nach Kontext).
Allerdings wendet perl auf intern als Latin-1 gespeicherte Strings
ASCII-Semantik und nicht Unicode-Semantik an. Das bedeutet, dass sogar
Strings, die durch Encode::decode
gejagt wurden, nicht korrekt als Textstrings behandelt werden.
Um das zu umgehen, muss man den String mit utf8::upgrade
zum
UTF-8-String befördern - was entgegen der Philosophie läuft, das interne
Speicherformat als Implementierungsdetail vor dem Programmierer zu
verbergen.
Es würde auch sehr helfen, getrennte Datentypen für Text- und Bytestrings zu haben, sodass man z.B. beim Konkatenieren von verschiedenartigen Strings eine Warnung ausgeben könnte.
Spooky Action at a Distance
... nennt man Programmteile, die sich gegenseitig beeinflussen, obwohl man es nicht erwartet.
Ein Beispiel sind die impliziten Iteratoren über Hashes, die
each
benutzt. So führt der folgende Code zu einer
Endlosschleife:
#!/usr/bin/perl use strict; use warnings; my %h = (a => 1, b => 2, c => 3); while (my ($k, $v) = each %h){ find($v); } sub find { while (my ($k, $v) = each %h){ if ($v eq $_[0]){ print "Found $v wth key $k\n"; return; } } }
Dadurch, dass der Iterator an den Hash gebunden ist, benutzen beide
Programmteile den gleichen Iterator. D.h. nach dem lesen des ersten Paares
wird find()
aufgerufen, was den Rest des Hashes aufbraucht
(anstatt von Vorne anzufangen), kehrt zurück, und die äußere while-Schleife
fängt wieder von vorne an, weil der Iterator ja einmal ganz durchgelaufen
war.