Live data from Hacker News

How “let it fail” leads to simpler code

yiming.dev

141–150 of 165 posts

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

#141
post #135

Earlier quoted context omitted.

> With Javascript (a weakly typed language): I'm always wary of these, because you can define that as strongly typed if it's the operation which is defined to perform the conversion internally, which IIRC is how it works in javascript. For instance the first example will do the exact same thing in Java, because addition between a string and a non-string is defined as converting the non-string to a string then concate…

> which IIRC is how it works in javascript. Yes, when objects are involved it's internally translated to: ([]).toString() + 1 This can be shown by changing the default implementation: > Array.prototype.toString = function() { return 'Boo!'; } > [] + 1; "Boo!1" Changing the prototype for Number doesn't work so I assume there's something slightly different going on there.

> Changing the prototype for Number doesn't work so I assume there's something slightly different going on there.

The answer is that addition first checks if either operand has a "primitive value" which is string-typed, if so it's a string concatenation, otherwise it's a numerical addition, at which point it converts both operands to numbers and adds them.

The primitive value of a `Number` is a `number`, so changing `Number.prototype.toString` has no effect (it's not even called). However if you set `Number.prototype[Symbol.toPrimitive]` then you can influence the rest of the process. Still won't affect an addition of primitive `number` values but:

    > Number.prototype[Symbol.toPrimitive] = function(hint) { return String(this.valueOf()) }
    > new Number(4) + 2
     4 + new Number(2)
    
[numeric binops]: https://262.ecma-international.org/13.0/#sec-applystringornu...

[numeric conversion]: https://262.ecma-international.org/13.0/#sec-tonumeric

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

#142

Earlier quoted context omitted.

That needs a few conditions to be accepted: - an isolated process which failure doesn’t cascade other part’s failure - as parent mentionned, where and what failed needs to be super clear - people are available to timely react to the error, so a rerun will succeed Failing any of the above, and you’ll need extensive and probably complex error handling that can at least help the system work in a degraded state until the…

I believe one of the assumptions is that the failed process can automatically restart (e.g. using systemd, Kubernetes, hypervisor policies, top-level retries) - so that transient errors recover automatically, and at worst cost some performance or tiny bits of lost work (e.g. the setting an end user just hit apply on doesn't get applied, so they have to click again).

Yes, that’s really where it gets funny.

In k8s, if I remember well, the default retry policy for failures will involve incremental back offs, which means if your transient error lasts 20 min your next retry might be in a few hours. It’s fine if your system is ok with that, otherwise jobs will need to “succeed” even when they fail, and so handle errors as gracefully as possible.

Same actually for the user input one: you need to tell your user it’s a recoverable error and not just throw a random “oopsy” message, which means at least some handling of the error to come clean at the end of the tunnel.

My take is, errors are complicated. It’s nice when a script can just die at the first error and not care about how or what happens from there, but that’s such a niche case.

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

#143
post #57

Earlier quoted context omitted.

Can you guess what this code does? class foo: pass obj = foo() obj.bar = "I thought Python was strongly typed?" print(obj.bar) And even better: class foo: a = 42 obj = foo() print(obj.a) del foo.a print(obj.a) Whatever your opinion on what the imprecise sentence "strongly typed language" should mean, these are definitely not features of one.

Yes, I can guess what the code does. But can you guess what this code will do? 1 + "1" Contrast Python (a strongly typed language): >>> 1 + "1" Traceback (most recent call last): File " ", line 1, in TypeError: unsupported operand type(s) for +: 'int' and 'str' >>> [] + 1 Traceback (most recent call last): File " ", line 1, in TypeError: can only concatenate list (not "int") to list With Javascript (a weakly typed la…

So your bar for strong typing is that some type conversions are not made implicitly. That's a pretty low bar.

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

#144
post #116
post #57

Earlier quoted context omitted.

Can you guess what this code does? class foo: pass obj = foo() obj.bar = "I thought Python was strongly typed?" print(obj.bar) And even better: class foo: a = 42 obj = foo() print(obj.a) del foo.a print(obj.a) Whatever your opinion on what the imprecise sentence "strongly typed language" should mean, these are definitely not features of one.

No need to guess, IDE is flashing bright red and mypy screaming main.py:5: error: "foo" has no attribute "bar". One could still say fuckit and run it anyway, but why would you take that risk. This would never get through to production.

It's not your IDE's features that determine if the language is strongly typed or not.

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

#145
post #91

Other frameworks like express (nodejs) or actix (Rust) also don't crash if you "throw" in an request handler so this doesn't sounds very exciting to me. The interesting question for me is how retries are handled after an error occurred? For example, if the error happens in an http request handler, does the request still fails with 500 or is it magically retried by Erlang while keeping the request hanging? For interna…

The Erlang VM is built around message passing, so in the case of a dropped database connection your application code would pass a message to the database API asking for the results of a SQL query. The database API is currently trying to reconnect, so it’s not processing that queue of messages, but once it gets there it’ll pick up the message, run the query, and then send one back to the process that asked. This is all largely transparent to your application code, beyond being able to set some preferences in your return message handler around things like how long you’re willing to wait.

The whole “let it crash” thing comes from Erlang’s process supervision - in practice the DB API isn’t actually retrying. It’s continually failing to connect and if it can’t the process just crashes. The supervisor then notices and starts a new process in its place, this continues until either a process successfully starts or the configured retry count is hit. If the retry count is exceeded then the supervisor crashes, either taking the entire application with it, or more commonly being restarted itself by the next supervisor up the tree.

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

#146
post #122

I was a believer in the "my code should never crash, no matter what" school of thought until I shipped a Dreamcast game with an out-of-date opening cutscene. It was an in-engine opening cutscene which was very nearly final; the file we shipped was about two or three weeks out of date compared against the version that should have gone on the disc (It had one missing shape key on a character's face at the end of a shot…

Or even better, it should be the latter during development and the former in the released version. You don't want your released game to crash in level 11 if the player happens to look behind the wrong lightpole because a texture is missing, but you do want to notice that in development.

Back in those days when we couldn’t patch games post-release, our team felt it was much too dangerous to change anything for the release for fear of code layout changing exposing some bug which had previously been harmless and undetected by QA, and so we would typically leave all of our debugging tools and runtime checks enabled in the final release builds of the game. It was just safer that way.

But with that said, we didn’t generally crash the game due to a missing texture, even during development, as that’s a super common problem which would have impacted development too much; instead, we just drew anything which used a missing texture in max-saturation pink and green instead, alternating between the two colors once per second, to make sure it’d be super visible to anyone looking at the game during development.

We did usually change that behaviour to instead render missing tetxures in alternating black/near-black instead of pink/green for our final releases, as that was deemed a relatively safe change.

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

#147

I learned from working on aviation systems is that when a system enters an unknown state, it must be disabled and locked out. In software, this is known as an assertion failure. When the assert trips, the program is, by definition, in an unknown state. A program cannot reasonably be allowed to continue in an unknown state - it may launch nuclear missiles. The only thing to be done is exit directly, do not pass Go, do…

I like this mindset.

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

#148
post #110

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…

dicts are just a little too easy to use. You just smear it down, pass it around, and you're in business. If you really want to shoot yourself in the foot, also modify its structure here and there along the way, it's just so convenient. Who needs all that hassle of declaring a data class for each little thing? It took me a little too long to realize that a data class represents a contract about the structure of your d…

I worked on a team once where a couple of co-workers were doing this and more in what is possibly the worst Python codebase I’ve had the misfortune of seeing.

Highlights included:

* DIY “json” logging function that did some obscene string concat work every time it was called and abused global vars; it also didn’t output valid JSON. Suggestions to just use a normal logging library were aggressively disregarded.

* dozens of functions, all of which indirectly mutated this extremely nested dictionary of data. They all had slightly different names, and none of them took this dictionary as a parameter, they just abused global vars. All of them would do these insane checks to ensure that the specific keys they were looking for existed

* none of it was in git properly; the 2 data engineers writing it passed the code back and forth using a google drive.

* instead of importing functions from the Python files they wrote, they’d invoke the functions by shelling out, calling Python , string interpolating the values and then waiting for completion by *waiting for a file of a specific name to be written into the file system.

Oh yeah and when they decided they wanted parallelism, instead of doing the sane thing and using something like joblib or multiprocessing to make stuff easy, they’d just shell out and invoke more Python processes via xargs…

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

#149
post #91

Other frameworks like express (nodejs) or actix (Rust) also don't crash if you "throw" in an request handler so this doesn't sounds very exciting to me. The interesting question for me is how retries are handled after an error occurred? For example, if the error happens in an http request handler, does the request still fails with 500 or is it magically retried by Erlang while keeping the request hanging? For interna…

The point is that Erlang (the BEAM VM) follows this pattern everywhere, not just for web requests. It’s baked into the language and runtime, and that’s infinitely more powerful and customizable than not crashing in a request handler.

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

#150
This is the way. Exception handling is often one of the worst aspect of a production codebase, especially since it is typically added late. Though error handling strategies benefit from careful design, they are usually added piecemeal. Making errors louder and more problematic is the best way to get them the attention they deserve.

This is not a new concept and it seems to be one of the core components of the Erlang Weltanschauung. It can be generalized further to systems as the principle of "Crash-Only Software," as advanced in this classic paper: https://dslab.epfl.ch/pubs/crashonly.pdf

Post reply on HN