Probleme bei der "Modularisierung" eines Scripts
Christoph Schnauß
- perl
hallo Forum,
ich habe ein etwas größeres Script, das insgesamt auch ganz prima funktioniert. Aus Gründen der Übersichtlichkeit wollte ich es nun gerne "zerhacken" und ein paar Teile in Module (*.pm) auslagern. Angefangen habe ich damit, die Liste der (globalen) Variablen, die ich drin habe, in so ein Modul zu packen, das rufe ich dann im Hauptscript mit "use" wieder auf. Das geht anstandslos.
Jetzt wollte ich das mit ein paar Subroutinen auch machen, und da klappt es nicht. Ich habe beispielsweise eine Subroutine, die so aussieht:
sub input {
if ($cgi->param('antwort')) {
$origdatum = $cgi->param('origdatum');
$origname = $cgi->param('origname');
$origemail = $cgi->param('origemail');
$origurl = $cgi->param('origurl');
$origthema = $cgi->param('origthema');
} else {
...
Es geht da noch 150 Zeilen weiter. Solange die Subroutine im Hauptscript steht, tut das brav alles, was ich verlange. Lade ich sie aber mit "use" oder "require" als Modul dazu, bekomme ich plötzlich einen 500er Errror und im log die Meldung "Can't call method "param" on an undefined value at Input.pm line 10" (das ist die oben angegebene "if"-Bedingung). Außerdem mag das Script danach eine vorhandene Datei nicht mehr öffnen und auslesen, das log sagt dazu dann "Use of uninitialized value in concatenation (.) or string ..." und "readline() on closed filehandle".
Da verstehe ich irgendwas nicht. Prinzipiell sollte es doch egal sein, ob eine Subroutine nun im "Hauptscript" steht oder aus einem Modul eingelesen wird, Hauptsache ist, daß es sie bei Bedarf gibt. Da ich exakt dieselben Fehlermeldungen bekomme, wenn ich "use" und wenn ich "require" nehme, kann ich mir das auch nicht mit unterschiedlichen Namensräumen erklären. Hat jemand eine Idee oder einen Hinweis, wie ich das mit einem Modul, das eine Subroutine enthält, nun exakt zu machen habe oder wo eventuell ein Denkfehler stecken könnte? Mit meinem Modul "Variablen.pm" gibts keine Probleme, und mit Anweisungen außerhalb von Subroutinen auch nicht
Grüße aus Berlin
Christoph S.
Hallo Christoph,
sub input {
if ($cgi->param('antwort')) {
$origdatum = $cgi->param('origdatum');
$origname = $cgi->param('origname');
$origemail = $cgi->param('origemail');
$origurl = $cgi->param('origurl');
$origthema = $cgi->param('origthema');
} else {
Wenn Du die Funktion "input" in der Datei aufrufst, in der Du die Variable $cgi Deklariert hast, liegt diese im selben Namensraum (oder darunter) und funktioniert somt auch.
Liegt diese Funktion jedoch in einer anderen Datei, liegt diese auch außerhalb des Geltungsbereiches der Variable $cgi. Somit findet Perl die Methode param für das $cgi-Objekt auch nicht.
Generell sollte man bei einem modularen Programmaufbau darauf achten, dass die einezelnen Programmteile auch wirklich Modular sind, und nicht voneinander abhängen.
Das machst Du z.B. damit, dass Du der Funktion alle benötigten Variablen und Methoden übergibst. In deinem Beispiel:
sub input {
my $cgi = shift;
if ($cgi->param('antwort')) {
$origdatum = $cgi->param('origdatum');
$origname = $cgi->param('origname');
$origemail = $cgi->param('origemail');
$origurl = $cgi->param('origurl');
$origthema = $cgi->param('origthema');
}
}
Aufruf der Funktion:
input( $cgi );
Du übergibst das CGI-Objekt also per Variable an deine Funktion, in der Du dann auf alle weiteren Methoden des CGI-Objektes zugriff hast.
Wenn Du dieses Konziquent durchziehst, hast Du eine schöne Sammlung an Funktionen, die Du je nach Bedarf in deine Programme einbinden kannst, ohne das es Konflikte gibt.
Damit Du später wieder weist, was Du deiner Funktion alles zu übergeben hast, macht es Sinn alles schön sauber zu Dokumentieren.
Die nützlichsten Module werden dann unter www.cpan.org ausgestellt ;)
Gruß
Helmut Weber
hallo,
Wenn Du die Funktion "input" in der Datei aufrufst, in der Du die Variable $cgi Deklariert hast, liegt diese im selben Namensraum (oder darunter) und funktioniert somt auch.
Danke für deine Antwort - bringt mich zwar nicht wörtlich weiter, aber hat mich auf die richtige Idee gebracht: ich muß auch im Modul selbst nochmal
use strict;
use CGI;
my $cgi = new CGI;
angeben, es reicht nicht, wenn das im "Hauptscript" steht. Und das wars dann, shebang brauche ich im Modul selbst nicht nochmal.
Damit Du später wieder weist, was Du deiner Funktion alles zu übergeben hast, macht es Sinn alles schön sauber zu Dokumentieren.
Keine Sorge, das mache ich gewohnheitsmäßig. Bisher allerdings immer mit Kommentarzeilen, bei Modulen kann ich das aber so umstellen, daß ich "perldoc" benutzen kann, und das ist einer der Gründe, weshalb ich das machen möchte.
Grüße aus Berlin
Christoph S.
Danke für deine Antwort - bringt mich zwar nicht wörtlich weiter, aber hat mich auf die richtige Idee gebracht: ich muß auch im Modul selbst nochmal
use strict;
use CGI;
my $cgi = new CGI;
angeben, es reicht nicht, wenn das im "Hauptscript" steht. Und das wars dann, shebang brauche ich im Modul selbst nicht nochmal.
Ob das sauberer ist als in jedem Modul direkt das CGI Modul zu adressieren?
Du erzeugst ja jetzt in jedem von dir eingebundenen Modul ein neues CGI Objekt, was in meinen Augen unsinnig ist, das es nur ja nur ein CGI Objekt gibt.
Sauberer wäre es in den Modulen die Variabel mit $main::cgi anzusprechen, da du ja auf dieses eine CGI Objekt des Hauptmodul zugreifen möchtest.
Struppi.
hallo Struppi,
Ob das sauberer ist als in jedem Modul direkt das CGI Modul zu adressieren?
Jaein ... weiß ich noch nicht. Schließlich hat mein gesamtes Script auch noch mehr zu tun als bloß HTML-Ausgaben zu erzeugen. Das heißt, ich kann dann "use CGI" im Hauptscript weglassen und nur in dem Modul angeben, das tatsächlich darauf Rücksicht nehmen muß, genauso, wie ich "use Fcntl ':flock';" dann nur in dem Modul anzugeben brauche, das tatsächlich darauf angewiesen ist.
Sauberer wäre es in den Modulen die Variabel mit $main::cgi anzusprechen, da du ja auf dieses eine CGI Objekt des Hauptmodul zugreifen möchtest.
Möglich. Da ihr mir gezeigt habt, wo mein Denkfehler lag, kann ich ja jetzt erstmal bißchen weiterwurschteln.
Grüße aus Berlin
Christoph S.
Jetzt wollte ich das mit ein paar Subroutinen auch machen, und da klappt es nicht. Ich habe beispielsweise eine Subroutine, die so aussieht:
sub input {
if ($cgi->param('antwort')) {
$origdatum = $cgi->param('origdatum');
$origname = $cgi->param('origname');
$origemail = $cgi->param('origemail');
$origurl = $cgi->param('origurl');
$origthema = $cgi->param('origthema');
} else {
...
Ein Problem mit dem wohl fast jeder irgendwann zu kämpfen hat. Helmut hat dir ja schon erklärt warum das falsch ist und wie man das lösen kann. Das Problem ist aber, dass in einem CGI Skript fast überall Aufrufen von Funktionen aus dem CGI Modul versteckt sind und wenn du immer das CGI Objekt als Parameter übergeben musst bekommst du sicher einiges zu tun.
Ich hab das mittlerweile so gelöst, dass ich alle Aufrufe mit CGI:: ausführe. Da meiner Meinung die Objektorientierte Variante für ein Objekt, das genau einmal und global gebraucht wird nicht sinnvoll ist. (wobei ich aber auch dazu sagen muss, dass ich die Funktionen aus dem Modul sehr intensiv nutze und fast keine HTML Ausgabe ohne mehr mache.)
Struppi.
hallo Struppi,
Helmut hat dir ja schon erklärt warum das falsch ist
Nee, das hat er nicht ;-)
und wie man das lösen kann.
Aber auf die richtige Lösung gebracht hat er mich, jawohl.
Ich hab das mittlerweile so gelöst, dass ich alle Aufrufe mit CGI:: ausführe. Da meiner Meinung die Objektorientierte Variante für ein Objekt, das genau einmal und global gebraucht wird nicht sinnvoll ist. (wobei ich aber auch dazu sagen muss, dass ich die Funktionen aus dem Modul sehr intensiv nutze und fast keine HTML Ausgabe ohne mehr mache.)
Ich habe mich längere Zeit etwas schwer getan mit dem CGI-Modul. Wenn schon, dann möchte man ja auch möglichst konsequent sein. Und wenn ich mich daran setze, ein Script, das ganz gut funktioniert, zu modernisieren, dann möchte ich das natürlich so "sauber" machen wie ich es nur irgend kann.
Grüße aus Berlin
Christoph S.