Что сломано об исключениях в Perl? - PullRequest
29 голосов
/ 30 января 2010

Обсуждение в другом вопросе заставило меня задуматься: что есть в системах исключений других языков программирования, которых нет в Perl?

Встроенные исключения Perl немного ad-hoc в том смысле, что они, как и объектная система Perl 5, были добавлены в качестве запоздалой мысли и перегружают другие ключевые слова (eval и die), которые не предназначены специально для исключений.

Синтаксис может быть немного уродливым по сравнению с языками со встроенным синтаксисом типа try / throw / catch. Я обычно делаю это так:

eval { 
    do_something_that_might_barf();
};

if ( my $err = $@ ) { 
    # handle $err here
}

Существует несколько модулей CPAN, которые предоставляют синтаксический сахар для добавления ключевых слов try / catch и позволяют легко объявлять иерархии классов исключений и тому подобное.

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

Ответы [ 8 ]

25 голосов
/ 30 января 2010

Try :: Tiny (или модули, построенные на его основе) - единственный правильный способ справиться с исключениями в Perl 5. Сложные проблемы тонки, но в связанной статье объясняется их подробно.

Вот как это использовать:

use Try::Tiny;

try {
    my $code = 'goes here';
    succeed() or die 'with an error';
}
catch {
    say "OH NOES, YOUR PROGRAM HAZ ERROR: $_";
};

eval и $@ - это движущиеся части, которые вам не нужны.

Некоторые люди думают, что это клудж, но, прочитав реализации других языков (а также Perl 5), он ничем не отличается от других. Есть только движущаяся часть $@, в которую вы можете зацепить руку ... но, как и в случае с другими частями машины с открытыми движущимися частями ... если вы не дотронетесь до нее, она не оторвет вам пальцы , Так что используйте Try :: Tiny и сохраняйте скорость набора текста;)

24 голосов
/ 30 января 2010

Типичный метод, который большинство людей научилось обрабатывать исключения, уязвим для пропущенных захваченных исключений:

eval { some code here };
if( $@ ) {  handle exception here };

Вы можете сделать:

eval { some code here; 1 } or do { handle exception here };

Это защищает от пропуска исключения из-за засорения $@, но оно все еще уязвимо для потери значения $@.

Чтобы быть уверенным, что вы не закроете исключение, при выполнении eval необходимо локализовать $@;

eval { local $@; some code here; 1 } or do { handle exception here };

Это все тонкие поломки, а профилактика требует большого количества эзотерических шаблонов.

В большинстве случаев это не проблема. Но я был сожжен исключением, съевшим деструкторы объектов в реальном коде. Отладка проблемы была ужасной.

Ситуация явно плохая. Посмотрите на все модули на CPAN, обеспечивающие достойную обработку исключений.

Потрясающие ответы в пользу Try :: Tiny в сочетании с тем фактом, что Try :: Tiny не "слишком умна наполовину", убедили меня попробовать это. Такие вещи, как TryCatch и Exception :: Class :: TryCatch , Error и так далее, слишком сложны для меня, чтобы мне доверять. Try :: Tiny - это шаг в правильном направлении, но у меня все еще нет легкого класса исключений для использования.

13 голосов
/ 04 февраля 2010

Некоторые классы исключений, например Ошибка , не может обрабатывать управление потоком из блоков try / catch. Это приводит к тонким ошибкам:

use strict; use warnings;
use Error qw(:try);

foreach my $blah (@somelist)
{
    try
    {
        somemethod($blah);
    }
    catch Error with
    {
        my $exception = shift;
        warn "error while processing $blah: " . $exception->stacktrace();
        next;    # bzzt, this will not do what you want it to!!!
    };

    # do more stuff...
}

Обходной путь - использовать переменную состояния и проверять ее вне блока try / catch, что для меня выглядит ужасно, как вонючий код n00b.

Две другие "ошибки" в Error (оба из которых вызвали у меня горе, поскольку их ужасно отлаживать, если вы не сталкивались с этим раньше):

use strict; use warnings;
try
{
    # do something
}
catch Error with
{
    # handle the exception
}

Выглядит разумно, верно? Этот код компилируется, но приводит к причудливым и непредсказуемым ошибкам. Проблемы:

  1. use Error qw(:try) был опущен, поэтому блок try {}... будет неправильно обработан (вы можете увидеть или не увидеть предупреждение, в зависимости от остальной части вашего кода)
  2. отсутствует точка с запятой после блока catch! Неинтуитивно, поскольку блоки управления не используют точки с запятой, но на самом деле try является вызовом прототипа .

О да, это также напоминает мне, что, поскольку try, catch и т. Д. Являются вызовами методов, это означает, что стек вызовов в этих блоках будет не таким, как вы ожидаете. (На самом деле есть два дополнительных уровня стека из-за внутреннего вызова внутри Error.pm.) Следовательно, у меня есть несколько модулей, заполненных шаблонным кодом, подобным этому, который просто добавляет беспорядок:

my $errorString;
try
{
    $x->do_something();
    if ($x->failure())
    {
        $errorString = 'some diagnostic string';
        return;     # break out of try block
    }

    do_more_stuff();
}
catch Error with
{
    my $exception = shift;
    $errorString = $exception->text();
}
finally
{
    local $Carp::CarpLevel += 2;
    croak "Could not perform action blah on " . $x->name() . ": " . $errorString if $errorString;
};
9 голосов
/ 30 января 2010

Проблема, с которой я недавно столкнулся при использовании механизма исключений eval, связана с обработчиком $SIG{__DIE__}. Я ошибочно предположил, что этот обработчик вызывается только при выходе из интерпретатора Perl через die(), и хотел использовать этот обработчик для регистрации фатальных событий. Затем выяснилось, что я регистрирую исключения в коде библиотеки как фатальные ошибки, которые явно были ошибочными.

Решением было проверить состояние переменной $^S или $EXCEPTIONS_BEING_CAUGHT:

use English;
$SIG{__DIE__} = sub {
    if (!$EXCEPTION_BEING_CAUGHT) {
        # fatal logging code here
    }
};

Проблема, которую я вижу здесь, заключается в том, что обработчик __DIE__ используется в двух похожих, но разных ситуациях. Эта переменная $^S очень похожа на позднюю надстройку для меня. Я не знаю, так ли это на самом деле.

2 голосов
/ 30 января 2010

В Perl комбинируются языковые и пользовательские исключения: оба установлены $@. В других языках языковые исключения отделены от написанных пользователем исключений и создают совершенно отдельный поток.

Вы можете поймать базу написанных пользователем исключений.

Если есть My::Exception::one и My::Exception::two

if ($@ and $@->isa('My::Exception'))

поймает обоих.

Не забудьте перехватить любые не-пользовательские исключения с else.

elsif ($@)
    {
    print "Other Error $@\n";
    exit;
    }

Также приятно заключить исключение в подпрограмму, вызывая подпрограмму, чтобы выбросить ее.

1 голос
/ 30 января 2010

Прошло много времени с тех пор, как я использовал Perl, поэтому моя память может быть нечеткой и / или Perl мог улучшиться, но насколько я помню (по сравнению с Python, который я использую ежедневно):

  1. , поскольку исключения являются поздним добавлением, они не всегда поддерживаются в основных библиотеках

    (Не соответствует действительности; они не всегда поддерживаются в основных библиотеках, поскольку программистам, написавшим эти библиотеки, не нравятся исключения.)

  2. предопределенной иерархии исключений не существует - вы не можете перехватить связанную группу исключений, перехватив базовый класс

  3. нет эквивалента try: ... finally: ... для определения кода, который будет вызываться независимо от того, было ли вызвано исключение или нет, например, освободить ресурсы.

    (finally в Perl в основном не нужен - деструкторы объектов запускаются сразу после выхода из области; не всякий раз, когда случается нехватка памяти. Таким образом, вы можете фактически освободить любые ресурсы, не связанные с памятью, в вашем деструкторе, и это будет работать разумно.)

  4. (насколько я могу судить) вы можете бросать только строки - вы не можете бросать объекты, которые имеют дополнительную информацию

    (Совершенно неверно. die $object работает так же, как die $string.)

  5. вы не можете получить трассировку стека, показывающую вам, где было сгенерировано исключение - в python вы получаете подробную информацию, включая исходный код для каждой строки в стеке вызовов

    (Неверно. perl -MCarp::Always и наслаждайтесь.)

  6. это безобразный бугорок.

    (Субъективно. В Perl он реализован так же, как и везде. Он просто использует ключевые слова с разными именами.)

1 голос
/ 30 января 2010

В C ++ и C # вы можете определить типы, которые могут быть выброшены, с отдельными блоками перехвата, которые управляют каждым типом. Системы типа Perl имеют определенные проблемы, связанные с RTTI и наследованием, согласно тому, что я читал в блоге chomatic.

Я не уверен, как другие динамические языки управляют исключениями; и C ++, и C # являются статическими языками, и это имеет определенную силу в системе типов.

Философская проблема заключается в том, что в Perl 5 добавлены исключения; они не созданы с самого начала разработки языка как нечто неотъемлемое от того, как написан Perl.

0 голосов
/ 01 января 2015

Не используйте исключения для обычных ошибок. Только фатальные проблемы, которые остановят текущее выполнение, должны умереть. Все остальные должны обрабатываться без die.

Пример: проверка параметров вызываемого субмарина: не умереть при первой проблеме. Проверьте все остальные параметры, а затем решите остановиться, вернув что-то или предупредив, исправив ошибочные параметры и продолжив. Что делать в режиме тестирования или разработки. Но возможно die в производственном режиме. Пусть приложение решит это.

JPR (мой логин CPAN)

Привет из Зогеля, Германия

...