Erlang:成功案例和错误处理作为交易

时间:2016-11-17 20:05:36

标签: erlang

(我想引用另一个问题作为参考: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块中的成功步骤是“解开"”

1 个答案:

答案 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/0cleanup_database/0cleanup_file/0

当然,这可能以不同的方式编程,而不是通过重新抛出异常,但通过例如返回{ok, Result}{error, Reason}。它只是实施细节。