在我的cowboy (Erlang)应用程序中,我有一个名为projects_from_json的处理程序。这将以一个JSON POST请求作为输入,并应由此创建一个数据库条目。然后,它应该用一条成功消息或正确的错误消息对用户做出响应。
我从可能发生(简化)的两个例外开始:
projects_from_json(Req=#{method := <<"POST">>}, State) ->
{ok, ReqBody, Req2} = cowboy_req:read_body(Req),
try
Name = parse_json(ReqBody),
% Continuation of happy path
db:create_project(Name)
catch
error:{badkey, Key} ->
% return error message about invalid JSON
% [<<"Key \"">>, Key, <<"\" does not exist in passed data">>]
throw:duplicate ->
% return error message about duplicate name
% [<<"A project with name \"">>, Name, <<"\" already exists">>]
end在这种情况下,这已经不起作用了,因为Name在第二个catch子句中是不安全的(因为它来自try子句,可能是未设置的)。我也无法将db:create_project(Name)的调用移到try之外,因为同样地,使用Name是不安全的。
由于有更多的异常和错误需要处理,我最终遇到了一个非常嵌套的情况(如下所示)。
我的第一个想法是将内容转移到自己的函数中,但大多数代码都是错误处理,据我所知,我只能通过从projects_from_json处理程序返回一个元组来处理错误。
下面是我的嵌套混乱。总之,我目前想要处理三种错误情况:
代码:
projects_from_json(Req=#{method := <<"POST">>}, State) ->
{ok, ReqBody, Req2} = cowboy_req:read_body(Req),
try
Name = project_name(ReqBody),
case validate_project_name(Name) of
invalid ->
molehill_respond:respond_error(<<"InvalidValue">>,
<<"The project name can only consist of ASCII characters and numbers, and dash in the middle of the word.">>,
400, Req2, State);
ok ->
{ok, Conn} = moledb:connect_from_config(),
try
moledb:create_project(Conn, Name),
Data = prepare_project_json(Name),
molehill_respond:respond_status(Data, 201, Req2, State)
catch
throw:duplicate_element ->
molehill_respond:respond_error(<<"DuplicateValue">>,
erlang:iolist_to_binary(
[<<"A project with name \"">>, Name, <<"\" already exists">>]),
409, Req2, State)
end
end
catch
error:{badkey, Key} ->
molehill_respond:respond_error(
<<"MissingKey">>,
erlang:iolist_to_binary(
[<<"Key \"">>, Key, <<"\" does not exist in passed data">>]),
400, Req2, State)
end.是否有可能将其重构为一个干净的表单,其中所有可能的错误都在函数末尾类似的缩进级别上?
molehill_respond是我为简化创建JSON返回消息而编写的一个辅助模块,moledb是一个执行所有SQL查询的辅助模块。
发布于 2018-10-29 11:37:21
我的第一次尝试(不改变任何函数或处理程序的语义)是:
projects_from_json(Req=#{method := <<"POST">>}, State) ->
try
{ok, ReqBody, Req2} = cowboy_req:read_body(Req),
Name = get_project_name(ReqBody),
{ok, Conn} = moledb:connect_from_config(),
Data = get_project_json(Conn, Name),
molehill_respond:respond_status(Data, 201, Req2, State)
catch
error:{badkey, Key} ->
molehill_respond:respond_error(
<<"MissingKey">>,
erlang:iolist_to_binary(
[<<"Key \"">>, Key, <<"\" does not exist in passed data">>]),
400, Req2, State);
error:{error, Label, Message, Status} ->
molehill_respond:respond_error(Label, Message, Status, Req2, State)
end.
get_project_name(ReqBody) ->
Name = project_name(ReqBody),
case validate_project_name(Name) of
invalid ->
erlang:error({
error,
<<"InvalidValue">>,
<<"The project name can only consist of ASCII characters and numbers, "
"and dash in the middle of the word.">>,
400
});
ok ->
Name
end.
get_project_json(Conn, Name) ->
try
moledb:create_project(Conn, Name),
prepare_project_json(Name)
catch
error:duplicate_element ->
erlang:error({
error,
<<"DuplicateValue">>,
erlang:iolist_to_binary(
[<<"A project with name \"">>, Name, <<"\" already exists">>]),
409
})
end.基本上,我将代码的一部分从projects_to_json/2移到了它自己的函数中,这样您就可以保持主代码更整洁。
但是您可能已经注意到,我的新函数仍然有很多用于封装错误的代码。如果您可以让validate_project_name/1和prepare_project_json/1以正确的格式自己引发错误,而不是返回invalid | ok,则这将减少为…。
projects_from_json(Req=#{method := <<"POST">>}, State) ->
try
{ok, ReqBody, Req2} = cowboy_req:read_body(Req),
Name = project_name(ReqBody),
validate_project_name(Name),
{ok, Conn} = moledb:connect_from_config(),
moledb:create_project(Conn, Name),
Data = prepare_project_json(Name)
molehill_respond:respond_status(Data, 201, Req2, State)
catch
error:{badkey, Key} ->
molehill_respond:respond_error(
<<"MissingKey">>,
erlang:iolist_to_binary(
[<<"Key \"">>, Key, <<"\" does not exist in passed data">>]),
400, Req2, State);
error:{error, Label, Message, Status} ->
molehill_respond:respond_error(Label, Message, Status, Req2, State)
end.
%% @doc this function may raise an error like {error, binary(), binary(), 400..499}
validate_project_name(Name) ->
…
%% @doc this function may raise an error like {error, binary(), binary(), 400..499}
prepare_project_json(Name) ->
…基本上,我们的想法是尽可能多地包含错误本身所需的信息,所以…不是duplicate_element失败,而是{error, …, …}失败,以便外部循环能够捕捉到这一点。如果您无法决定您的函数将如何失败,只需将它们封装在其他函数中,这些函数将将错误/结果转换为预期的形式。
https://codereview.stackexchange.com/questions/206445
复制相似问题