内容简介:本文提出了另一种可以构建你的程序的方法,它的灵感来自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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Script深度剖析
卢云鹏、沈维伦、Don Gosselin、李筱青 / 卢云鹏、沈维伦、李筱青 / 北京大学出版社 / 2004-10-1 / 49.0
本书适合于大中专院计算机相关专业作为教材,也是JavaScript初学者以及JavaScript爱好者的理想参考用书。书中详细介绍了基本的JavaScript程序设计原理以及实现它们的语法,内容包括JavaScript简介,变理、函数、对角和事件,数据类型、运算符,结构化逻辑控制结构和语句,窗口和框架,表单,动态HTML和动画,cookie和安全性,服务器端 JavaScript,数据库连接,使用......一起来看看 《Java Script深度剖析》 这本书的介绍吧!