Как обрабатывать исключения на процедурном языке?

Как выполнить обработку исключений на процедурном языке, таком как C или Perl? (Я знаю, что Perl также занимается объектно-ориентированным программированием.) Как лучше всего обрабатывать исключения в процедурном коде Perl?


person someguy    schedule 15.09.2009    source источник
comment
Программирование на Perl достаточно процедурное программирование, но это не делает его процедурным языком. Это мультипарадигмальный язык. Он допускает объектно-ориентированное программирование, но также заимствует из функциональных языков программирования. Единственное, что я не думаю, что это могло бы обойтись без большой работы, - это логическое программирование (например, Prolog). Где C — это процедурный язык, и самое близкое, что вы можете получить, — это практиковать и применять методологию абстрактных типов данных.   -  person Axeman    schedule 15.09.2009
comment
@Axeman, см. дистрибутив AI::Prolog на CPAN: search.cpan.org/~jjore/AI-Prolog/lib/AI/Prolog.pm   -  person friedo    schedule 16.09.2009


Ответы (6)


В Perl 5 обработка исключений выполняется с помощью eval и die. Вы просто оцениваете тело кода, и если оно умирает, вы можете проверить $@ на наличие ошибки. Это не так просто, если вы хотите сделать это правильно, поэтому существуют различные модули try/catch. Вас может заинтересовать Try::Tiny, который не имеет зависимостей и описывает все ловушки, с которыми вам приходится сталкиваться при использовании наивной обработки исключений eval. (См. также эту запись в блоге автора Try::Tiny.)

person zoul    schedule 15.09.2009
comment
eval имеет проблемы с производительностью, которые могут сделать его чрезмерно дорогим для крупномасштабных систем. - person DVK; 15.09.2009
comment
@DVK, ты говоришь об оценке строк или блоков? Проблемы с eval строк хорошо известны, но обработка исключений с помощью eval обычно использует блочное eval. Если вы говорите, что с оценкой блоков возникают проблемы с производительностью, можете ли вы предоставить источник или демонстрацию проблемы? - person daotoad; 15.09.2009
comment
@zoul, я думаю, у вас ошибка: 'with eval' => &with_eval нужен `` перед подименем. В моей системе perl 5.8 два слоя eval дают 44% штраф по сравнению с отсутствием eval, а один слой дает 24% штраф. Однако я думаю, что это может быть несправедливым критерием. Обертывание запроса к базе данных или доступа к файлу в eval не будет иметь таких накладных расходов, поскольку эти операции вряд ли будут привязаны к процессору. Я думаю, что урок, который нужно усвоить, заключается в том, что вы можете зайти слишком далеко со своими оценками. Оберните куски кода разумного размера, и вы не будете тратить все свое время на создание и удаление контекстов eval. - person daotoad; 16.09.2009
comment
@daotoad: О боже. Обратная косая черта имеет большое значение, не так ли? :) Спасибо, исправлено. Сейчас разница в производительности около 1/3, но как вы уже писали, это не должно иметь большого значения. - person zoul; 16.09.2009

Вот пример того, как я делаю исключения в Perl (без использования одного из модулей Try):

use Carp;
use English qw( -no_match_vars );

do_something_needing_rollback_if_failed();
eval {
    do_something_dangerous();
} or do {
   # Exception was thrown by dangerous method
   # Save the error:
   my $error = $EVAL_ERROR;

   # Try to rollback
   eval { rollback(); }
     or do { confess qq{Couldn't rollback: $EVAL_ERROR. Original error $error}; }

   # Let's rethrow:
   confess qq{Rolled back! Error was $error};
}

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

person Todd Gardner    schedule 15.09.2009

Это скорее зависит от того, что вы подразумеваете под «обработкой исключений».

Некоторые ОС имеют механизмы обработки исключений — см. процедуры обработки исключений Windows или обработчики сигналов Linux (некоторые из них являются исключениями).

Если вы имеете в виду идиому в пользовательском коде, чтобы сигнализировать об ошибке и раскрутить стек, очищая все выделенные объекты, тогда код C не вызывает деструкторы для выделенных объектов, когда стек раскручивается (он просто освобождает память), так что это нормально в C для функций, возвращающих значение состояния, и для вызывающего кода, выполняющего любую необходимую очистку перед возвратом.

Я не очень хорошо знаю Perl, но поиск в Google «обработки исключений Perl» показывает, что он имеет как встроенные механизмы, эквивалентные try/catch, так и модули, которые обеспечивают обработку исключений в «стиле OO».

person Pete Kirkham    schedule 15.09.2009

В C были setjmp и longjump; по сути, setjmp() сохраняет текущий контекст в стеке, а longjmp() может вернуться к точке, сохраненной setjmp(). Запись в Википедии об этом прекрасно описывает детали.

person JustJeff    schedule 15.09.2009

В Perl, как обычно, есть несколько способов сделать это. Однако сообщество разработчиков в целом установило правильный способ делать большинство вещей.

Я сам становлюсь поклонником Exception::Base.

Если вам нужна более легкая реализация, просто используйте Carp.

Вы перехватываете исключения, используя конструкцию eval { ... }; if ($@) { ... }.

С помощью Exception::Base он предлагает вам способ абстрагироваться от конструкции if ($@) { ... }. Однако вам все равно нужно будет использовать eval { ... }.

person Jeremy Wall    schedule 15.09.2009

Традиционный подход состоит в том, чтобы... ну... не :)

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

int try_it() {
  if (!do_something(...)) {
     return TRYIT_FAILURE;
  }
}

void my_gui() {
  rc = try_it()
  if (rc == TRYIT_FAILURE) {
    message_box("failed when trying it.", MB_ABORT|MB_RETRY);
  }
  ...
}

Вы также можете сделать это в большой функции, используя вложенные ifs, если вы хотите что-то вроде конструкции try...except:

if (stage1()) {
   if (stage2()) {
       if(stage3()) {
           printf("success!\n");
       } else {
           // error handling for stage 3
       }
   } else {
       // error handling for stage 2
   }
} else { 
   // error handling for stage 1
}

И вы можете сделать то же самое с gotos, если вы немного злитесь.

Тем не менее, вы можете создавать настоящие исключения, по крайней мере, в C. C имеет два стандартных библиотечных вызова для такого рода вещей: setjmp и longjmp. С их помощью вы можете вернуться через несколько вызовов функций к заранее определенному месту, где, как вы знаете, произошло исключение (переход). Подробнее об этом см. Setjmp.h#exception_handling в Википедии.

Кажется, в Perl есть способ сделать это, хотя мне он кажется далеко не интуитивным. Опять же, я не пишу код на Perl :)

Часто задаваемые вопросы по Perl Q4.8

person Lee B    schedule 15.09.2009
comment
-1 Разработчики Perl наверняка используют исключения. Я бы сказал, что они более важны в динамическом языке, таком как perl, чем в таком языке, как java или C++; - person Jeremy Wall; 16.09.2009