erlang游戏开发
由Oleg Tarasenko
该博客建议了一种替代方法,您可以在受Elixir管道宏'|>启发的情况下构建程序,而无需使用我最近编写的微小Epipe库的可怕的解析转换。 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%……可读性也相应下降。 哎哟!
也许有一种更清洁的方式来实现这一目标?
使用“铁路”方法(理论)进行更好的错误处理设计 铁路方法的思想是使用铁路开关作为模拟来分解“阶梯式”功能块:
*图片来源:Scott Wlaschin 可以将其转换为以下Erlang代码:
switch_component(Input) -> case some_action() of {ok, Response} -> {ok, Response}; % Green track Error -> {error, Error} % Red track end.
一旦为所有必需的操作创建了两种(正常/错误)开关,您就可以像在铁路上完成的那样将它们组合起来:
*图片来源:Scott Wlaschin 因此,回顾一下,实际发生的是:
在成功方案的情况下,所有功能(“铁路开关”)将按顺序执行,我们沿着“成功轨道”行进。 否则,我们的火车将切换到“错误轨道”,并沿该路线行驶,绕过所有其他步骤:
*图片来源:Scott Wlaschin 使用“铁路”方法进行更好的错误处理设计 我们发布了一个小型的erlang库 ,该库简化了Erlang的铁路分解。 因此,在上述示例的基础上,让我们看一下如何使用Epipe实现我们的用例:
-record(connection, { socket, user, addr, port, passwd }). connect(Addr, Port, User, Password) -> Connection &#61; #connection{ user &#61; User, passwd &#61; Password, add &#61; Addr, port &#61; Port }, % Defining list of railway switches to follow ConnectionSteps &#61; [ {get_socket, fun get_socket/1}, {recv_greetings, fun recv_greetings/1}, {send_user, fun send_user/1}, {send_passwd, fun send_passwd/1} ], % Running through switches 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} &#61; Success -> Success end. % Building blocks. Note that every function can return either {ok, Connection} or {error, Reason} get_socket(Connection) -> case ssl:connect(Addr, Port, ExtraOptions) of {ok, Socket} -> {ok, Connection#connection{socket &#61; Socket}}; Error -> {error, Error} end. recv_greetings(Connection) -> case recv(Connection) of {ok, <<"&#43;OK", _Rest/binary>>} -> {ok, Connection}; {ok, <<"-ERR ", Error/binary>>} -> {error, Error}; Err -> {error, Err} end. send_user(Connection &#61; #connection{user &#61; User}) -> Msg &#61; list_to_binary(User), send(Connection, <<"USER ", Msg/binary>>), case recv(Connection) of {ok, <<"&#43;OK", _Rest/binary>>} -> {ok, Connection}; {ok, <<"-ERR ", Error/binary>>} -> {error, Error}; Err -> {error, Err} end. send_passwd(Connection &#61; #connection{passwd &#61; Passwd}) -> Msg &#61; list_to_binary(Passwd), send(Connection, <<"PASS ", Msg/binary>>), case recv(Connection) of {ok, <<"&#43;OK", _Rest/binary>>} -> {ok, Connection}; {ok, <<"-ERR ", Error/binary>>} -> {error, Error}; Err -> {error, Err} end.
与嵌套的case语句实现相比&#xff0c;所生成的代码在代码行方面并不小&#xff0c;但它的可读性肯定更高&#xff0c;从而更易于调试和支持。
如果你想看到一个真实世界中的实现&#xff0c;请看一看这个使用铁路方法进行重构的例子。
最初于 2018 年6月13日 发布在 www.erlang-solutions.com 上。
翻译自: https://hackernoon.com/railway-oriented-development-with-erlang-46ea6db4a795
erlang游戏开发