(我想引用另一个问题作为参考:How do I elegantly check many conditions in Erlang?)
"成功案例代码的通用形式与错误处理分开"似乎是:
try
ok = do_x(),
...
ok = do_y()
catch
error:{badmatch, x_failure} -> do_something1();
...
error:{badmatch, y_failure} -> do_something2();
当try子句中的函数执行具有副作用的事情时,如何使用此模式,例如写入文件,发送网络数据包,将行写入数据库等等?是否存在"回滚的通用模式"在catch子句中?例如:
try
%How to make this whole block a transaction?
ok = step_1_write_file(),
ok = step_2_write_database(),
ok = step_3_send_packet(),
...
catch
error:{badmatch, database_failure} -> clean_up_step_1() %delete file?
error:{badmatch, sendpacket_failure} -> clean_up_step_1_and_2() %??
似乎错误处理变得繁重,需要执行的清理依赖于失败的try
块中的步骤。
是否存在将此视为事务的通用编程模式,而在失败子句之前的try块中的成功步骤是“解开"”
答案 0 :(得分:3)
我个人学会了通过传递一个“验证器”列表来编写这样的算法。并且,可选地,'终结者'一些通用的迭代函数。
因此,您的案例可能编程如下:
noop() -> ok.
transaction([{Fun, Rollback} | Rest]) ->
try
{ok, Result} = Fun(),
[Result | transaction(Rest)]
catch Type:Reason ->
Rollback(),
erlang:raise(Type, Reason, erlang:get_stacktrace())
end;
transaction([Fun | Rest]) ->
% not every action require cleanup on error
transaction([{Fun, fun noop/0} | Rest]);
transaction([]) -> [].
main() ->
Actions = [
{fun write_file/0, fun cleanup_file/0},
{fun write_database/0, fun cleanup_database/0},
fun do_safe_thing/0,
{fun send_packet/0, fun cancel_send_packet/0},
],
transaction(Actions).
正如您所看到的,由于此列表使用正文递归进行评估,因此通过此列表的迭代将形成一个调用堆栈,如果在某个步骤中这些函数中的一个将失败,则将展开堆栈并调用每个清理函数以相反的顺序。
例如,如果do_safe_ting/0
落下,则将按此顺序调用清除函数noop/0
,cleanup_database/0
和cleanup_file/0
。
当然,这可能以不同的方式编程,而不是通过重新抛出异常,但通过例如返回{ok, Result}
和{error, Reason}
。它只是实施细节。