Live data from Hacker News

How “let it fail” leads to simpler code

yiming.dev

31–40 of 165 posts

Re: How “let it fail” leads to simpler code

#31

I'd be very interested to see non-BEAM approaches to enabling this - i kind of end up in the same pattern thanks to "expected? Return an Error . Unexpected? Throw." However, the supervising part is then difficult. How do people approach this in Python? NodeJS? Rust? .NET?

If you can stomach kubernetes, you get this for free for all languages. Just panic!() or die(), and you'll get a fresh pod in a few seconds.

Outside of k8s I try to use the lang's preferred tool. Python -> supervisord, Node -> pm2, etc.

Re: How “let it fail” leads to simpler code

#32
I spent a good part of this week overhauling a microservice where most fucntions were a giant try/catch & would maybe throw a new error. Just getting rid of the try catches & letting the code fail has been a huge help in seeing what is going wrong as the code executes.

I also am delighted to see the idea of expected errors here. Another thing I've been doing for a long time is tagging erros with an expected = true property when it's something we expect to see, like, oh, we went to get this oauth token but the credentials were wrong. Expectedness shows up in thr logs now & we can see mych more clearly where there are real problems.

Re: How “let it fail” leads to simpler code

#33
A corollary or generalized interpretation of this approach (and someone please specify if there’s a formal term for this) is: “fail locally, and immediately.”

What I mean is that once something unexpected happens your code should ideally fail in that step itself.

The simplest most common example I’ve seen with python programmers is when they pass around dicts as arguments in complex code bases. Methods expect various keys to be present, and often methods also have fail safe defaults if some keys are absent. The defaults are written for the specification, sure, but often they also tolerate unexpected exceptions that happened upstream.

Now when an unexpected exception happens, your program fails somewhere else and the stacktrace is useless. The only way to figure out what went wrong is to debug it line by line.

With python there’s still no elegant solution. I’m now trying to ensure all my methods are typed and use dataclasses and pydantic classes to type and group these parameters but there’s still opportunities for these “fail later” errors. Solutions and suggestions would be appreciated!

Re: How “let it fail” leads to simpler code

#34

I don't agree with this approach. Say you have a network service that relies on other network services. It is not difficult to write those such that they know to back off / retry when something disappears. It's extremely useful in a lot of situations: if you do work on a laptop that gets regularly unplugged, having running test services that know to reconnect makes your life easier. In production, having things autom…

I've said this often specifically in the context of golang, but while you're right that retries and similar are a common case, they are fairly similar to the 'expected' error case in the article, and can almost always be handled at precisely the place where you raise the error. In python this is @retry.retry(exceptions=[RpcException], tries=5, backoff=2, jitter=1) def my_external_rpc_call(...): .... And RpcException…

A warning about this; retries should only be done at boundaries. And it's important to know if e.g. the http or API library already implements retires, not to mention which errors should be retried. I have seen at least one codebase where the retries where completely out of hand.

In short, I've found retrying well is harder than it looks.

Re: How “let it fail” leads to simpler code

#35
The article doesn’t seem to look at how resources are cleaned up when a BEAM process crashes. https://elixirforum.com/t/understanding-the-advantages-of-le... says “All resources are owned by a process in Erlang, and the VM guarantees clean-up of resources once the process dies”. My Google-fu failed me when I searched for more details about Erlang process cleanup of resources, or how to register cleanup actions (e.g. delete some temporary file on crash).

Re: How “let it fail” leads to simpler code

#36

I don't agree with this approach. Say you have a network service that relies on other network services. It is not difficult to write those such that they know to back off / retry when something disappears. It's extremely useful in a lot of situations: if you do work on a laptop that gets regularly unplugged, having running test services that know to reconnect makes your life easier. In production, having things autom…

What you're describing are "known" states; the idea behind "let it fail" is that you shouldn't write code that exhaustively handles every single potential outcome, just the ones that are part of your code's path in general use. Definitely write code to handle network issues. Don't write code to handle random bitflips, ways to handle garbage coming back from the service you're connecting to, or try to handle OOM error…

Adding to that, even stuff like OOM errors _can_ be known states. It's not unreasonable for stuff like "one database per machine" to be able to adapt to the available memory. The point of "let it fail" is _just_ to drop the outcomes of your code's path in general use.

Re: How “let it fail” leads to simpler code

#37
post #5

I've seen a lot of new developers shocked by this approach, which surprises me a little. They seem to think that it's up to the application to handle all errors, even those of the programmer(s). This, of course, is unreasonable since it would essentially require knowing all the bugs in advance. :-)

I was on a team for a short while (Java programmers) and their frontend code was really overly "careful". For example, they would always check if a method existed, before calling it.

    var o = new SomeObject();

    if (o.computeSomething != null && o.computeSomething != undefined) {
       o.computeSomething(...);
    }
Their reasoning was that in JavaScript (with the old syntax) you just add functions to the prototype, so you could forget to do it or mistype it.

    SomeObject.prototype.computeSomethinnn = function () ...
I was sort of tripping over myself in objections to what they were doing:

* you shouldn't check for null or undefined, but rather do `o.computeSomething instanceof Function`

* there's no need to do `!= null` and `!= undefined` because `!=` (as opposed to `!==`) actually checks for both

* you shouldn't do the check at all because if you actually mistype the function name all you're doing is hiding the error. Failing sooner is better.

* a missing method should be picked up in the unit tests (but they didn't have any tests at all because "our system is too complex to be tested automatically")

* probably some others...

That team really hated JavaScript and their code showed it.

BTW, the indentation above is not wrong... they did indent by 3 spaces. I read a story about 3 space indents on thedailywtf.com and thought that it was clearly made up... after this team I believe it.

Re: How “let it fail” leads to simpler code

#38

A corollary or generalized interpretation of this approach (and someone please specify if there’s a formal term for this) is: “fail locally, and immediately.” What I mean is that once something unexpected happens your code should ideally fail in that step itself. The simplest most common example I’ve seen with python programmers is when they pass around dicts as arguments in complex code bases. Methods expect various…

>Solutions and suggestions would be appreciated!

Use a language with strong typing?

Re: How “let it fail” leads to simpler code

#39
post #37
post #5

I've seen a lot of new developers shocked by this approach, which surprises me a little. They seem to think that it's up to the application to handle all errors, even those of the programmer(s). This, of course, is unreasonable since it would essentially require knowing all the bugs in advance. :-)

I was on a team for a short while (Java programmers) and their frontend code was really overly "careful". For example, they would always check if a method existed, before calling it. var o = new SomeObject(); if (o.computeSomething != null && o.computeSomething != undefined) { o.computeSomething(...); } Their reasoning was that in JavaScript (with the old syntax) you just add functions to the prototype, so you could…

It sounds like a pretty Java thing to do considering the prevalence of `null` and null-checks in the language. It's always interesting to see the habits that programmers bring from their main language(s) to ones they're picking up, especially when they're under pressure to deliver so they can't learn to program idiomatically.

Re: How “let it fail” leads to simpler code

#40
post #38

A corollary or generalized interpretation of this approach (and someone please specify if there’s a formal term for this) is: “fail locally, and immediately.” What I mean is that once something unexpected happens your code should ideally fail in that step itself. The simplest most common example I’ve seen with python programmers is when they pass around dicts as arguments in complex code bases. Methods expect various…

>Solutions and suggestions would be appreciated! Use a language with strong typing?

Python is strongly typed. You want statically typed. (Instead of duck typed / dynamically typed)
Post reply on HN