假设我在Erlang中有一些函数fn1(),如果函数执行成功则返回{ok, Result}
,如果有错误则返回{error, "ErrorReason"}
。
现在在另一个函数fn2()中我调用fn1()并且我需要检查fn1的结果并且仅在它是{ok, Result}
时继续。
我想,我可以使用case或try catch来做到这一点。但效率是我的主要关注点,我想知道以下两种方法中的哪一种更有效:
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.
答案 0 :(得分:10)
你真的想尝试避免像瘟疫一样的尝试/捕获。在Erlang中这是一个非常罕见的习语 - 实际上只用于几个特殊情况:
尝试/ catch在C ++这样的语言中是必不可少的,其中应用程序在存在或错误时不稳定,但Erlang在这些情况下是稳定的 - 进程崩溃但不会导致系统崩溃。
您应该编写快乐路径,匹配返回值,如果应用程序偏离您的预期,那么 让它崩溃 。崩溃告诉您有问题并告诉您修复它。
try / catch的问题在于它可以简单地掩盖问题,甚至更糟糕的是,将最终的崩溃从它应该发生的地方移开(在你已经包装的表达式中)并使其出现在其他地方 - 编程逻辑期望它已经成功=这使得调试变得更加困难。
编写快乐路径并让它崩溃是第一次这样做时非常令人不安,感觉就像外出时没穿衣服,但实际上你很快就习惯了它:)答案 1 :(得分:8)
case方法会更有效,因为它只是模式匹配,并且不涉及构建调用堆栈和东西。
在这两个示例中,您将在本地处理“错误”,因此try catch中没有任何意义。您可能会看到以下内容:
fn2() ->
{ok, Result} = fn1(),
%Do stuff with Result
ok.
这里的意图是你使fn2()抛出坏匹配,如果fn1()没有返回ok。你让别人“上面”处理这个问题。例如。这可能会扼杀你的过程,并让你的主管创建一个新的。
答案 2 :(得分:4)
你应该总是测量以找到这样的东西。
您的代码也没有按照您的想法执行。
-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.
想一想。捕获的错误情况本身不会处理错误 他们只返回像{exit,Reason}或{error,Reason}这样的元组,这意味着 下一层(即fn2的调用者)也将不得不乱七八糟地检查 错误返回 - 如果在所有级别重复此代码将是一团糟。
“erlang”方式是在程序顶部有一个try-catch,然后终止 如果发生错误,突然退出(为什么)。
事实上,您通常不应该这样做 - 您应该将您的流程链接到另一个流程 那么违规的过程将会消亡,“其他过程将解决错误”。
异常传播到调用堆栈并飞到链接的进程 治疗。所以我们有两种类型的进程 - 没有内置错误处理的进程 和只进行错误处理的进程。
答案 3 :(得分:2)
在这种情况下,无论什么效率更高,您都应该使用case
替代方案,因为它更简洁地描述了正在发生的事情。您的fn1()
此处返回一个值,指示是否存在成功值或错误。在case
版本中,您直接与此匹配,而在try
版本中,您与成功值匹配,如果返回错误,则会生成错误。它不必要地复杂化并隐藏正在发生的事情,因此编程风格很差,应该避免使用。
正如戈登已经指出有一个try
会有更多的错误,可能会掩盖你应该看到的其他真正的错误。
case
在这里也会更快,但差异可能很小。清晰,简洁和良好的编程风格更为重要!
答案 4 :(得分:0)
案例总是会更有效率,但差异很小,而且在您的特定情况下更有意义。特别是,哪种方法产生更易理解的代码。见这个基准:
%% 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).