Erlang philosophy handling error - case against throw

I am writing a REST service in Erlang and must check the received data before passing it to other internal functions for further processing; for this I am currently using nested case expressions as follows:

 case all_args_defined(Args) of true -> ActionSuccess = action(Args), case ActionSuccess of {ok, _} -> ...; {fail, reason} -> {fail, reason} end, _ -> {fail, "args not defined"} end, ... 

I understand that this is disgusting, but in this way I can provide detailed error messages. Also, I don’t think the usual solution to this problem is applicable here: I don’t want the REST service to crash and restart every time someone throws invalid arguments at it.

However, I am considering abandoning all these cases in favor of a try/catch umbrella try/catch that catches any badmatch errors - will this work?

 fun() -> true = all_args_defined(Args), {ok, _} = action(Args). %% somewhere else catch fun(). 
+6
source share
3 answers

Since you want to get a bug report, you need to structure things around the execution of actions and the reporting of results. Maybe something like this:

 execute(Action, Args) -> try check_args(Args), Result = action(Action, Args), send_result(Result) catch throw:{fail, Reason} -> report_error(Reason); ExceptionClass:Term -> %% catch-all for all other unexpected exceptions Trace = erlang:get_stacktrace(), report_error({crash, ExceptionClass, Term, Trace}) end. %% all of these throw {fail, Reason} if they detect something fishy %% and otherwise they return some value as result (or just crash) action(foo, [X1, X2]) -> ...; action(foo, Args) -> throw({fail, {bad_arity, foo, 2, Args}}); action(...) -> ... %% this handles the formatting of all possible errors report_error({bad_arity, Action, Arity, Args}) -> send_error(io_lib:format("wrong number of arguments for ~w: " "expected ~w, but got ~w", [Action, Arity, length(Args)])); report_error(...) -> ...; report_error({crash, Class, Term, Trace}) -> send_error(io_lib:format("internal error: " "~w:~w~nstacktrace:~n~p~n", [Class, Term, Trace])). 
+6
source

I had this problem while developing an application that creates users.

First I came up with this solution:

 insert() -> try check_1(), % the check functions throw an exception on error. check_2(), check_3(), do_insert() catch throw:Error1 -> handle_error_1(); throw:Error2 -> handle_error_2(); _:Error -> internal_error() end. 

The problem with this solution is that you lose the stack trace with the try ... catch block. Instead, the best solution is:

 insert() -> case catch execute() of ok -> all_ok; {FuncName, Error} -> handle_error(FuncName, Error); {'EXIT', Error} -> internal_error(Error) end. execute() -> check_1(), % the check functions throw an exception on error. check_2(), check_3(), do_insert(). 

This way you have a complete error stack on error.

+2
source

I came across the same issue when writing my own REST services.

Let's start with the philosophy:

I like to think of my apps as a box. On the inside of the box are all the parts that I built and which have direct control. If something breaks down here, this is my mistake, it should crash, and I should read about it in the error log. At the edge of the window are all points of connection to the outside world - they should not be trusted. I avoid handling exceptions in the inner parts and use it as needed for the outer edge.

In similar projects, I worked on:

I usually have about a dozen user input checks. If something looks bad, I log it and return an error to the user. Having a stack trace does not really matter to me - if a user has forgotten a parameter, there is nothing in my code to find and fix. I would rather see a text log that says something like: "at 17:35, user X turned to path Y, but the Z parameter was missing."

I will organize my checks on functions that return ok or {error, string()} . The main function simply performs the check and returns ok if they are all passed, otherwise it returns the first error, which is then logged. Inside my validation functions, I use exception handling as needed, because I cannot consider all the ways that users can get corrupted.

As suggested by my colleagues, you can alternatively have each check to throw an exception instead of using a tuple.

As for your implementation, I think your idea of ​​using a single exception handler is good if you have only one check. If you need more checks, you can implement something like the one described so that you can have a more specific log.

+2
source

Source: https://habr.com/ru/post/893775/


All Articles