首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在没有深嵌套树的二郎牛仔中管理许多异常

在没有深嵌套树的二郎牛仔中管理许多异常
EN

Code Review用户
提问于 2018-10-28 16:24:37
回答 1查看 202关注 0票数 1

在我的cowboy (Erlang)应用程序中,我有一个名为projects_from_json的处理程序。这将以一个JSON POST请求作为输入,并应由此创建一个数据库条目。然后,它应该用一条成功消息或正确的错误消息对用户做出响应。

我从可能发生(简化)的两个例外开始:

代码语言:javascript
复制
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处理程序返回一个元组来处理错误。

下面是我的嵌套混乱。总之,我目前想要处理三种错误情况:

  • InvalidValue:用户发送的项目名称包含无效字符
  • DuplicateValue:一个同名的项目已经存在
  • MissingKey:用户没有提供所有必需的密钥

代码:

代码语言:javascript
复制
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查询的辅助模块。

EN

回答 1

Code Review用户

回答已采纳

发布于 2018-10-29 11:37:21

我的第一次尝试(不改变任何函数或处理程序的语义)是:

代码语言:javascript
复制
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/1prepare_project_json/1以正确的格式自己引发错误,而不是返回invalid | ok,则这将减少为…。

代码语言:javascript
复制
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, …, …}失败,以便外部循环能够捕捉到这一点。如果您无法决定您的函数将如何失败,只需将它们封装在其他函数中,这些函数将将错误/结果转换为预期的形式。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/206445

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档