Výjimky v Perlu
Základy vyvolání a zachycení výjimek v jazyce Perl leží ve dvojici syntaktických konstrukcí die
a eval
. 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 autodie
a no 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 Error
a Exception::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 Rada a program využívající modul Rada.