【问题标题】:Erlang error handling philosophy - case vs throwErlang 错误处理哲学 - 案例与抛出
【发布时间】:2011-10-15 03:16:37
【问题描述】:

我正在用 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().

【问题讨论】:

  • 只是对“让它崩溃”哲学的评论:如果您编写的代码本身无法对错误做任何明智的事情(例如您个人操作的代码),您应该为成功案例,如果有问题就让它崩溃。这使代码保持简短和集中。然而,在某些时候,您会想要处理错误——比如在主管中,或者在您的 REST 接收-执行-回复循环中。然后,您使用链接或 try/catch 进行设置,以便将错误传播到该点并仅在那里处理。
  • 是的,这是有道理的——我只是被其他…跨度>

标签: exception-handling error-handling erlang


【解决方案1】:

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


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

【讨论】:

  • 匹配异常元组的好主意!但是,您将如何处理来自gen_server 的投掷?例如,gen_server 中的find/1 函数由于无效的搜索词而引发异常会破坏gen_server 状态,对吗?这是否可以通过调用站点上的模式匹配并捕获 badmatch 来更好地处理?
  • 您可以在每个 handle_cast/handle_call/handle_info 回调中放置相同类型的伞式 try/catch,这样服务器就不会崩溃,除非发生真正意外的事情,在所有其他情况下,它只会发送一个回复,说明发生了错误(或记录它发生的错误,用于强制转换)。
【解决方案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 上获得完整的错误堆栈。

【讨论】:

  • 我认为 RichardC 的解决方案更加简化了这一点:)
  • 第一个版本最简单,最能满足原问题。这个想法不是生成错误,而是返回一个合适的错误值,我认为堆栈跟踪并不重要。如果您想生成不同的错误,但您可以使用erlang:get_stacktrace()
【解决方案3】:

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

让我们从哲学开始:

我喜欢把我的应用程序想象成一个盒子。盒子里面是我制造的所有部件,可以直接控制。如果这里有问题,那是我的错,它应该崩溃,我应该在错误日志中阅读它。盒子的边缘是与外界的所有连接点——这些都是不可信的。我避免在内部部分进行异常处理,并根据需要将其用于外部边缘。

在我参与过的类似项目中:

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

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

按照我的同事的建议,您也可以让每个检查都抛出异常,而不是使用元组。

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

【讨论】:

  • RichardC 的模式匹配异常代码是否适用于您的情况? IE。如果您将{fail, ...} 从您的检查中扔掉并在单个伞形(模式匹配)catch 块中报告错误怎么办?使用返回值而不是异常有什么好处?
  • 我认为,使用单个雨伞捕获并让每次检查都可能抛出 {fail, Reason} 在功能上与我提到的方法相同。您可能想尝试一下,看看哪个对您来说更具可读性。我想传达的主要观点是,如果您有多个检查,您可能希望它们以人类可读的方式登录 - 例如对每种类型的错误都有一条消息并将其写入文本文件(而不是错误日志中的堆栈跟踪)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-08
  • 1970-01-01
  • 1970-01-01
  • 2016-06-17
  • 2012-12-11
  • 2011-04-07
  • 2018-01-12
相关资源
最近更新 更多