Autor Thema: "peer review" - myUtils im "package"-Format (für Fortgeschrittene)  (Gelesen 1013 mal)

Offline Beta-User

  • Moderator
  • Hero Member
  • ***
  • Beiträge: 16352
Hallo zusammen,

nachdem wir die Tage hier eine nette Diskussion über "alles mögliche" hatten, ist es m.E. an der Zeit, mal eine alte Idee umzusetzen und sowas wie eine "gepackagte Muster-myUtils"-Datei hier vorzustellen. Da es aaO. u.A. auch schwerpunktmäßig um Schleifen und shift ging, knüpft die hier angehängte Datei da eigentlich ganz gut an, hat allerdings den kleinen Nachteil, dass sie sehr "modulnah" geschrieben ist und daher einige Funktionen direkt nutzt, die man als "normaler User" eher zugunsten der üblichen "fhem"-Aufrufe meiden sollte.

Vorneweg kurz meine persönlich Antwort auf die Frage, ob man packagen sollte: Es kommt darauf an!
  • Wer etwas tiefer in Perl einsteigen will (oder muss), kommt um das Thema kaum rum.
  • Wer nur sehr gelegentlich eigenen Code schreiben "muss" sollte sich eher an die mit FHEM verteilte (aktuelle!) myUtils-Musterfile halten und z.B. dann auch Prototypen verwenden. Die sind zwar definitiv nicht "Perl Best Practices", verhindern aber gewisse Fehler, denen man sonst anders Herr werden muss.
  • Die Vorteile:
    -- Innerhalb eines package ist man in seinem eigenen "namespace". Man braucht sich weder groß Gedanken zu machen, was Funktionsbezeichnungen (sub-Namen) angeht noch, was Variablennamen angeht. Die können dann auch wieder ohne "ich komme aus Datei-xy"-Präfix auskommen, und man muss nicht künstlich versuchen, die Zahl der Funktionen klein zu halten. Braucht man ein Helferlein, um eine wiederkehrende Teilaufgabe zu lösen, führt man die halt ein, fertig...
    -- man kann sich "perlcritic" (auch online und kostenlos!) zunutze machen, um "typische Fehler" aufzuspüren.
  • Der Nachteil: Alles, was man von FHEM (aus dem "main"-Kontext) braucht, muss man sich aktiv holen. Schlampt man, schießt man sich FHEM gnadenlos ab! Es kommt dann eben was in der Art:
Undefined subroutine &MYSENSORS::DEVICE::onStreamMessage called at ./FHEM/00_MYSENSORS.pm line 428.Hier ist dann auch gleich noch bzgl. Doku (commandref) "id" mit angerissen, und es gibt "summary"-Einträge.

Das ganze nennt sich "peer review", weil jeder eingeladen ist, ggf. dazu Verbesserungsvorschläge zu liefern, es dürfen aber selbstredend auch Fragen zum Code an sich gestellt werden. Vorab der Hinweis: Manche Einträge sind offenkundig völlig überflüssig; die stehen da für eine kommentierte Variante, siehe Folgebeitrag...

Viel Spaß,

Beta-User
« Letzte Änderung: 03 September 2021, 12:13:53 von Beta-User »
Server: HP-T620@Debian 10, aktuelles FHEM + ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn:MySensors, Weekday-&RandomTimer, Twilight,  AttrTemplate {u.a. mqtt2, mysensors, zwave}

Offline Beta-User

  • Moderator
  • Hero Member
  • ***
  • Beiträge: 16352
Antw:"peer review" - myUtils im "package"-Format
« Antwort #1 am: 30 August 2021, 16:32:45 »
Hier jetzt also ein paar Kommentare zum Code (bitte um Nachsicht, wenn manches etwas pointiert formuliert ist):
package FHEM::attrT_z2m_thermostat_Utils;    ## no critic 'Package declaration'

use strict;
use warnings;
Wir fangen gleich in unserem eigenen namespace an, was "von FHEM aus" gebraucht wird (Initialize-Funktion), exportieren wir später noch. "strict" und "warnings" bleiben Pflicht!
Und weil diverse formale Dinge aus diversen Gründen halt hingebogen werden müssen, gibt's ein Plaster, damit Perlcritic das insgesamt auf Stufe 3 "ok" findet :P .

use JSON qw(decode_json);
use Carp qw(carp);
Zwei Funktionen, die wir tatsächlich später nutzen. Aus den beiden Perl-Modulen brauchen wir nur jeweils eine spezielle Funktion, nicht alles.

#use POSIX qw(strftime);
#use List::Util qw( min max );
#use Scalar::Util qw( looks_like_number );
#use Time::HiRes qw( gettimeofday );
...auskommentiert & ansonsten einfach eine Liste mit Funktionen, die ich immer mal wieder brauche...
Wer mag, kann sich ja damit beschäftigen, was "min" und "max" aus "99_Utils.pm" nach derzeitigem Stand machen...

Und: "use POSIX;" (ohne konkrete Funktion) geht heutzutage gar nicht mehr!

use GPUtils qw(GP_Import);

## Import der FHEM Funktionen
#-- Run before package compilation
BEGIN {
    # Import from main context
    GP_Import(
        qw(
          AttrVal
          InternalVal
          ReadingsVal
          ReadingsNum
          ReadingsAge
          CommandGet
          CommandSet
          readingsSingleUpdate
          json2nameValue
          defs
          Log3
          )
    );
}
Wie gesagt: Wer was von FHEM braucht, muss es importieren. Das gilt für Funktionen, Variablen und Konstanten, einfach alles - den Import machen wir hier "etwas" (zu) ausgiebig.
- "fhem" wurde hier weggelassen, ist aber für Einsteiger dringlich zu empfehlen!
- "Log3" gibt es, weil "Log" als verbesserungswürdig angesehen wurde. Ambitioniertere Einsteiger (und Fotgeschrittene erst recht!) nutzen daher "Log3" und vergessen, dass es "Log" früher mal gab... (

sub ::attrT_z2m_thermostat_Utils_Initialize { goto &Initialize }
Das brauchen wir, damit die Datei überhaupt durch fhem.pl geladen werden kann! Es gilt weiter die Regel, dass der Dateiname ohne Präfix (99_) mit dem "_Initialize" als Funktionsname benötigt wird, es kommen dann noch die Doppelpunkte vorneweg, die nach "main" verweisen - das könnte man also auch so schreiben: 
sub main::attrT_z2m_thermostat_Utils_Initialize { goto &Initialize }
(Wer was besseres für den "goto" kennt: her damit... In Perl scheint das aber nicht so verpöhnt zu sein wie sonstwo).

# initialize ##################################################################
sub Initialize {
  my $hash = shift;
  return;
}
Die eigentliche Initialize-Funktion, hier in der "shift"-Schreibweise.
Und nein, man braucht kein "undef" bei dem return, und nein, man läßt das explizite return auch nicht weg.

# Enter you functions below _this_ line.

my %jsonmap = (

);
%jsonmap ist einfach nur ein völlig unnötiges Beispiel für eine Variable, die von überall her aus dem Code - aber auch nur von hier - verfügbar ist...

Jetzt also zur eigentlichen Schleifen-Funktion, die diese myUtils enthält:
sub z2t_send_weekprofile {
  my $name       = shift // carp q[No device name provided!]              && return;
  my $wp_name    = shift // carp q[No weekprofile device name provided!]  && return;
  my $wp_profile = shift // carp q[No weekprofile profile name provided!] && return;
  my $model      = shift // ReadingsVal($name,'week','5+2');
  my $topic      = shift // AttrVal($name,'devicetopic','') . '/set';
Es werden ein paar Variablen abgefragt. Die ersten drei sind Pflicht, die letzten beiden optional.
"carp" ermöglicht es, eine explizite Fehlermeldung zurückzugeben - also etwas mehr als "die Zahl der übergebenen Parameter paßt nicht"...
Bei den optionalen Parametern erfolgt gleich eine default-Wertezuweisung. Wer das mit "q{5+2}" etc. notieren will: feel free...

  my $hash = $defs{$name};
Nur ein Beispiel, wie man auf importierte Hashes aus main zugreift (ist eher gedacht für Fortgeschrittene!)

  $topic   .= ' ';
  my $wp_profile_data = CommandGet(undef,"$wp_name profile_data $wp_profile 0");
  if ($wp_profile_data =~ m{(profile.*not.found|usage..profile_data..name)}xms ) {
    Log3( $hash, 3, "[$name] weekprofile $wp_name: no profile named \"$wp_profile\" available" );
    return;
  }
Etwas "normaler Code". Die "Command.*"-Schreibweise ist auch wieder eher was für Fortgeschrittene - mein Beispiel ist leider an der Stelle nicht optimal, aber nur zu Demo-Zwecken wollte ich dann auch nicht den sehr "modulnahen" Code nicht ändern, der auch via contrib verteilt wird. Gilt enstpechend auch für "readingsSingleUpdate()" weiter unten.

  my @D = qw(Sun Mon Tue Wed Thu Fri Sat); # eqals to my @D = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat");
  my $payload;
  my @days = (0..6);
Ein paar Variablen bzw. Listen werden initialisiert...

  my $decoded;
  if ( !eval { $decoded  = decode_json($wp_profile_data) ; 1 } ) {
    Log3($name, 1, "JSON decoding error in $wp_profile provided by $wp_name: $@");
    return;
  }
"eval" kommt mir grundsätzlich "evil" vor... Will sagen: Dreimal (!) überlegen, ob man es benötigt, und im FHEM-Kontext ist meistens "AnalyzePerlCommand()" die bessere Wahl. Manchmal kann man es aber nicht vermeiden. Dann sollte man (als sehr fortgeschrittener User?) wissen, dass es zwei Formen gibt. Hier ist die "entschärfte Variante" am Werk, und nach meinen bisherigen Recherchen ist die obige Variante die "eigentlich korrekte" Variante, wie man in den allermeisten Fällen eventuelle erwartete/mögliche Programmfehler abfangen kann. Dass man die (immer mal wieder in der freien Wildbahn anzutreffende) "große Wumme" mit den runden Klammern wirklich braucht, ist mir eher selten untergekommen - genauer gesagt ist mir nur eine Gelegenheit gegenwärtig, bei der es sich nicht vermeiden ließ (bzw. bis dato keine andere funktionierende Variante vorgeschlagen wurde).

  if ( $model eq '5+2' || $model eq '6+1') {
    @days = (0,1);
  } elsif ($model eq '7') {
    @days = (1);
  }
Ja, man muss nicht zwingend nummerisch "von bis" schreiben, sondern kann auch gezielt einzelne Werte in die abzuarbeitende nummerische Liste schreiben...

  for my $i (@days) {
      $payload = '{';

      for my $j (0..7) {
        if (defined $decoded->{$D[$i]}{'time'}[$j]) {
          my $time = $decoded->{$D[$i]}{'time'}[$j-1] // "00:00";
          my ($hour,$minute) = split m{:}xms, $time;
          $hour = 0 if $hour == 24;
          $payload .= '"hour":' . abs($hour) .',"minute":'. abs($minute) .',"temperature":'.$decoded->{$D[$i]}{'temp'}[$j];
          $payload .= '},{' if defined $decoded->{$D[$i]}{'time'}[$j+1];
        }
      }
      $payload .='}';
      if ( $i == 0 && ( $model eq '5+2' || $model eq '6+1') ) {
        CommandSet($hash,"$name holidays $payload");
        $payload = '{';
      }
      CommandSet($hash,"$name workdays $payload") if $model eq '5+2' || $model eq '6+1' || $model eq '7';
  }
  readingsSingleUpdate( $hash, 'weekprofile', "$wp_name $wp_profile",1);
  return;
}
Die eigentliche "Doppelschleife", hier dann in der Version mit der expliziten "Benennung" der Laufvariablen...

1;

__END__
Die "1;" ist weiter für alle Pflicht, das "__END__" nur für die, die einfach gerne "sauber arbeiten".

Doku schenke ich mir, ist eigentlich selbsterklärend.
« Letzte Änderung: 30 August 2021, 17:24:18 von Beta-User »
Server: HP-T620@Debian 10, aktuelles FHEM + ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn:MySensors, Weekday-&RandomTimer, Twilight,  AttrTemplate {u.a. mqtt2, mysensors, zwave}

 

decade-submarginal