Эрланг: Что является более эффективным в этом сценарии - используя операторы try catch или case? - PullRequest
2 голосов
/ 02 декабря 2009

Скажем, у меня есть некоторая функция fn1 () в Erlang, которая возвращает {ok, Result}, если функция была выполнена успешно, и {error, "ErrorReason"}, если произошла ошибка.

Теперь в другой функции fn2 () я вызываю fn1 (), и мне нужно проверить результат fn1 и продолжить, только если он равен {ok, Result}.

Я подумал, я могу сделать это, используя любой случай или попытаться поймать. Но Эффективность - моя главная задача, и я хотел бы знать, какой из двух методов ниже эффективнее:

try-catch Метод

fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

case Метод

fn2() ->
   Res = fn1(),
   case Res of
      {ok, Result} -> 
         %Do something with Result
         ok;
      {error, Reason} ->
         Reason
   end.

Ответы [ 5 ]

10 голосов
/ 03 декабря 2009

Вы действительно хотите попытаться избежать попытки поймать, как чума. Это очень необычная идиома в Erlang - на самом деле используется только в нескольких особых случаях:

  • где вы проверяете предоставленные пользователем вход и у вас нет никаких гарантий, что это будет «правильно»
  • где у вас есть то, что есть глубоко вложенный и стоимость развернув его в случае ошибки это слишком дорого
    • как транзакции mensia
    • или в парсере / лексере

Try / catch имеет важное значение в таких языках, как C ++, где приложение нестабильно в присутствии или в ошибках, но Erlang стабилен в таких обстоятельствах - процесс завершается сбоем, но не останавливает систему.

Вы должны запрограммировать «счастливый путь», сопоставить возвращаемые значения, и если приложение отклоняется от ожидаемого, тогда допустит сбой . Авария говорит вам, что у вас есть проблема, и просит ее исправить.

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

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

8 голосов
/ 02 декабря 2009

Метод case будет более эффективным, поскольку он просто сопоставляется с шаблоном и не требует построения стека вызовов и прочего.

В обоих примерах вы собираетесь обрабатывать «ошибку» локально, поэтому нет смысла в попытке catch. То, что вы можете увидеть, иногда выглядит примерно так:

fn2() ->
  {ok, Result} = fn1(),
  %Do stuff with Result
  ok.

Здесь предполагается, что вы заставите fn2 () сгенерировать плохой матч, если fn1 () не вернется нормально. Вы позволяете кому-то другому «выше» справиться с проблемой. Например. это может убить ваш процесс и заставить вашего руководителя создать новый.

4 голосов
/ 02 декабря 2009

Вы должны всегда измерять, чтобы найти такие вещи.

Ваш код также не делает то, что вы думаете.

-module(glurk).
-compile(export_all).

fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

fn1() ->
    {error, a}.

Попробуйте это:

 c(glurk).   
./glurk.erl:6: Warning: variable 'Result' is unused
{ok,glurk}
16> glurk:fn2().
{error,{{badmatch,{error,a}},
        [{glurk,fn2,0},
         {erl_eval,do_apply,5},
         {shell,exprs,6},
         {shell,eval_exprs,6},
         {shell,eval_loop,3}]}}

Это потому, что fn1 не вызвал исключение он получил нормальное возвращаемое значение {error, a}, которое не соответствует шаблону с {ok, Result}

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

fn1(....) ->
     ...
     %% success case
     Val;

     %% failure case
     throw(...) | exit(...) | error(...)

Нельзя просто накачать одну и ту же функцию на fn1 и fn2.

Если бы у вас был случай, когда вызываемая функция должна была выйти из глубокой рекурсии тогда первый метод будет более эффективным, чем второй - так как вы могли бы немедленно выйдите из глубокой рекурсии, сказав throw (...).

Таким образом, ответ зависит от природы функции, которую вы вызываете.

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

Я бы написал

{ok,Result} = ...

На самом деле ваш первый код содержит более тонкую ошибку

 fn2() ->
   try
      {ok, Result} = fn1(),
      %Do something with Result
      ok
   catch
      throw:Term -> Term;
      exit:Reason -> {exit, Reason};
      error:Reason -> {error,{Reason,erlang:get_stacktrace()}}
   end.

Подумай об этом. Пойманные ошибки сами по себе не обрабатывают ошибку они просто возвращают кортежи типа {выход, причина} или {ошибка, причина}, это означает, что следующий уровень вверх (т.е. вызывающий fn2) также придется возиться с проверкой возвращается ошибка - если это будет повторяться на всех уровнях, код будет беспорядочным.

Способ "erlang" состоит в том, чтобы сделать один пробный бросок вверху программы и просто завершить его внезапно с выходом (почему), если возникает ошибка.

На самом деле часто вам даже не следует этого делать - вы должны связать свой процесс с другим процессом. тогда нарушающий процесс умрет и «другие процессы исправят ошибку».

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

2 голосов
/ 04 декабря 2009

В этом случае, независимо от того, что является более эффективным, вам определенно следует использовать альтернативу case, поскольку она более кратко описывает происходящее. Ваш fn1() здесь возвращает значение, указывающее, есть ли успешное значение или ошибка. В версии case вы напрямую сравниваете с этим, в то время как в версии try вы сравниваете со значением успеха, которое генерирует ошибку, если ошибка была возвращена. Это излишне запутано и скрывает то, что происходит, поэтому это плохой стиль программирования, и его следует избегать.

И, как уже указывал Гордон, имея try, он обнаружит больше ошибок, чем вы, вероятно, намереваетесь, и может маскировать другие настоящие ошибки, которые вы должны увидеть.

case также будет быстрее здесь, но разница, вероятно, небольшая. Четкость, лаконичность и хороший стиль программирования гораздо важнее!

0 голосов
/ 09 апреля 2015

Дело всегда будет более эффективным, но разница невелика и важнее, что имеет больше смысла в вашем конкретном случае. Особенно тот подход, который производит более понятный код. Смотрите этот тест:

%% Results:
%% 7> errors:run_normal(100).
%% {9,ok}
%% 8> errors:run_normal(1000).
%% {107,ok}
%% 9> errors:run_normal(10000).
%% {856,ok}
%% 10> errors:run_normal(1000, 10).
%% {263,ok}
%% 11> errors:run_wcatch(10000).
%% {2379,ok}
%% 12> errors:run_wcatch(1000, 10).
%% {401,ok}
%% 18> errors:run_normal_cplx(10000, 50).
%% {7910,ok}
%% 19> errors:run_wcatch_cplx(10000, 50).
%% {10222,ok}
-module(errors).

-compile(export_all).

run_normal(Iterations) ->
    get_result(Iterations, fun() -> normal() end).

run_normal(Iterations, Level) ->
    get_result(Iterations, fun() -> deepnormal(Level) end).

run_wcatch(Iterations) ->
    get_result(Iterations, fun() -> wcatch() end).

run_wcatch(Iterations, Level) ->
    get_result(Iterations, fun() -> deepwcatch(Level) end).

run_normal_cplx(Iterations) ->
    get_result(Iterations, fun() -> normal_complex() end).

run_normal_cplx(Iterations, Level) ->
    get_result(Iterations, fun() -> deepnormal_complex(Level) end).

run_wcatch_cplx(Iterations) ->
    get_result(Iterations, fun() -> wcatch_complex() end).

run_wcatch_cplx(Iterations, Level) ->
    get_result(Iterations, fun() -> deepwcatch_complex(Level) end).

%%------------------------------------------------------------------------------

get_result(Iterations, Fun) ->
    timer:tc(fun() -> run(Iterations, Fun) end).

run(0, _Fun) ->
    ok;
run(Iterations, Fun) ->
    Fun(),
    run(Iterations - 1, Fun).

%%------------------------------------------------------------------------------

normal() ->
    case foo(atom) of
        {ok, atom} -> ok;
        {error, atom} -> ok
    end.

normal_complex() ->
    case foo_cplx() of
        {ok, Res} -> Res;
        {error, Res} -> Res
    end.

deepnormal(Level) ->
    case deepfoo(atom, Level) of
        {ok, atom} -> ok;
        {error, atom} -> ok
    end.

deepnormal_complex(Level) ->
    case deepfoo_cplx(Level) of
        {ok, Res} -> Res;
        {error, Res} -> Res
    end.

wcatch() ->
    try
        {ok, atom} = foothrow(atom)
    catch
        throw:{error, atom} -> ok
    end.

wcatch_complex() ->
    try
        {ok, _Res} = foothrow_cplx()
    catch
        throw:{error, Res} -> Res
    end.

deepwcatch(Level) ->
    try
        {ok, atom} = deepfoothrow(atom, Level)
    catch
        throw:{error, atom} -> ok
    end.

deepwcatch_complex(Level) ->
    try
        {ok, _Res} = deepfoothrow_cplx(Level)
    catch
        throw:{error, Res} -> Res
    end.

%%------------------------------------------------------------------------------

foo(Arg) -> {error, Arg}.

foothrow(Arg) -> throw({error, Arg}).

deepfoo(Arg, 0) -> {error, Arg};
deepfoo(Arg, Level) -> deepfoo(Arg, Level - 1).

deepfoothrow(Arg, 0) -> throw({error, Arg});
deepfoothrow(Arg, Level) -> deepfoothrow(Arg, Level - 1).


foo_cplx() -> {error, {<<"Some">>, "Complex", data}}.

foothrow_cplx() -> throw({error, {<<"Some">>, "Complex", data}}).

deepfoo_cplx(0) -> {error, {<<"Some">>, "Complex", data}};
deepfoo_cplx(Level) -> deepfoo_cplx(Level - 1).

deepfoothrow_cplx(0) -> throw({error, {<<"Some">>, "Complex", data}});
deepfoothrow_cplx(Level) -> deepfoothrow_cplx(Level - 1).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...