Подтвердить что ты не робот

Что сломалось об исключениях в Perl?

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

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

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

eval { 
    do_something_that_might_barf();
};

if ( my $err = [email protected] ) { 
    # handle $err here
}

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

Основная проблема, которую я вижу в системе исключений Perl, - использование специального глобального [email protected] для хранения текущей ошибки, а не выделенного механизма catch -типа, который может быть более безопасным, с точки зрения перспективы, хотя я "никогда не сталкивался с какими-либо проблемами при запуске [email protected].

4b9b3361

Ответ 1

Некоторые классы исключений, например. Error, не может обрабатывать управление потоком из блоков 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.

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

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

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

  • use Error qw(:try) был опущен, поэтому блок try {}... будет ошибочным (вы можете увидеть или не увидеть предупреждение, в зависимости от остальной части вашего кода)
  • отсутствует точка с запятой после блока 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;
};

Ответ 2

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 и [email protected] являются движущимися частями, которые вам не нужно беспокоиться.

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

Ответ 3

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

eval { some code here };
if( [email protected] ) {  handle exception here };

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

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

Это защищает от отсутствия исключения из-за того, что [email protected] является сбитым, но он по-прежнему уязвим для потери значения [email protected].

Чтобы убедиться, что вы не создаете исключение, когда вы делаете свой eval, вам нужно локализовать [email protected];

eval { local [email protected]; some code here; 1 } or do { handle exception here };

Это все тонкое разрушение, и для предотвращения требуется много эзотерического шаблона.

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

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

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

Ответ 4

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

Решение заключалось в проверке состояния переменной $^S или $EXCEPTIONS_BEING_CAUGHT:

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

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

    (Полностью false. die $object работает так же, как die $string.)

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

    (False. perl -MCarp::Always и наслаждайтесь.)

  • это прикладом-уродливым клодом.

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

Ответ 7

С Perl комбинируются языковые и пользовательские исключения: оба установлены [email protected]. В других языках исключения языка отличаются от пользовательских исключений и создают полностью отдельный поток.

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

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

if ([email protected] and [email protected]>isa('My::Exception'))

поймает оба.

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

elsif ([email protected])
    {
    print "Other Error [email protected]\n";
    exit;
    }

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

Ответ 8

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

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

JPR (мой адрес CPAN)

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