6 ms·
Just try writing three Go programs with error handling, then, try other languages! I was pissed at first. However, now I cannot code in any language without ov
by poweroftrue 7y ago
Just try writing three Go programs with error handling, then, try other languages!
I was pissed at first. However, now I cannot code in any language without overusing try and being scared of each line.
Using Go's error handling is actually making your code smarter, I mean, you don't want your code to break with a weird message because of something stupid.
The simplest example is adding a default-path whenever I'm reading a file, Go's error handling reminds me of what to do if this file doesn't exist! Or cannot read etc.
Don't take my word for it, Go ;) try it.
- joeblubaugh 7y agoReminds me very much of “Simple, Elegant, Wrong”
- dnautics 7y agotry errors in elixir: with {:ok, val1} <- happy_path1(val0), {:ok, val2} <- happy_path2(val0, val1), {:ok, val3} <- happy_path3(some_val) do function_might_crash_let_it_crash!(some_val) happy_result() else {:error, :enoent} -> handle_notfound_error() {:error, :eperm} -> report_permission_error() _ -> raise("don't worry this process is supervised, let it crash!") end low cyclomatic complexity makes for a nice user experience, and you learn the philosophy of "if it doesn't work, just turn it off and on again". Why be scared? Just let it go. The VM has your back.
- tyre 7y agoyes! Or using function heads, though for cases with many errors this can end up being less clear. Rust's `match` also does an ok job
- jeremyjh 7y agoMatch isn't really comparable to Elixir's with. The Elvis operator is closer, you can have a chain of commands with early exit.
- chewxy 7y agoWhat's the equivalent in elixir for something like this? func do(a Param) (res Result, err error) { var aPrime T if aPrime, err = getResource(a); err != nil && err == RecoverableError { aPrime = definitelyNoErrorAlternativeGetResource(a) } var aPrimePrime T ... // many more steps return doSomething(aPrimePrimePrime) }
- dnautics 7y agothis was really hard to read. let's give it a shot, though. @spec my_fn(a::input_type)::{:ok, res::result_type} | {:error, any} def my_fn(a) do a |> get_resource |> case do {:ok, result} -> result {:error, :recoverable} -> get_alt_resource(a) err = {:error, :reallybad} -> throw err end |> many_more_steps_might_have_similar_pattern catch err err end However what you presented seems to me to be an antipattern. Sometimes code clarity trumps terseness; in this case the results from get_resource and get_alt_resource are categorically identical. If we are allowed to refactor to be actually sane code and not require everything to be in a single function: def my_fn(a) do with {:ok, res1} <- resource_wrapper(a), {:ok, res2} <- next_step(res1), ... case #handle errors here. end end @doc """ fetches resources of type `your-favorite-type` which can either come from `get_resource` or `get_alt_resource` """ def resource_wrapper(a) do case get_resource(a) do ok = {:ok, a} -> ok {:error, :recoverable} -> get_alt_resource(a) # note with this code if alt_resource # gives you an error tuple you are still # good to go err = {:error, :fatal} -> err end end seems much more sane
- arthurbrown 7y agoWhat happens with this code when err isn't recoverable? you can manage it a few ways in Elixir, struct matching is one: def perform(param) do res = case get_resource(param) do {:ok, val} -> val {:error, %RecoverableError{alternative_value: val}} -> val end do_more_things(res) end
- didibus 7y agoThought I'd show it in Java for comparison's sake: Result do(Param a) throws Exception { T aPrime; try { aPrime = getResource(a); } catch (RecoverableError e) { aPrime = definitelyNoErrorAlternativeGetResource(a); } T aPrimePrime; ... // many more steps return doSomething(aPrimePrimePrime); }
- didibus 7y agoSeems identical to Java: try { var val1 = happy_path1(val0); var val2 = happy_path2(val0, val1); var val3 = happy_path3(some_val); function_might_crash_let_it_crash!(some_val); return happy_result(); } catch (NotFoundError e) { handle_notfound_error(); } catch (PermissionError e) { report_permission_error(); } catch (Exception e) { throw new Exception("don't worry this process is supervised, let it crash!"); }
- dnautics 7y agoIf you're rethrowing the exception, what happens in the VM if that exception is not caught? IIRC, the VM exits, so, it's not at all identical. There are potentially severe nonlocal effects and you're already coding defensively by putting a catch/rethrow.
- repolfx 7y agoThe VM doesn't exit unless you're terminating the main thread (in which case, what else should it do? there's no way to continue).
- didibus 7y agoAs far as I know, you didn't get a supervisor for free in Elixir either, you decided to use one, and spent some time determining how to configure it to recover from its child processes failing. In Java, you can similarly put a try/catch at a higher level in the call tree and decide to retry everything on error. Now, Erlang processes don't share state, so that makes them easier to "handle" when they crash, I'm not denying that. My point was more that "try" can be as ergonomic as "with". The re-throw was just to mimic your Elixir example. Normally I'd just let exceptions that shouldn't be handled at that level bubble up the call tree to wherever it makes more sense for it to be handled. Now, I'll admit, most Java code I've seen professionally use exceptions badly, in that they tend to be overly aggressive and defensive, try-catching everywhere at all levels when they should just let things bubble up as appropriate. I believe that's just a general misunderstanding though. Often it is caused by Java's use of CheckedExceptions, programmers just try/catch to make the compiler error go away, when they should just re-declare the exception since it doesn't need handling most of the time, or re-throw a runtime error if they don't care to have compile time checks for handling them. When I code in Java, I barely ever try/catch anything. Normally I have one try/catch around my main method which can recover the whole app from any error, and from there, if there are smaller process trees that can be recovered independently at their level I sprinkle a few more try/catch in those places. P.S.: I also don't want to get into too many details, but the VM actually doesn't crash, Java will call your UncaughtExceptionHandler for any given thread that crashes, the default one performs a System/exit in error state, but for a server app for example, you'd handle it.