内容简介:本文提出了另一种可以构建你的程序的方法,它的灵感来自Elixir的管道宏’|>’,此方法就是使用我最近编写的小型我们来执行一个小实践任务,它将演示这种铁路方式的函数式编程方法。考虑一下我们使用Erlang构建POP3电子邮件客户端的情况。 我们的目标是实现与POP服务器建立连接的控制流程。
本文提出了另一种可以构建你的程序的方法,它的灵感来自Elixir的管道宏’|>’,此方法就是使用我最近编写的小型 epipe 库,而不用令人可怕的parse transforms。 Epipe本身的灵感来源于Scott Wlaschin发表的 这篇文章 。
准备开始
我们来执行一个小实践任务,它将演示这种铁路方式的函数式编程方法。
考虑一下我们使用Erlang构建POP3电子邮件客户端的情况。 我们的目标是实现与POP服务器建立连接的控制流程。
下图说明完成此操作所需的步骤:
首先,让我们写一个函数来实现建立连接的功能:
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).
上述代码非常漂亮,仅仅四行就完成了我们想要的功能。但是等等……上面的实现是非常完美的场景。 显然我们需要添加一些错误处理来应对边界条件的场景 :( 。我的意思是,“可能会出错的地方”?
增加错误处理
让我们总结如下图中所有可能的边界情况:
我们增加错误处理的代码,然后看看代码变成什么样子!
剧透:下面的例子很简单,可以通过将操作分成单独的函数来美化,但是嵌套的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代码:
switch_component(Input)-> case some_action() of {ok, Response} -> {ok, Response}; % Green track Error -> {error, Error} % Red track end.
一旦为所有需要的操作创建了两种方式( ok / error )切换分支,就可以像在铁路上一样优雅地组合它们:
所以,简单来说,确切的情况是:
在成功的情况下,所有功能(“铁路道岔”)都按顺序执行,我们沿着“成功轨道”行进。否则,我们的列车将切换到“错误轨道”,并沿该路线行驶,绕过所有其他步骤:
用“铁路”的方法来设计更好的错误处理
我们发布了一个很小的 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
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 代码
RGB转16进制工具
RGB HEX 互转工具