Vom Spaghetti-Code zur wartbaren Software

Spaghetticode (Wikipedia)

Ein Teller Spaghetti sieht verworren und unübersichtlich aus. Von diesem Aussehen leitet sich der Name Spaghetticode ab. Spaghetticode ist ein abwertender Begriff für Software-Quellcode, der komplexe und verworrene Kontrollstrukturen aufweist.

Spaghetticode

Spaghetticode

Strukturierung

oder warum ich immer wieder Encodingprobleme habe.

Strukturierung

Strukturierung

Beispiel Strukturierung

sub block_example {
    my ($number, $count, $offset) = @_;

    $number *= $count;
    $number += $offset;

    return $number;
}

Teilaufgaben

Aufgabe: Dateien mit unterschiedlichem Encoding einlesen und die Werte darin sortiert als Tabelle in HTML ausgeben.

  1. Dateien einlesen (unterschiedliches Encoding berücksichtigen)
  2. Daten sortieren
  3. HMTL ausgeben (UTF-8)

Spaghettiansatz

Die Aufgabe ist einfach

Morgen kommt jemand auf die Idee, dass die Ausgabe in Excel benötigt wird - oh.

HTML in Excel?

"Done" oder wie ich

nicht einmal halbfertig war

bei Fehlern/Anfoderungen

Testen ist teuer

Refactoring: legacy code

use strict;
use warnings;

no warnings qw(uninitialized);

Alles andere sollte ohne Codezerstörung behebbar sein. Sinnvollerweise das "no warnings" nicht global, sondern nur in die fehlerhaften Blöcke einfügen. Beim weiteren Refactoring verschwinden diese dann wieder.

Fehler lernen sprechen

local $SIG{__DIE__}
    = sub { use Carp qw(confess); confess @_ };
local $SIG{__WARN__}
    = sub { use Carp qw(cluck);   cluck   @_ };

local *CORE::GLOBAL::die
    = sub { ...; die ...; };
local *CORE::GLOBAL::warn
    = sub { ...; warn ...; };

Ende der Annonymität

PackageName::__ANON__()
im caller stack

my $code_ref1 = sub {
    local *__ANON__= 'code_ref1';
    ...
};
my $code_ref2 = sub {
    local *__ANON__= 'code_ref2';
    ...
};

global destruction

sigtrap - oder warum File::Temp bei "ctrl C" nicht aufräumt.

use sigtrap 'handler'
    => \&my_handler, 'normal-signals';
use sigtrap 'handler'
    => sub { exit }, 'normal-signals';

Variablen und Datentypen

Moose::Util::TypeConstraints
|-- Any
`-- Item
    |-- Bool
    |-- Maybe[`a]
    |-- Undef
    `-- Defined -------.
        |               \
        `-- Value        `-- Ref
           `-- Str           |-- ScalarRef[`a]
               |-- Num       |-- ArrayRef[`a]
               |   `-- Int   |-- HashRef[`a]
               |-- ClassName |-- CodeRef
               `-- RoleName  |-- RegexpRef
                             |-- GlobRef
                             |-- FileHandle
                             `-- Object

Warnungen vermeiden

Defined
`-- Value
    `-- Str
        `-- Num

Jetzt weiss ich, warum $var == 0 Warnungen werfen kann.

Hier hilft "use Scalar::Util qw(looks_like_number);".

Kontext != || ne Chaos

Kontext: Es ist nicht nur entscheidend, was man schreibt, sondern auch in welchem Umfeld.

Kontext

Kontext

if ( scalar @errors > 0 ) {
if (        @errors > 0 ) {
if (        @errors     ) {

http://perldoc.perl.org/perlop.html

Kontext

Boolean ist etwas Besonderes

   perl -e 'use Devel::Peek; Dump 1 == 1'

und

   perl -e 'use Devel::Peek; Dump 0 == 1'

Devel::Peek schaut in den Bauch einer Perl-Datenstruktur.

Boolean ist etwas Besonderes

SV - Scalar value
Flags
 IOK      -> int gültig
 NOK      -> numeric gültig
 POK      -> pointer auf String gültig
 READONLY -> 0 == 1 = 1 geht eben nicht zu schreiben
 IV = 1   -> int ist 1
 NV = 1   -> numeric ist 1
 PV       -> es liegt ein string auf Adresse 0x...
             mit dem Wert "1" mit \0 abgeschlossen
CUR = 1   -> Der string hat eine Länge von 1
LEN = 1   -> Die gesamte Datenstruktur hat eine
             Länge von 8 Bytes

Boolean ist etwas Besonderes

Für viele, die aus anderen Programmiersprachen kommen, ist schon mal völlig unklar, wie ein Scalar gleichzeitig mehrere gültige Werte (String, Numeric, Integer) beinhalten kann.

"https://metacpan.org/module/Scalar::MultiValue" ist ein Modul, mit dem man das aktiv machen kann.

Boolean ist etwas Besonderes

|| && and or

macht das nicht. Hier wird der rechte Teil der Anweisung nicht boolsch evaluiert.

Deswegen ergibt

50 && 100

100 und nicht nur einen wahren Boolean.

Boolean ist etwas Besonderes

Dual-Value numerisch verwenden

$boolean = !! $any;
$numeric = 0 + $boolean;
$string  = q{} . $boolean;

0 + !! 'bla'
0 + !! ( $result && $result->event )

Array/Hash/Liste

$array_ref   = [1, 2];

# nein, das ist keine Listen Referenz
$scalar_ref  = \(1, 2);

# nein, das ist auch keine Listen Referenz
@scalar_refs = \(1, 2);

scalar @array
@array = ($scalar);

wantarray

sub my_method {
    my (undef, $second) = @_;

    defined wantarray
        or return;     # called in void context

    # hier evtl. aufwendige Ermittlung der Rückgabe

    return wantarray
        ? $second      # called in list context
        : [ $second ]; # called in scalar context
}

wantarray in Pascal-Perl

sub my_method {
    my (undef, $second) = @_;

    my @any;
    if ( defined wantarray ) {
        # hier evtl. aufwendige Ermittlung der Rückgabe

        if ( wantarray ) {
            @any = ( $second );     # called in list context
        }
        else {
            @any = ( [ $second ] ); # called in scalar context
        }
    }
    else {                          # called in void context
        @any = ();
    }

    return @any;
}

Arrays und Hashes mit Listen befüllen

@array = (1, 2, 3);
%hash  = (1, 2, 3); # Odd number of elements in hash assignment

Die Magie der leeren Liste

() zum Leeren passt immer

Die Magie der leeren Liste

$integer = () = "1\n2\n3\n" =~ m{ ( \n ) }xmsg; # 3

scalar

sub value {
    my @values = @_;

    return @values ? $value[0] : (); # () und nicht undef
}

my %hash = (
    undef => scalar value(),
    empty => [ value() ],
    one   => scalar value(1),
);

Wer alle Klammern schreibt,

schreibt nicht alle.

my ${scalar} = 'string';
${_}->sub();

Häufungen von schließenden Klammern sind Zeichen für schlechte Strukturierung. Bei den öffnenden Klammern stelle man ich z.B. if vor.

{{{{{{{{{{
(((((
1
)))));
}}}}}}}}}}

Das + und die Klammer {

{} als Block oder als Hash-Referenz ?

@array = map { 1; { $_ => $_ } } (1, 2); # block
@array = map {
    1;
    { $_ => $_ }; # block
} (1, 2);
@array_of_hash = map {
    1;
    +{ $_ => $_ }; # hash reference
} (1, 2);

Das + und die Klammer (

() als gruppierende oder Funktionsklammer?

# Funktionsklammer
return (           5   -     2   ) * 3;   # 3

# gruppierende Klammer
return +(          5   -     2   ) * 3;   # 9

# Leerzeichen nach + => unlesbar
return + ( + ( + ( 5 ) - + ( 2 ) ) * 3 ); # 9

Ende