Erlang错误处理哲学 - 案例与投掷

时间:2011-07-28 13:22:19

标签: exception-handling error-handling erlang

我正在Erlang中编写REST服务,需要先验证接收到的数据,然后再将其传递给其他内部函数进行进一步处理;为了做到这一点,我目前正在使用嵌套的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,
...

我意识到这有点难看,但这样我就可以提供详细的错误信息。另外,我不认为通常的使其崩溃哲学在这里适用 - 我不希望我的REST服务崩溃并且每次有人抛出无效参数时都会重新启动。

但是,我正在考虑放弃所有cases支持保护try/catch阻止任何badmatch错误的问题 - 这会有效吗?

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

%% somewhere else
catch fun().

3 个答案:

答案 0 :(得分:6)

由于您希望实现的是错误报告,因此您应该围绕执行操作和报告结果来构建事物。也许是这样的:


  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])).

答案 1 :(得分:2)

我在开发创建用户的应用程序时遇到了这个问题。

我首先提出这样的解决方案:

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().

这样,您就可以在Error上获得完整的错误堆栈。

答案 2 :(得分:2)

在编写自己的REST服务时,我遇到了完全相同的问题。

让我们从哲学开始:

我喜欢把我的应用程序想象成一个盒子。在盒子的内侧是我建造的所有部件并且可以直接控制。如果有什么东西在这里破坏,这是我的错,它应该崩溃,我应该在错误日志中读到它。在框的边缘是与外界的所有连接点 - 这些都不值得信任。我避免在内部部件中进行异常处理,并根据外边缘的需要使用它。

我曾参与的类似项目:

我通常会对用户输入进行十几次检查。如果看起来不好,我会记录它并向用户返回错误。拥有堆栈跟踪对我来说并不是特别有意义 - 如果用户忘记了参数,我的代码中没有任何东西可以追捕并修复。我宁愿看到一个文本日志,上面写着:“在17:35,用户X访问路径Y但是缺少参数Z”。

我将检查组织成返回ok{error, string()}的函数。 main函数只是迭代检查并返回ok如果它们全部通过,否则返回第一个错误,然后记录。在我的检查功能中,我根据需要使用异常处理,因为我不可能考虑用户可以搞砸的所有方式。

正如我的同事所建议的那样,你也可以让每一个检查抛出一个异常,而不是使用一个元组。

至于您的实现,我认为如果您只进行一次检查,那么使用单个异常处理程序的想法是很好的。如果您最终需要更多检查,您可能希望实现我所描述的内容,以便您可以进行更具体的日志记录。