[erlang-questions] Error handling with good looking code?

Garrett Smith g@REDACTED
Tue Nov 15 18:11:31 CET 2011


Hi Attila,

On Thu, Nov 3, 2011 at 8:08 AM, Attila Rajmund Nohl
<attila.r.nohl@REDACTED> wrote:
> Hello!
>
> I have this code (cut out some non-interesting pieces):
>
> upload_trace_record_file2(Host, Usr, Pwd, RemDir, NetTraceRef) ->
>    case sftp_client:open(Host, ?SFTP_PORT, Usr, Pwd) of
>        {ok, Pid} ->
> ....
>            case sftp_client:send(Pid, TRFile, RemTRFile) of
>                ok ->
>                    ok;
>                {error, Reason} ->
>                    {error, sftp_client:format_error(Reason)}
>            end,
>            sftp_client:close(Pid),
>            file:delete(TRFile);
>        {error, Reason} ->
>            {error, sftp_client:format_error(Reason)}
>    end.

Aren't you just talking about this:

 case open_client(Info) of
     {ok, C} ->
         Result = send_files(C, Files),
         cleanup(C, Files),
         Result
     {error, Err} -> {error, Err}
 end

Alternatively,

 case open_client(Info) of
     {ok, C} ->
         try
             send_files(C, Files)
         after
             cleanup(C, Files)
         end
     {error, Err} -> {error, Err}
 end

> update_trace_record_file(TRFile, NetTraceRef) ->
>    case exml:read_file(TRFile, [{space, normalize}]) of
>        {ok, ReadExml} ->
>            {traceCollecFile, Attributes, ChildList} = ReadExml,
> ...
>            file:write_file(TmpTRFile, TraceText),
>            TmpTRFile;
>        _ ->
>            throw({error, bad_xml_file})
>    end.

The general pattern seems fine:

 case read_file(Info) of
     {ok, File} ->
         write_file(File),
         File;
     {error, Err} -> throw(Err)
 end

The alternative is to return a tagged value:

 case read_file(Info) of
     {ok, File} ->
         write_file(File),
         {ok, File};
     {error, Err} -> {error, Err}
 end

Some lesser observations:

- You're losing valuable information by ignoring the error from
exml:read_file/2 - that should probably be in the thrown term

- Use exit(Reason) rather than throw if its part of the API (i.e.
anyone outside your code might handle it) - exit will propagate up the
stack and cause the process to terminate with the term provided minus
the call stack

> There are at least two obvious bugs in the error handling (the error
> from sftp_client:send is not used; the update_trace_record_file throws
> an exception instead of returning an error), but it made think about
> how to handle the errors properly in this code. The "let it crash"
> philosophy doesn't really work here, because we need to produce a
> meaningful error message to the user and the stack trace is not a
> meaningful message to them. I was thinking something like this:
>
> upload_trace_record_file2(Host, Usr, Pwd, RemDir, NetTraceRef) ->
>    try
>        {sftp_client, {ok, Pid}} = {sftp_client,
> sftp_client:open(Host, ?SFTP_PORT, Usr, Pwd)},
> ....
>        {sftp_client, ok} = {sftp_client, sftp_client:send(Pid,
> TRFile, RemTRFile)},
>        sftp_client:close(Pid),
>        file:delete(TRFile)
>   catch
>        error:{badmatch, {sftp_client, Reason}} ->
>            {error, sftp_client:format_error(Reason)};
>        error:{badmatch, {exml, _}} ->
>                  {error, bad_xml_file}
>    end.
>
> update_trace_record_file(TRFile, NetTraceRef) ->
>    {exml, {ok, ReadExml}} = {exml, exml:read_file(TRFile, [{space,
> normalize}])},
>    {traceCollecFile, Attributes, ChildList} = ReadExml,
> ...
>    file:write_file(TmpTRFile, TraceText),
>    TmpTRFile.
>
> but it looks somewhat ugly. Three "assignments" and function calls are
> hidden away in tuples, so it's a little hard to read, even though the
> rest of the code is simpler. Do you have any other ideas?

If I were to restate, you're suggesting that this:

 case foo() of
     {ok, Val} -> good(Val);
     {error, Err} -> bad(Err)
 end

be written like this:

 try
     {foo, {ok, Val}} = {foo, foo()},
     good(Val)
 catch
     error:{badmatch, {foo, {error, Err}}} ->
         bad(Val)
 end

When used for good, Erlang lets you write clear, logical, maintainable
code. If your eyes start to bleed when reading code (as is the case
here for me), push through the confusion / fuzziness and work until
the code is obviously descriptive of the problem you intend to solve.

Just my 2 cents :)

Garrett



More information about the erlang-questions mailing list