Best Practice main -> eigener Namespace

Begonnen von RichardCZ, 10 April 2020, 15:26:19

Vorheriges Thema - Nächstes Thema

RichardCZ

Nachdem ich bei Hobo nun etliche Funktionen aus main geholt (und wieder reinimportiert) habe...
und mit Absicht darunter auch einiges der zentralen Funktionalität war und mir - mittlerweile  ;) - Hobo auch nicht mehr um die Ohren fliegt, kann ich ja einige Erkenntnisse weitergeben:

Prinzipiell gehe ich so vor, dass ich ein Modul erzeuge (Name = Dateiname), da Code aus main reinverschiebe und diesen Code wieder nach main exportiere.
Dabei gehe ich erstmal nur sehr grob Pi * Daumen vor und rechne von vornherein damit, dass sich Namen und Inhalt der Module noch ändern können bzw. werden.

Beispiel: HomeBot::Command

https://gl.petatech.eu/root/HomeBot/-/blob/master/lib/HomeBot/Command.pm

Man könnte sagen, da sind einige der zentralsten FHEM Funktionen drin ohne die gar nix läuft. Diese sind auch ziemlich eng mit dem main Namespace und insbesondere mit den globalgalaktisch globalen Variablen verwoben.

Am Anfang fackel' ich die ganzen globalen Variablen ab mittels modulglobalen Referenzen drauf

my $attr        = \%main::attr;
my $defaultattr = \%main::defaultattr;
my $defs        = \%main::defs;
my $modules     = \%main::modules;
my $ntfyHash    = \%main::ntfyHash;
my $oldvalue    = \%main::oldvalue;
my $ra          = \%main::ra;
my $readyfnlist = \%main::readyfnlist;
my $selectlist  = \%main::selectlist;
my $sleepers    = \%main::sleepers;


Das hat den Vorteil, dass ich nun eine Stelle im Code habe - schön oben prominent nach den uses - wo ich diese galaktisch globalen Variablen über modulglobale Variablen (in meinem Namensraum) referenziere.

Im Code wird aus einem $defs{blah}->{foo}->{bar} (bei euch $defs{blah}{foo}{bar}, was das gleiche in schmutzig ist) also das konsistente
$defs->{blah}->{foo}->{bar}

Ab diesem Zeitpunkt ist der Code von der globalgalaktischen Variable entkoppelt und funktioniert auf einer Referenz auf ein Hash array. Ob das nun

\%main::defs
oder
my $defs = Object::Singleton->new('defs');
ist wurst. ;-)

zugegeben, ich verwende das elegante :Export Attribut aus Export::Attrs um mir die Redundanzen von https://perldoc.perl.org/Exporter.html zu sparen, aber wenn man das nicht hat, ist es halt ein wenig mehr Schreibarbeit und mehr Wartungsaufwand (exportierte identifier an zwei Stellen in-sync halten, aber wer Prototypen los ist muss das wenigstens nicht an 3 Stellen machen)

Was bedeutet das für ein Modul?

Man macht es genauso. :-)

27_DollesModul.pm

wird einfach zu einem "main::-Interface"

package main;

# siehe https://forum.fhem.de/index.php/topic,109986.0.html
use FHEM::Mein::Echtes::Modul::Mit::Sinnvollem::Namen qw(DollesModul_Init DollesModul_Define DollesModul_HinternPutz);


ab da sind dann die importierten (und nur die!) identifier wieder in main::

und irgendwo dann unter

lib/FHEM/Mein/Echtes/Modul/Mit/Sinnvollem/Namen.pm

oder wo auch immer das FHEM @INC hinzeigt, hat man dann endlich ein gekapseltes Modul
wo man sich die main globals holt und die relevante Funktionalität exportiert

package FHEM::Mein::Echtes::Modul::Mit::Sinnvollem::Namen; # ab hier nur noch <modulname>

require Exporter;
  our @ISA = qw(Exporter);
  our @EXPORT_OK = qw(DollesModul_Init DollesModul_Define DollesModul_HinternPutz);

my $attr        = \%main::attr;
my $defs        = \%main::defs;

... Code here ....


Vorteile:

Muss ich echt ausführen?

* alles was nicht explizit exportiert wird ist privat. Definitiv keine Seiteneffekte im Namensraum.
* man hat weiterhin Zugriff auf main, dann eben als main::xxx Je mehr main:: man schreiben muss, desto mehr stutzig sollte man werden
* man kann sich das DollesModul als Funktionspräfix schenken
* endlich hat man zumindest die theoretische Möglichkeit für <modulname> eine Testsuite zu schreiben
* man kann ohne weitere Codeänderungen mit perl -c <modulname> checken ob alles syntaxmäßig in Ordnung ist.
* jetzt kann man mittels use <anderes modul> qw(import1 import2); Funktionalität aus anderen Modulen holen ohne main::zu behelligen
* und ungefähr noch 100 weitere Dinge
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

RichardCZ

Zitatman hat weiterhin Zugriff auf main, dann eben als main::xxx Je mehr main:: man schreiben muss, desto mehr stutzig sollte man werden

Und weil wir auch diese Nicht-Eleganz ganz  ;) loswerden wollen:

https://gl.petatech.eu/root/HomeBot/-/commit/d3e80b4655b67acfe62455b09f0bd014e3293413

Womit wir uns auch die ganzen main::-Änderungen im Code sparen und eine Stelle im Code haben an der sozuagen die Referenzübergabe zu unserem Namespace passiert.

In dem obigen Beispiel ist nämlich ab dem Alias die devspec2array sub unsere lokale sub. Man könnte die auch anstelle des goto &func aliases kurzerhand umdefinieren - wenn man denn wollte.

Übrigens, wer nun schreit "Der hat goto gesagt", siehe https://perldoc.perl.org/functions/goto.html

ZitatThe goto &NAME form is quite different from the other forms of goto. In fact, it isn't a goto in the normal sense at all, and doesn't have the stigma associated with other gotos.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey

Hi RichardCZ,

Ich finde deine Ansätze sehr interessant.
Vor allem, da man so Standard Modultests schreiben könnte. Ich hab die derzeit notgedrungen als eigenes FHEM Modul etabliert.

Zu der Referenz auf die globalen Variable habe ich eine Frage:
ZitatAm Anfang fackel' ich die ganzen globalen Variablen ab mittels modulglobalen Referenzen drauf

Wieso deklariert Du die nicht mit our.
Das bindet doch ebenfalls die Variable aus FHEM.


Grüße Sidey


Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

RichardCZ

Zitat von: Sidey am 12 April 2020, 17:21:57
Vor allem, da man so Standard Modultests schreiben könnte. Ich hab die derzeit notgedrungen als eigenes FHEM Modul etabliert.

Ja, Tests sind lange überfällig und da arbeite ich auch primär darauf zu derzeit.

Zitat
Zu der Referenz auf die globalen Variable habe ich eine Frage:
Wieso deklariert Du die nicht mit our.
Das bindet doch ebenfalls die Variable aus FHEM.

Das our funktioniert aber nur innerhalb eines (des aktuellen) package. Hier geht es ja darum eine globale Variable aus einem anderen Package zu referenzieren. Die gibt es ja nur in main:: und nicht in MyPackage::
https://perldoc.perl.org/functions/our.html

Auch hier habe ich eigentlich das langfristige Ziel die globalen Variablen ganz loszuwerden. Attr/Defs/... vermutlich dann als Singleton-Objekte realisiert.
Also ist das derzeit eine Zwischenkrücke um mal wenigstens das Interface an eine Stelle im Code zu bekommen, bzw. so ein erster Schritt der Kapselung.

Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

DS_Starter

Ich habe angefangen meine Module hinsichtlich diverser Vorschläge / Optimierungen zu überarbeiten.
Aktuell habe ich 93_Log2Syslog auf Packages umgestellt. Bevor ich es nun einchecke habe ich nochmal

perlcritic -4

drüber laufen lassen. Nun bringt der Check aber

Package declaration must match filename at line 34, column 1.  Correct the filename or package statement.  (Severity: 5)

Nun, das folgt sicher daraus weil das File 93_Log2Syslog heißt, das Package aber package FHEM::Log2Syslog;


package FHEM::Log2Syslog;

use strict;
use warnings;
use TcpServerUtils;
....


@Cooltux, das sollte ja bei deinem "package FHEM::AutoShuttersControl;"  auch so angemeckert werden ?
Ich habe hier noch nicht gesehen, dass wir z.B. FHEM::93_Log2Syslog definieren würden  ::) ?

mit "## no critic" ignorieren ?

Grüße,
Heiko
ESXi@NUC+Debian+MariaDB, PV: SMA, Victron MPII+Pylontech+CerboGX
Maintainer: SSCam, SSChatBot, SSCal, SSFile, DbLog/DbRep, Log2Syslog, SolarForecast,Watches, Dashboard, PylonLowVoltage
Kaffeekasse: https://www.paypal.me/HMaaz
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter

Wzut

Zitat von: DS_Starter am 14 April 2020, 11:17:20
mit "## no critic" ignorieren ?
ich habe mir jetzt erst einmal die Variante :
package FHEM::readingsWatcher;  ## no critic 'package'
# das no critic könnte weg wenn die Module nicht mehr zwingend mit NN_ beginnnen müssen

Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

DS_Starter

Danke mein lieber Wzut.  Dachte ich mir.  :D

Grüße,
Heiko
ESXi@NUC+Debian+MariaDB, PV: SMA, Victron MPII+Pylontech+CerboGX
Maintainer: SSCam, SSChatBot, SSCal, SSFile, DbLog/DbRep, Log2Syslog, SolarForecast,Watches, Dashboard, PylonLowVoltage
Kaffeekasse: https://www.paypal.me/HMaaz
Contrib: https://svn.fhem.de/trac/browser/trunk/fhem/contrib/DS_Starter

CoolTux

Zitat von: RichardCZ am 10 April 2020, 15:26:19
Nachdem ich bei Hobo nun etliche Funktionen aus main geholt (und wieder reinimportiert) habe...
und mit Absicht darunter auch einiges der zentralen Funktionalität war und mir - mittlerweile  ;) - Hobo auch nicht mehr um die Ohren fliegt, kann ich ja einige Erkenntnisse weitergeben:

Prinzipiell gehe ich so vor, dass ich ein Modul erzeuge (Name = Dateiname), da Code aus main reinverschiebe und diesen Code wieder nach main exportiere.
Dabei gehe ich erstmal nur sehr grob Pi * Daumen vor und rechne von vornherein damit, dass sich Namen und Inhalt der Module noch ändern können bzw. werden.

Beispiel: HomeBot::Command

https://gl.petatech.eu/root/HomeBot/-/blob/master/lib/HomeBot/Command.pm

Man könnte sagen, da sind einige der zentralsten FHEM Funktionen drin ohne die gar nix läuft. Diese sind auch ziemlich eng mit dem main Namespace und insbesondere mit den globalgalaktisch globalen Variablen verwoben.

Am Anfang fackel' ich die ganzen globalen Variablen ab mittels modulglobalen Referenzen drauf

my $attr        = \%main::attr;
my $defaultattr = \%main::defaultattr;
my $defs        = \%main::defs;
my $modules     = \%main::modules;
my $ntfyHash    = \%main::ntfyHash;
my $oldvalue    = \%main::oldvalue;
my $ra          = \%main::ra;
my $readyfnlist = \%main::readyfnlist;
my $selectlist  = \%main::selectlist;
my $sleepers    = \%main::sleepers;


Das hat den Vorteil, dass ich nun eine Stelle im Code habe - schön oben prominent nach den uses - wo ich diese galaktisch globalen Variablen über modulglobale Variablen (in meinem Namensraum) referenziere.

Im Code wird aus einem $defs{blah}->{foo}->{bar} (bei euch $defs{blah}{foo}{bar}, was das gleiche in schmutzig ist) also das konsistente
$defs->{blah}->{foo}->{bar}

Ab diesem Zeitpunkt ist der Code von der globalgalaktischen Variable entkoppelt und funktioniert auf einer Referenz auf ein Hash array. Ob das nun

\%main::defs
oder
my $defs = Object::Singleton->new('defs');
ist wurst. ;-)

zugegeben, ich verwende das elegante :Export Attribut aus Export::Attrs um mir die Redundanzen von https://perldoc.perl.org/Exporter.html zu sparen, aber wenn man das nicht hat, ist es halt ein wenig mehr Schreibarbeit und mehr Wartungsaufwand (exportierte identifier an zwei Stellen in-sync halten, aber wer Prototypen los ist muss das wenigstens nicht an 3 Stellen machen)

Was bedeutet das für ein Modul?

Man macht es genauso. :-)

27_DollesModul.pm

wird einfach zu einem "main::-Interface"

package main;

# siehe https://forum.fhem.de/index.php/topic,109986.0.html
use FHEM::Mein::Echtes::Modul::Mit::Sinnvollem::Namen qw(DollesModul_Init DollesModul_Define DollesModul_HinternPutz);


ab da sind dann die importierten (und nur die!) identifier wieder in main::

und irgendwo dann unter

lib/FHEM/Mein/Echtes/Modul/Mit/Sinnvollem/Namen.pm

oder wo auch immer das FHEM @INC hinzeigt, hat man dann endlich ein gekapseltes Modul
wo man sich die main globals holt und die relevante Funktionalität exportiert

package FHEM::Mein::Echtes::Modul::Mit::Sinnvollem::Namen; # ab hier nur noch <modulname>

require Exporter;
  our @ISA = qw(Exporter);
  our @EXPORT_OK = qw(DollesModul_Init DollesModul_Define DollesModul_HinternPutz);

my $attr        = \%main::attr;
my $defs        = \%main::defs;

... Code here ....


Vorteile:

Muss ich echt ausführen?

* alles was nicht explizit exportiert wird ist privat. Definitiv keine Seiteneffekte im Namensraum.
* man hat weiterhin Zugriff auf main, dann eben als main::xxx Je mehr main:: man schreiben muss, desto mehr stutzig sollte man werden
* man kann sich das DollesModul als Funktionspräfix schenken
* endlich hat man zumindest die theoretische Möglichkeit für <modulname> eine Testsuite zu schreiben
* man kann ohne weitere Codeänderungen mit perl -c <modulname> checken ob alles syntaxmäßig in Ordnung ist.
* jetzt kann man mittels use <anderes modul> qw(import1 import2); Funktionalität aus anderen Modulen holen ohne main::zu behelligen
* und ungefähr noch 100 weitere Dinge

Guten Morgen Heiko,

Ja ich habe die Meldungen. Aber bei Meldungen wo ich noch nichts machen kann schaue ich einfach drüber weg  ;D
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Sidey

Ich habe den Vorschlag von RichardCZ so verstanden, dass das eigentlich Modul in ein eigens Package in lib kommt.
Da muss auch keine Nummer vor den Dateinamen.

Das FHEM Modul bleibt im Namespace Main und ist nur die Brücke um die lib zu laden.

Grüße Sidey

Gesendet von meinem Moto Z (2) mit Tapatalk

Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

CoolTux

#9
Das ist die Idee wie es mal werden konnte wenn man BPB PBP und so umsetzen wollte.
So machen es auch große Perl Projekte (Proxmox, OTRS).

Aber aktuell geht es nur so.
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

RichardCZ

Zitat von: CoolTux am 14 April 2020, 13:06:48
Das ist die Idee wie es mal werden konnte wenn man BPB und so umsetzen wollte.

Best Perl Best - SIR!  :)

Nicht zu verwechseln mit BPP - den British Petrol Practices.
Witty House Infrastructure Processor (WHIP) is a modern and
comprehensive full-stack smart home framework for the 21st century.

Sidey

Signalduino, Homematic, Raspberry Pi, Mysensors, MQTT, Alexa, Docker, AlexaFhem

Maintainer von: SIGNALduino, fhem-docker, alexa-fhem-docker, fhempy-docker

CoolTux

Zitat von: RichardCZ am 14 April 2020, 14:49:53
Best Perl Best - SIR!  :)

Nicht zu verwechseln mit BPP - den British Petrol Practices.

Ich habe das mal anders geschrieben. Die wenigstens kennen sich mit Best Perl Best aus  ;D
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

CoolTux

Zitat von: Sidey am 14 April 2020, 15:01:35
Ja wie denn jetzt?

FHEM::PACKAGENAME

also

FHEM::AutoShuttersControl

FHEM::UWZ
Du musst nicht wissen wie es geht! Du musst nur wissen wo es steht, wie es geht.
Support me to buy new test hardware for development: https://www.paypal.com/paypalme/MOldenburg
My FHEM Git: https://git.cooltux.net/FHEM/
Das TuxNet Wiki:
https://www.cooltux.net

Christoph Morrison

Ich hab beim Modul mod-Serienjunkies mal den gewohnte Weg verlassen und habe in FHEM/ nur einen Loader, während die Payload in lib/ liegt.

Leider funktioniert das Ausliefern über update mit dieser Struktur nach wie vor nicht.