GUI mit Perl/Tk

Wie erstelle ich ein CPAN-konformes Modul?

Inhaltsverzeichnis

Motivation

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.

Modulrahmen erstellen

~/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

Makefile.PL anpassen

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.

README

Diese README hat den gleichen Zweck wie in 99% aller Fälle in denen eine README existiert:

Changes

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.

Testskripte

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...

Das Modul

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:

Standard-Pragmas

Als erstes wollen wir die "Standard"-Pragmas verwenden, die in keinem Skript und in keinem Modul fehlen sollten:

    use strict;
    use warnings; 

Exporter

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.

Konstruktor

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;
  }

Methoden

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};
  }

Abschluss

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

POD

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.

Test

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.

Packen und los!

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

Automatische Versionsnummer in CVS/RCS

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 
};

Literatur

Quellen

  1. Originalbeitrag im PerlCommunity-Wiki, abgerufen aus dem Internetarchiv am 15.11.2016. Das Wiki ist leider aktuell und immer mal wieder nicht erreichbar. Das ist der Grund, warum es diese Seite gibt.
Top