Diese Coding-Guidelines sind für die Programmierung von Perl-Modulen nach dem CPAN-Standard gedacht.
Diese Module sollen objektorientiert und getestet sein.
Den ersten Schritt überlassen wir einem "externen" Programm - h2xs. Das dient eigentlich der Einbindung von (C-)Header-Dateien in XS-Module, kann aber genauso gut für die Erstellung eines Rumpfes für Module verwendet werden.
Als Beispiel wird der Namensraum PerlCommunity
verwendet.
~/EigeneModule> h2xs -b 5.6.1 -XA -n PerlCommunity::Testmodul
Writing PerlCommunity-Testmodul/lib/PerlCommunity/Testmodul.pm
Writing PerlCommunity-Testmodul/Makefile.PL
Writing PerlCommunity-Testmodul/README
Writing PerlCommunity-Testmodul/t/PerlCommunity-Testmodul.t
Writing PerlCommunity-Testmodul/Changes
Writing PerlCommunity-Testmodul/MANIFEST
Diese Datei wird angepasst: PerlCommunity-Testmodul/Makefile.PL
Im Makefile.PL stehen verschiedene Angaben wie Abhängigkeiten und Infos zu Autor und minimaler Perl-Version:
use 5.006001;
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
NAME => 'PerlCommunity::Testmodul',
VERSION_FROM => 'lib/PerlCommunity/Testmodul.pm', # finds $VERSION
PREREQ_PM => {}, # e.g., Module::Name => 1.1
($] >= 5.005 ? ## Add these new keywords supported since 5.005
(ABSTRACT_FROM => 'lib/PerlCommunity/Testmodul.pm', # retrieve abstract from module
AUTHOR => 'Renee Baecker ') : ()),
);
Abhängigkeiten (Modulname und minimale Modulversion) müssen bei PREREQ_PM eingetragen werden, z.B.
PREREQ_PM => {Spreadsheet::SimpleExcel => 1.1},
Hier wird gesagt, dass unser neues Modul das Modul Spreadsheet::SimpleExcel braucht und dass es eventuell noch installiert werden muss bevor PerlCommunity::Testmodul installiert werden kann (passiert dann automatisch).
Gegebenenfalls müssen noch die Autorenangaben angepasst werden, ist bei einem rein internen Gebrauch aber uninteressant.
Da das Makefile.PL ein Perl-Programm ist, können auch noch andere Sachen dort gemacht werden, die als Vorbereitung zum Testen und Installieren dienen.
Diese README hat den gleichen Zweck wie in 99% aller Fälle in denen eine README existiert:
In der Changes-Datei werden alle Veränderungen am Modul festgehalten. Diese Datei sollte gut gepflegt werden, damit Veränderungen nachvollzogen werden können. Das kann die Fehlerfindung beschleunigen ("in Version 1.0 hat's doch noch funktioniert").
Am Anfang sieht es noch so aus:
Revision history for Perl extension PerlCommunity::Testmodul.
0.01 Mon Mar 27 09:30:57 2006
- original version; created by h2xs 1.23 with options
-b 5.6.1 -XA -n PerlCommunity::Testmodul
<23 id="manifest">MANIFEST
Im MANIFEST sind alle Dateien festgehalten, die zu dem Modul-Paket gehören. Am Anfang sieht es so aus:
Changes
Makefile.PL
MANIFEST
README
t/PerlCommunity-Testmodul.t
lib/PerlCommunity/Testmodul.pm
Häufig kommt es vor, dass man intern noch weitere Module braucht, die von PerlCommunity::Testmodul benutzt werden. Z.B. ein PerlCommunity::Testmodul::Untermodul. Das speichert man dann unter lib/PerlCommunity/Testmodul/Untermodul.pm. Dieses Modul muss dann auch in die MANIFEST-Datei eingetragen werden:
Changes
Makefile.PL
MANIFEST
README
t/PerlCommunity-Testmodul.t
lib/PerlCommunity/Testmodul.pm
lib/PerlCommunity/Testmodul/Untermodul.pm
Auch neue Test-Skripte müssen hier eingetragen werden.
Tests sind übermäßig wichtig. Am Anfang sieht es aus wie Mehraufwand, aber wenn man nicht richtig testet, geht am Ende viel Zeit durch Fehlersuche und Bugfixing verloren. Eventuell fällt der Fehler auch erst nach einer gewissen Zeit im laufenden Betrieb auf und dann kostest es umso mehr Zeit und Nerven...
Endlich können wir uns dem Modul an sich widmen. h2xs schreibt schon einen Rumpf für das Modul - mit einer Standard-POD. Dieses Modul muss man noch anpassen:
Als erstes wollen wir die "Standard"-Pragmas verwenden, die in keinem Skript und in keinem Modul fehlen sollten:
use strict;
use warnings;
Da wir das Modul objektorientiert verwenden wollen, brauchen wir keinen Exporter. Damit fallen folgende Zeilen aus dem Modul-Rumpf weg (einfach rauslöschen):
require Exporter;
our @ISA = qw(Exporter);
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
# This allows declaration use PerlCommunity::Testmodul ':all';
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory.
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
);
Wer das Modul nicht Objektorientiert verwenden will und den Exporter benutzen möchte, sollte sich mal perldoc Exporter anschauen.
Im Gegensatz zu Java und anderen Sprachen ist kein Name für den Konstruktor vorgeschrieben, aber es ist üblich auch in Perl den "Konstruktor" "new" zu nennen. Damit sieht dann später die Instanzierung der Klasse so aus:
my $object = PerlCommunity::Testmodul->new();
Der Methode wird als erster Parameter der Name der Klasse (hier: PerlCommunity::Testmodul) übergeben. Natürlich kann man auch weitere Parameter übergeben.
Ein Objekt ist in Perl in der Regel eine "geblesste" (perldoc -f bless) Hashreferenz. Damit sieht ein Standardkonstruktor so aus:
sub new{
my ($class) = @_;
my $self = {};
return bless $self,$class;
}
Dann kann man seine eigenen Methoden schreiben. Da wir das Modul Objektorientiert verwenden wollen, muss man berücksichtigen, dass beim Aufruf von
$object->methode('Welt');
das Objekt als erster Parameter übergeben wird.
Die Methode sieht dann so aus:
sub methode{
my ($self,$value) = @_; # $self ist das Objekt, $value ist hier "Welt"
print $value;
}
Die "berühmten" getter/setter-Methoden können einzeln oder auch in einer Methode zusammen geschrieben werden. Nehmen wir an, wir wollen ein Attribut "Farbe" des Objekts erst setzen und danach auslesen:
Methoden getrennt:
Aufruf:
$object->set_farbe('blau');
print $object->get_farbe();
Implementierung im Modul:
sub set_farbe{
my ($self,$farbe) = @_;
$self->{FARBE} = $farbe if(defined $farbe);
}
sub get_farbe{
my ($self) = @_;
return $self->{FARBE};
}
Methoden in einer Methode:
Aufruf:
$object->farbe('blau');
print $object->farbe();
Implementierung im Modul:
sub farbe{
my ($self,$farbe) = @_;
$self->{FARBE} = $farbe if(defined $farbe);
return $self->{FARBE};
}
Ein Modul muss einen "wahren" Rückgabewert liefern. Üblich ist - und das wird von h2xs auch so gemacht -, eine 1 zurückzuliefern. Sonst kommt eine Meldung wie
PerlCommunity/Testmodul.pm did not return a true value
Die Dokumentation im POD-Format ist innerhalb des Moduls gut aufgehoben. Man sollte darauf achten, dass die Dokumentation möglichst vollständig ist. Es sollte die Verwendung (Synopsis) und die Methoden nennen und möglichst die Rückgabewerte und Parameter sinnvoll benennen und beschreiben. Die einzelnen POD-Anweisungen sind unter perldoc perlpod zu finden.
Danach wird noch getestet, ob alles vorhanden ist und ob das Modul funktioniert. Man sollte dabei darauf achten, dass die Testskripte aktuell sind und alle Features des Moduls abdecken...
Zum Testen startet man als erstes das Makefile.PL-Skript:
~/EigeneModule/PerlCommunity-Testmodul 104> perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for PerlCommunity::Testmodul
Hierbei wird das Makefile
erstellt, das dann noch für make
benutzt wird:
~/EigeneModule/PerlCommunity-Testmodul 105> make
cp lib/PerlCommunity/Testmodul.pm blib/lib/PerlCommunity/Testmodul.pm
Manifying blib/man3/PerlCommunity::Testmodul.3
Zum Abschluss dieses Schrittes werden noch die Testskripte aufgerufen. Damit wird überprüft, ob das Skript überhaupt Fehlerfrei arbeitet:
~/EigeneModule/PerlCommunity-Testmodul 111> make test
/usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PerlCommunity-Testmodul....ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.06 cusr + 0.02 csys = 0.08 CPU)
So arbeitet das Modul fehlerfrei, aber häufig sieht das Ergebnis nicht ganz so gut aus (vor allem, wenn man wirklich viele Testfälle schreibt). So sieht es aus, wenn Fehler auftauchen:
~/EigeneModule/PerlCommunity-Testmodul 114> make test
/usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
t/PerlCommunity-Testmodul....
# Failed test in t/PerlCommunity-Testmodul.t at line 18.
# Looks like you failed 1 test of 2.
t/PerlCommunity-Testmodul....dubious
Test returned status 1 (wstat 256, 0x100)
DIED. FAILED test 2
Failed 1/2 tests, 50.00% okay
Failed Test Stat Wstat Total Fail Failed List of Failed
-------------------------------------------------------------------------------
t/PerlCommunity-Testmodul.t 1 256 2 1 50.00% 2
Failed 1/1 test scripts, 0.00% okay. 1/2 subtests failed, 50.00% okay.
make: *** [test_dynamic] Error 2
Hier wird auch gleich gesagt, welche Tests fehlschlagen, so dass man die Fehlersuche eingrenzen kann.
Nach dem Test kann man auch automatisch eine Distribution packen lassen, die fertig für den Upload in CPAN oder für das unternehmenseigene Repository ist:
~/EigeneModule/PerlCommunity-Testmodul 118> make tardist
rm -rf PerlCommunity-Testmodul-0.01
/tools/devel_tools/bin/perl "-MExtUtils::Manifest=manicopy,maniread" \
-e "manicopy(maniread(),'PerlCommunity-Testmodul-0.01', 'best');"
mkdir PerlCommunity-Testmodul-0.01
mkdir PerlCommunity-Testmodul-0.01/t
mkdir PerlCommunity-Testmodul-0.01/lib
mkdir PerlCommunity-Testmodul-0.01/lib/PerlCommunity
tar cvf PerlCommunity-Testmodul-0.01.tar PerlCommunity-Testmodul-0.01
a PerlCommunity-Testmodul-0.01/ 0K
a PerlCommunity-Testmodul-0.01/t/ 0K
a PerlCommunity-Testmodul-0.01/t/PerlCommunity-Testmodul.t 1K
a PerlCommunity-Testmodul-0.01/MANIFEST 1K
a PerlCommunity-Testmodul-0.01/META.yml 1K
a PerlCommunity-Testmodul-0.01/lib/ 0K
a PerlCommunity-Testmodul-0.01/lib/PerlCommunity/ 0K
a PerlCommunity-Testmodul-0.01/lib/PerlCommunity/Testmodul.pm 2K
a PerlCommunity-Testmodul-0.01/Changes 1K
a PerlCommunity-Testmodul-0.01/README 2K
a PerlCommunity-Testmodul-0.01/Makefile.PL 1K
rm -rf PerlCommunity-Testmodul-0.01
gzip --best PerlCommunity-Testmodul-0.01.tar
Für Benutzer von Versionsverwaltungsprogrammen bietet sich nachfolgendes Vorgehen an, um die Versionsnummer im Modul automatisch zu aktualisieren.
Im Kopfteil des Perlmoduls wird eine Versionsnummer erstellt. Wenn man mit CVS oder RCS arbeitet, dann sollte man diese ändern, damit CVS bzw. RCS die Versionsnummer automatisch aktualisiert: Die Zeile
our $VERSION = '0.01';
sollte man also ersetzen durch:
our $VERSION = do { my @r = (q$Revision: 1.9 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
Will man Konflikte mit einer gleichnamigen Variable $VERSION im Programm vermeiden, könnte man ggf. diese Zeile ergänzen:
$PerlCommunity-Testmodul::VERSION = do {
my @r = (q$Revision: 1.9 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r
};