6 ms·
> If something blows up in production in an unexpected way, sometimes that's okay. You log the problem, fix it, and it's fine. as the person on call for such e
by j3s 4y ago
> If something blows up in production in an unexpected way, sometimes that's okay. You log the problem, fix it, and it's fine.
as the person on call for such events, it's really not fine.
would you rather handle these errors upfront during development time or unexpectedly and uncontrollably, during runtime? having been on-call in one way or another for ~10 years, i know which i'd prefer.
after having used golang in production, i will never go back to using a dynamically typed language if i can avoid it. almost 0 thought or effort is required to handle errors correctly in go, and it's still possible to ignore them (just use `_`). not that i'd recommend ever doing that.
- kaba0 4y agoIt’s a big assumption that you can even handle the error at the callsite, or that you will actually handle it correctly there, or just write some low-effort attempt because the overall picture is more important for now, but later on you won’t notice how it is not correct and it will just silently fail. Exceptions are in my honest opinion better on every single front. Checked exceptions would be the panacea but Java’s version is not the best due to subclassing. But overall bubbling up exceptions is the correct thing to do — in most cases a problem can’t be meaningfully handled at the call site. Let’s say you want to read from a file during a HTTP request but the read fails n methods deep. There can be any number of other error occurrences and the only sane choice here would be to return some error code to the web client requesting the site. Why do you want to handle it n times instead of only at the goal?
- AnimalMuppet 4y agoBut people write "low-effort attempts" at exception handlers all the time. So using exceptions instead of error values doesn't actually change the situation.
- kaba0 4y agoDefaults matter. Exceptions give me the question whether I want to worry about this piece of code here, or just go on and be reminded that it is actually important at runtime with an unmissable runtime error which has exact character number for the origin of the mistake. Even Java’s checked exceptions can be just added to the method signature. (Ab)using Result types without good syntax for dealing with the very common case of “handle it higher up” (Rust’s ? macro is ok, not perfect but it is a much better compromise) is coming from a wrong angle imo.
- j3s 4y agoconsider this example - one using dynamic code with exception-based handling (ruby), and one using golang's more explicit error handling: ruby: begin res = Net::HTTP.post_form(APIURL, ...) puts res.body rescue e => e # do error handling end go: response, err := http.PostForm(APIURL, url.Values{...}) if err != nil { # handle PostForm error } defer response.Body.Close() body, err := ioutil.ReadAll(response.Body) if err != nil { # handle ReadAll error } fmt.Printf("%s\n", string(body)) Now, for which errors do you handle? In golang, you can see clearly where errors may occur. In Ruby, it's unclear whether HTTP.post_form is even capable of throwing errors, much less what they are. For the record, here are a few of the many types of errors that can be thrown by the ruby code. Here are a few: Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, ... there's no way to tell _where_ the error will even come from - you might get a Net:: error, you might get a Timeout:: error, hell you could even get an Errno::! What most programmers wind up doing is handling no errors at all until they occur in runtime - or wrapping every single "suspicious" statement in a catch-all begin/rescue block, even when it's not needed. In golang, each function capable of returning an error simply returns it, and you must handle it at that point in time. It is very straightforward. In my experience, it results in much more reliable code. In Ruby, you have no idea what's coming. Do you see what I mean?
- weatherlite 4y ago> there's no way to tell _where_ the error will even come from - you might get a Net:: error, you might get a Timeout:: error, hell you could even get an Errno::! What most programmers wind up doing is handling no errors at all until they occur in runtime - or wrapping every single "suspicious" statement in a catch-all begin/rescue block, even when it's not needed. Been programming Ruby for 8 years and have never seen anyone do that, so no... If that block of code can really throw so many different types of exceptions (usually - it can't) you can always catch StandardError.
- kaba0 4y agoI’m not familiar with Ruby, but in java you can just specify the try-catch block’s scope as explicitly as you want. ``` try { var response = http.postForm(APIURL,) } catch (Exception e) { # handle PostForm error } try { var body = ioutil.ReadAll(response.Body) } catch(SomeSpecificExceptionType e) { # handle specific exception differently } catch(Exception e) { # run for every other kind of error } If a given method is defined as `void postForm(…) throws IOException` you have to handle the exception at the call site, or mark the containing call itself with a similar throws clause. I really don’t see anything not handled by these. Also, the defer part can be done by a try-with-resources construct that will clause the resource in every case, whether exited without exception or with one.
- mumblemumble 4y agoFor reliability, the absolute best discipline I've ever experienced, is "Either handle the error at (or very close to) the callsite, or crash the process." But only if you make sure that everyone on the team understands that (A) this is the only way to do it, and (B) there is no other way to do it, and (C) you will do it no other way. I think because it sets up the right psychological incentives. Because almost any option other than those two ends up looking like, "Hmm, I don't know how to handle this, but maybe someone else will, so let's bubble it up and hope they do," in practice. It's just the way we humans are. And that sets up an intractable moral hazard situation. It creates this middle ground where two different owners of two different parts of the code can finger-point and blame and try to pass the buck. And, knowing that (though probably only unconsciously), they won't just become sloppier about handling errors. They will become sloppier about allowing errors to happen in the first place, and doing things that make it harder to handle errors. And they won't even know they're doing it. That's what I think of when I read your hypothetical, "Let’s say you want to read from a file during a HTTP request but the read fails n methods deep." That sounds to me like code that's been structured in a way that, on this team I was alluding to above, probably wouldn't have happened in the first place and definitely wouldn't have made it past code review. The pushback would be something along the lines of, "Why on earth are you doing both file and network I/O within a single business operation but at two very different depths in the call stack? Did you even notice what you were doing here?"
- kaba0 4y agoI just gave you an example from the top of my head. Here is another more realistic one, which occurred to me during the time I wrote a toy JVM: a Java class file contains a constant pool with different kinds of types. During the loading process this pool can be references by other entities as well, but they require the necessary type. Assuming verified byte code, this will be the case, but if we care about verification as well then you will quickly get into the situation where you simply can’t handle “during the loading of this class’s nth method, one of the constant pool references were of incorrect type”. It can’t be handled at method loading (or even more nested), and panicking is not the correct behavior (you only don’t want to load the given class). So you have to manually bubble it up for no benefit.