4 ms·
For 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
by mumblemumble 4y ago
For 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.
- mumblemumble 4y agoI could believe that, but implementing JVMs is such a very specific use case, far removed from what most programmers do day-to-day. Not just because it's a VM. Because you're implementing a pre-existing specification, so you're not at liberty to make your own design decisions around something like classloader semantics. It feels like a red herring example to me. "Here's how we should think about dealing with error conditions in 2022." "But what if I've got a spec that forces me to do it 1994-style?"
- kaba0 4y agoThere is nothing inherent to JVMs here, it is just the all around most common error type. It simply don’t make sense to handle many type of errors at call site, because it misses the big picture and for these, return types are not a good fit.
- mumblemumble 4y agoSo, the idea is basically that of the impureim sandwich: push everything that can fail out to the periphery. If it's difficult to handle errors at the call site, it's a sign that the call site is in the wrong place.
- prirun 4y ago> For 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." Here's a counterexample from HashBackup (I'm the author): A filesystem is being backed up. Some weird error occurs, let's say it tries to read an extended attribute and gets some goofy error that raises an exception and it's not in a try/except. That error bubbles up to the try/except for "save a file", prints the exception message, and continues by backing up the next file. Is it more reliable to crash when reading an extended attribute, aborting the whole backup? Or is it better to just tell the user about the error, finish the backup, fail at the end of the backup with a non-zero exit code, and report it in an email? At the time of failure, the only way I can see that the error could have been handled at the call site would be to return an empty extended attrbute. To me, that's more like the "silent but wrong" solution. And there are many of these situations for saving a single file: get the flags, read the extended attribute, read the permissions and times, figure out if it's sparse, etc. Instead of having error handling at each of these steps, they can have no error handling and just raise exceptions and the high-level "save a file" handler will do everything. Of course, along the way there are some low-level try/except blocks around code that needs cleanups, like: fd = None try: fd = os.open(...) do some stuff finally: if fd != None: os.close(fd) But you have the same thing with defers in Go.
- lanstin 4y agoWith the basic idea that you should never crash due to something outside your process call stack (whether that be disk full or network problems or bad messages or bad values in a database) and always abort immediately with an email and stack trace to yourself or equiv on internal logic errors.