用铁路的方法简化Erlang的case嵌套问题

栏目: Erlang · 发布时间: 6年前

内容简介:本文提出了另一种可以构建你的程序的方法,它的灵感来自Elixir的管道宏’|>’,此方法就是使用我最近编写的小型我们来执行一个小实践任务,它将演示这种铁路方式的函数式编程方法。考虑一下我们使用Erlang构建POP3电子邮件客户端的情况。 我们的目标是实现与POP服务器建立连接的控制流程。

本文提出了另一种可以构建你的程序的方法,它的灵感来自Elixir的管道宏’|>’,此方法就是使用我最近编写的小型 epipe 库,而不用令人可怕的parse transforms。 Epipe本身的灵感来源于Scott Wlaschin发表的 这篇文章

准备开始

我们来执行一个小实践任务,它将演示这种铁路方式的函数式编程方法。

考虑一下我们使用Erlang构建POP3电子邮件客户端的情况。 我们的目标是实现与POP服务器建立连接的控制流程。

下图说明完成此操作所需的步骤:

用铁路的方法简化Erlang的case嵌套问题

首先,让我们写一个函数来实现建立连接的功能:

connect(Addr, Port, ConnOptions, User, Password)->
    {ok, Socket} = ssl:connect(Addr, Port, ConnOptions),
    ok = receive_greetings(Socket),
    ok = send_user(Socket, User),
    ok = send_password(Socket, Password).

上述代码非常漂亮,仅仅四行就完成了我们想要的功能。但是等等……上面的实现是非常完美的场景。 显然我们需要添加一些错误处理来应对边界条件的场景 :( 。我的意思是,“可能会出错的地方”?

增加错误处理

让我们总结如下图中所有可能的边界情况:

用铁路的方法简化Erlang的case嵌套问题

我们增加错误处理的代码,然后看看代码变成什么样子!

剧透:下面的例子很简单,可以通过将操作分成单独的函数来美化,但是嵌套的case语句是不可避免的。

connect(Addr, Port, ConnOptions, User, Password)->
    case ssl:connect(Addr, Port, ConnOptions) of
        {ok, Socket} ->
            case receive_greetings(Socket) of
                ok ->
                    case send_user(Socket, User) of
                        ok ->
                            case send_password(Socket, Password) of
                                ok -> ok;
                                _Err -> error_logger:error_msg("Auth error")
                            end;
                        _Err ->
                            error_logger:error_msg("Unknown user")
                    end;
                Err -> error_logger:error_msg("Could not receive_greetings")
            end;
        _Error -> error_logger:error_msg("Could not connect")
    end.

现在我们添加了所有的错误处理代码。但是,代码的大小增加了400 %……可读性也相应降低了!

也许有一个更清晰的方式来实现这一点?

用“铁路”的方法来设计更好的错误处理(理论)

铁路 方法背后的想法是用铁路道岔作为模拟物来分解“每一步”功能块:

用铁路的方法简化Erlang的case嵌套问题

这种方法可以翻译为如下的Erlang代码:

switch_component(Input)->
    case some_action() of
        {ok, Response} -> {ok, Response}; % Green track
        Error          -> {error, Error}  % Red track
    end.

一旦为所有需要的操作创建了两种方式( ok / error )切换分支,就可以像在铁路上一样优雅地组合它们:

用铁路的方法简化Erlang的case嵌套问题

所以,简单来说,确切的情况是:

在成功的情况下,所有功能(“铁路道岔”)都按顺序执行,我们沿着“成功轨道”行进。否则,我们的列车将切换到“错误轨道”,并沿该路线行驶,绕过所有其他步骤:

用铁路的方法简化Erlang的case嵌套问题

用“铁路”的方法来设计更好的错误处理

我们发布了一个很小的 Erlang库 ,它简化了Erlang的“铁路道岔”分解方式。那么,考虑上面的例子,让我们来看一下如何使用Epipe实现我们的用例:

-record(connection, {
    socket,
    user,
    addr,
    port,
    passwd
}).

connect(Addr, Port, User, Password)->
    Connection = #connection{
        user = User,
        passwd = Password,
        add = Addr,
        port = Port
    },
    % 定义要遵循的铁路道岔列表
    ConnectionSteps = [
        {get_socket, fun get_socket/1},
        {recv_greetings, fun recv_greetings/1},
        {send_user, fun send_user/1},
        {send_passwd, fun send_passwd/1}
    ],
    % 通过道岔运行
    case epipe:run(ConnectionSteps, Connection) of
        {error, Step, Reason, _State} ->
            error_logger:error_msg("Failed to establish connection. Reason: ~p", [Step]),
            {error, Reason};
        {ok, _Conn} = Success -> Success
    end.


% 构建功能块。注意:每一个函数可以返回 {ok, Connection} 或 {error, Reason}

get_socket(Connection)->
    case ssl:connect(Addr, Port, ExtraOptions) of
        {ok, Socket} -> {ok, Connection#connection{socket = Socket}};
        Error        -> {error, Error}
    end.

recv_greetings(Connection)->
    case recv(Connection) of
        {ok, <<"+OK", _Rest/binary>>}   -> {ok, Connection};
        {ok, <<"-ERR ", Error/binary>>} -> {error, Error};
        Err                             -> {error, Err}
    end.

send_user(Connection = #connection{user = User})->
    Msg = list_to_binary(User),
    send(Connection, <<"USER ", Msg/binary>>),

    case recv(Connection) of
        {ok, <<"+OK", _Rest/binary>>}   -> {ok, Connection};
        {ok, <<"-ERR ", Error/binary>>} -> {error, Error};
        Err                             -> {error, Err}
    end.

send_passwd(Connection = #connection{passwd = Passwd})->
    Msg = list_to_binary(Passwd),
    send(Connection, <<"PASS ", Msg/binary>>),

    case recv(Connection) of
        {ok, <<"+OK", _Rest/binary>>}   -> {ok, Connection};
        {ok, <<"-ERR ", Error/binary>>} -> {error, Error};
        Err                             -> {error, Err}
    end.

总结

与嵌套的case语句实现相比,最终的代码在代码行方面不会更小,但它确实更具可读性,使得调试和支持变得更加容易。

如果你希望看到真实的实现案例,请查看使用铁路方法执行的 重构例子

原文链接: https://www.erlang-solutions.com/blog/railway-oriented-development-with-erlang.html


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Traction: A Startup Guide to Getting Customers

Traction: A Startup Guide to Getting Customers

Gabriel Weinberg、Justin Mares / S-curves Publishing / 2014-8-25 / USD 14.99

Most startups end in failure. Almost every failed startup has a product. What failed startups don't have is traction -- real customer growth. This book introduces startup founders and employees to......一起来看看 《Traction: A Startup Guide to Getting Customers》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具