Hlavní navigace

Perličky: jednoduché výjimky

Michal Svoboda 27. 6. 2008

Více nebo méně rozvinutý systém výjimek existuje v každém vysokoúrovňovém programovacím jazyce. V Perlu stojí implementace výjimek na velmi jednoduchém principu, nad kterým lze podle potřeby vybudovat aparát s požadovaným stupněm rozvinutí. Dnes bude probrán ten nejjednodušší, ovšem často postačující.

Výjimky v Perlu

Základy vyvolání a zachycení výjimek v jazyce Perl leží ve dvojici syntaktických konstrukcí dieeval. Vestavěná funkce die v běžném programu způsobí jeho ukončení, s případnou doplňující zprávou. Uvnitř bloku eval nedojde k ukončení programu, pouze tohoto bloku. Případnou doplňující hlášku nalezneme v proměnné $@ (pro angličtináře $EVAL_ERROR).

eval { die "vyjimka" };         # není-li řetězec ukončen \n,
                    # přidá se na konec informace
                    # o místě výskytu výjimky
print '$@: ', $@;           # $@: vyjimka at a.pl line 6.

eval { die "vyjimka\n" };
print '$@: ', $@;           # $@: vyjimka


eval {
    eval {
        die "vyjimka\n"
    };
    die             # prázdné die s neprázdným
                    # $@ přidá informaci o propagaci
};

print '$@: ', $@;           # $@: vyjimka at a.pl line 8.
                    #        ...propagated at a.pl line 10.

Ošetřit takovouto výjimku je možné pomocí klasického porovnání řetězců vůči $@. Jelikož je $@ globální, výskyt výjimky v ošetřovacím kódu má za následek přepsání $@. Při složitějším ošetřování je tedy vhodné si uložit nejprve obsah $@ do lokální proměnné.

sub f1 {
    die "vyjimka";
};

sub f2 {
    die "neco jineho";
};

for my $f (\(&f1, &f2)) {
    eval {
        $f->();
    };
    my $error = $@;

    if ($error ~~ /^vyjimka/) {
        print 'Chycena vyjimka: ', $@;
    } else {
        $@ = $error;
        die;
    }
}

print 'Konec programu';

Při volání první funkce f1 je vyvolána výjimka "vyjimka". Dejme tomu, že náš program je schopen tuto výjimku ošetřit, porovnáváme tedy $@ s jejím výskytem. (Zde je malá reklama na nový smart-match operátor ~~, dostupný od verze Perlu 5.10 a téměř výhradně používaný v Perlu 6.) Při volání funkce f2 se vyskytne jiná výjimka, kterou nejsme schopni ošetřit, a tudíž propagujeme výjimku dále. V našem programu již další nadřazený blok eval není, výskyt druhé výjimky tedy ukončí celý program a na vykonání posledního print již nedojde.

Umřít nebo zhebnout?

Představme si, že máme následující modul pro vypsání řady čísel:

package Rada;

use Exporter::Easy ( EXPORT => [ qw/&rada/ ] );

sub rada {
    my $max = shift // 0;
    die "Parametr musi byt kladny" if $max <= 0;

    local ($\, $,) = (qq/\n/, q/ /);
    print (1 .. $max);
};

1;

A v programu vypisradu.pl jej voláme takto:

use Rada;

rada(5);
rada(-1);

První volání vypíše správně řadu čísel, druhé volání (samozřejmě) umře s nepříliš vypovídající hláškou.

1 2 3 4 5
Parametr musi byt kladny at Rada.pm line 14.

Pomocí funkce croak z modulu Carp můžeme posunout „perspektivu“ chybové hlášky do volajícího modulu.

package Rada;

use Carp;
…
    croak "Parametr musi byt kladny" if $max <= 0;
…

Výsledná hláška pak bude „ukazovat“ na místo volání funkce rada.

Parametr musi byt kladny at vypisradu.pl line 9

Úplné doznání k posloupnosti volání dostaneme pomocí funkce confess, případně pomocí použití parametru -MCarp=verbose při volání skriptu.

Parametr musi byt kladny at Rada.pm line 13
        Rada::rada(-1) called at vypisradu.pl line 9

Automatické vyvolání výjimky

Řada zabudovaných funkcí Perlu, například open, (bohužel) používá namísto výjimek sémantiku vracení false při neúspěchu. Pedantické testování návratových hodnot je velmi nezáživné, a proto většina programátorů návratové hodnoty kontroluje pouze v těch nejvážnějších případech, či vůbec. Neošetřená chyba se ale může propagovat dále v kódu a vynořit na místě, který s původní funkcí nesouvisí.

open my $file, ">/etc/ahoj";
print $file "ahoj";
close $file;

Za podmínky, že nemáme přístup pro zápis do /etc, bude výstup programu vypadat nějak takto:

print() on closed filehandle $file at error.pl line 7.

… což nám o skutečném problému vypovídá přesně nic. (Hláška se objeví, pouze pokud máme use warnings; a ke všemu program drze skončí úspěšně.) Abychom nemuseli každou operaci testovat ( dadadada or die "selhalo dadadada"), použijeme modul Fatal, který umí kolem požadovaných funkcí nainstalovat pohodlný wrapper. Volaná funkce pak při selhání nevrací speciální hodnotu, ale výjimku.

use Fatal qw/open close/;

# zbytek jako předtím

Výstup programu pak bude:

Can't open(GLOB(0x606c50), >/etc/ahoj): Permission denied at (eval 1) line 3
        main::__ANON__('GLOB(0x606c50)', '>/etc/ahoj') called at error.pl line 7

Modul Fatal bohužel nejde použít na všechno. Některé funkce nelze univerzálně nahradit žádným wrapperem (například

print). Další skupina funkcí vrací při neúspěchu jinou hodnotu než false, například system. Konečně, některé funkce je potřeba volat v seznamovém kontextu a wrapper modulu Fatal by tento kontext nezachoval. Řešením všech případů je buď si napsat vlastní wrappery, nebo použít již existující moduly, např. IO::Moose pro objektové I/O včetně výjimek, nebo IPC::System::Simple pro náhražku funkce

system.

Na CPANu lze také (ačkoliv zatím pouze v beta verzi) nalézt modul autodie, který řeší některé z těchto problémů, ale zejména přidává novou funkcionalitu – lexikální omezení efektů, kde lze používat use autodieno autodie pro každý blok zvlášť, podobně jako  use strict.

Pokračování příště

V dnešním článku byly ukázány principy, na kterých stojí aparát výjimek v jazyce Perl. Prozatím jsme se však zabývali pouze výjimkami reprezentovanými řetězci. Takováto reprezentace v řadě případů plně postačuje, zejména pokud není potřeba různé výjimky třídit a ošetřovat zvlášť (princip vše nebo nic). V příštím článku si ukážeme, jak elegantně jsou tyto principy našroubovány na již probraný aparát objektového programování (což, jako v jiných programovacích jazycích, dává výjimkám úplně novou dimenzi), zejména pak moduly ErrorException::Class. Samozřejmě, má-li některý z čtenářů zkušenosti s nějakým jiným zajímavým modulem, ozvěte se, a případně jej včleníme do článku také.

Demonstrační příklady pro další experimenty: jednoduché generování a chytání výjimek, modul Radaprogram využívající modul Rada.

Našli jste v článku chybu?

30. 6. 2008 18:09

P.S. jeste me k tomu napada:

Pokud byste byl tvurce te funkce open (nebo jine, ktera muze potencialne selhat) tak nemate zadnou kontrolu nad tim, zda nejaky uzivatel to "or die" pouzije nebo ne. Pripadne ani nad tim do jake miry bude obsirny pri pripadne spovedi (ve vasem kodu neni ani $! - duvod chyby). Napriklad takova funkce "system" je dost brutus na analyzu navratove hodnoty.

Pomoci vyjimek muzete prave elegantne vyresit oboje dvoje. Vyjimky totiz nelze nevedomky ignor…



27. 6. 2008 12:55

Ted nevim zda narazite na "open F" misto "open my $f" ... to bylo vysvetleno v nejakem predeslem dile o referencich. Nebo na to "or die ..." coz je vysvetleno v dnesnim clanku. Bud muzete poctive kazdou operaci kontrolovat na selhani, coz je dost otrava a zneprehlednuje to kod. Nebo si muzete rict ze neco selhat "nemuze" a popustit uzdu a pak riskujete ze se vam pripadna chyba bude propagovat dale v kodu. Konecne, a nejlepe, muzete pouzit vyjimky, a to bud…
DigiZone.cz: ČRo rozšiřuje DAB do Berouna

ČRo rozšiřuje DAB do Berouna

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

Lupa.cz: Insolvenční řízení kvůli cookies? Vítejte v ČR

Insolvenční řízení kvůli cookies? Vítejte v ČR

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Vitalia.cz: To není kašel! Správná diagnóza zachrání život

To není kašel! Správná diagnóza zachrání život

Podnikatel.cz: Babiše přesvědčila 89letá podnikatelka?!

Babiše přesvědčila 89letá podnikatelka?!

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube

120na80.cz: Rakovina oka. Jak ji poznáte?

Rakovina oka. Jak ji poznáte?

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed