Сложности с использованием обработки исключений в Perl (перевод)

May 22, 2009 13:57

перевод статьи Peter Makholm Perl exception handling is hard

Всем хорошо известно, что обработка исключений в Perl реализуется при помощи eval и die, вместо try и catch.

eval {
do_something()
or die "Err!";
};

if ($@) {
print "Catched exception: $@";
}

Так советует делать Damian Conway в 16 главе своей книги Perl Best Practices и perldoc -f die. К сожалению, такая схема работает не во всех случаях. Рассмотрим пример:

#!/usr/bin/perl

package Foo;

sub new {
my $class = shift;
return bless {}, $class;
}

sub DESTROY {
my $self = shift;
eval { 1; };
}

package main;
eval {
my $foo = Foo->new();
open my $fh, ">", "/"
or die "Could not open /: $!";
print "Doing some work\n";
};
if ($@) {
print STDERR "Something bad happened: $@\n";
} else {
print "Everything went well\n";
}

Ввиду невозможноси открыть "/" для записи, мы ожидаем получить сообщение об ошибке. Но в реальности скрипт завершается успешно, хотя при этом не выводит "Doing some work". Что-же происходит? При выходе из области видимости $foo вызывается деструктор, в котором вызывается eval и изменяет значение $@.

Что можно сделать для решения проблемы? Прежде всего можно избежать модификации @_ в деструкторе. Добиться этого можно при помощи локализации @_. Даже если вы не используете eval явно, он может вызыватся где-то в глубине. Поэтому всегда начинайте писать деструктор по следующему шаблону:

sub DESTROY {
my $self = shift;
local $@;
...;
}

Скорее всего вы также захотите локализовать $?, но это уже другая история.

Еще одним вариантом решения будет изменение схемы эмуляции try/catch. В случае аварийного завершения блока eval возвращает значение undef. Поэтому можно явно прописать в блоке возврат "истинного" значения и заменить проверку $@ на логическое условие or.

eval {
do_work();
1;
} or {
print "catch: $@";
}

Конечно, описание ошибки в $@ может быть некорректным, поэтому такой метод не так хорош, как вариант с деструктором. В случае использования модулей сторонних авторов вы можете реализовать многоуровневую схему используя оба приведенных решения. Я думаю, в Perl::Critic есть политика обрабатывающая вышеприведенную try/catch конструкцию.

Мне стало интересно, как модули обработки исключений с CPAN ведут себя с вариантом вызова деструктора. Я проверил два из них: Error.pm обрабатывает вызов дестуктора корректно, т.к. в нем исключение запоминается до вызова die. Более современный модуль TryCatch.pm, к сожалению, работает в таком случае некорректно (см. rt #46294), хотя он намного функциональнее.

Дополнение: Я планирую написать патч для TryCatch.pm, который реализует функцию throw, аналогично Error.pm.

perl, exception handling

Previous post Next post
Up