Erlang философия обработки ошибок - случай против броска - PullRequest
6 голосов
/ 28 июля 2011

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

case all_args_defined(Args) of
    true ->
        ActionSuccess = action(Args),

        case ActionSuccess of
            {ok, _} -> ...;
            {fail, reason} -> {fail, reason}
        end,
    _ ->
        {fail, "args not defined"}
end,
...

Я понимаю, что это довольно уродливо, но таким образом я могу предоставить подробные сообщения об ошибках.Кроме того, я не думаю, что здесь применима обычная философия make it crash - я не хочу, чтобы моя служба REST аварийно завершала работу и перезапускалась каждый раз, когда кто-то выдвигал недопустимые аргументы.* Однако я собираюсь отказаться от всех этих cases в пользу зонтичного try/catch блока, перехватывающего любые badmatch ошибки - сработает ли это?

fun() ->
    true = all_args_defined(Args),
    {ok, _} = action(Args).

%% somewhere else
catch fun().

Ответы [ 3 ]

6 голосов
/ 28 июля 2011

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


  execute(Action, Args) ->
    try
      check_args(Args),
      Result = action(Action, Args),
      send_result(Result)
    catch
      throw:{fail, Reason} ->
        report_error(Reason);
      ExceptionClass:Term ->
        %% catch-all for all other unexpected exceptions
        Trace = erlang:get_stacktrace(),
        report_error({crash, ExceptionClass, Term, Trace})
    end.

  %% all of these throw {fail, Reason} if they detect something fishy
  %% and otherwise they return some value as result (or just crash)
  action(foo, [X1, X2]) -> ...;
  action(foo, Args) -> throw({fail, {bad_arity, foo, 2, Args}});
  action(...) -> ...

  %% this handles the formatting of all possible errors 
  report_error({bad_arity, Action, Arity, Args}) ->
    send_error(io_lib:format("wrong number of arguments for ~w: "
                             "expected ~w, but got ~w",
                             [Action, Arity, length(Args)]));
  report_error(...) -> ...;
  report_error({crash, Class, Term, Trace}) ->
    send_error(io_lib:format("internal error: "
                             "~w:~w~nstacktrace:~n~p~n",
                             [Class, Term, Trace])).
2 голосов
/ 28 июля 2011

Я столкнулся с точно таким же вопросом при написании своих собственных служб REST.

Давайте начнем с философии:

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

В аналогичных проектах, над которыми я работал:

У меня обычно около дюжины проверок пользовательского ввода.Если что-то выглядит плохо, я регистрирую это и возвращаю ошибку пользователю.Наличие трассировки стека для меня не имеет особого значения - если пользователь забыл параметр, в моем коде нет ничего, что можно было бы выследить и исправить.Я предпочел бы увидеть текстовый журнал, который говорит что-то вроде: «в 17:35 пользователь X получил доступ к пути Y, но пропустил параметр Z».

Я организую свои проверки в функции, которые возвращают ok или {error, string()}.Основная функция просто перебирает проверки и возвращает ok, если все они прошли, в противном случае она возвращает первую ошибку, которая затем регистрируется.Внутри моих функций проверки я использую обработку исключений по мере необходимости, потому что я не могу рассмотреть все способы, которыми пользователи могут испортить.

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

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

2 голосов
/ 28 июля 2011

У меня была эта проблема при разработке приложения для создания пользователей.

Сначала я пришел с таким решением:

insert() ->
    try
        check_1(), % the check functions throw an exception on error.
        check_2(),
        check_3(),
        do_insert()
    catch
        throw:Error1 ->
            handle_error_1();
        throw:Error2 ->
            handle_error_2();
        _:Error ->
            internal_error()
    end.

Проблема с этим решением заключается в том, что вы теряететрассировка стека с помощью блока try ... catch.Вместо этого, лучшим решением будет:

insert() ->
    case catch execute() of
        ok -> all_ok;
        {FuncName, Error} ->
            handle_error(FuncName, Error);
        {'EXIT', Error} ->
            internal_error(Error)
    end.

execute() ->
    check_1(), % the check functions throw an exception on error.
    check_2(),
    check_3(),
    do_insert().

Таким образом, у вас будет полный стек ошибок при ошибке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...