Hacker News new | ask | show | jobs
by tikhonj 1063 days ago
The April 2022 update at the end of the article has a good short summary:

> If you're looking to reduce the whole discourse to "X vs Y", let it be "serde vs crossing your fingers and hoping user input is well-formed". It is one of the better reductions of the problem: it really is "specifying behavior that should be allowed (and rejecting everything else)" vs "manually checking that everything is fine in a thousand tiny steps", which inevitably results in missed combinations because the human brain is not designed to hold graphs that big.

My pet theory is that this corresponds to the "two cultures" of software engineering: do you value up-front work and abstraction to reduce cognitive load and debugging, or would you rather (try to) pay more attention and spend more time debugging to reduce how much you have to learn and think up-front? Go seems pretty firmly in the latter camp. That's exactly why I am not interested in the language either, despite the various things it gets right.

4 comments

I think this is true generally, but I’d narrow down this particular trade off further: a complex language can do more validation, that otherwise has to be manual. As such, it’s a trade off between language complexity and boilerplate.

It’s easy to argue against boilerplate: just find an example where a particular language feature shines and show that it reduces amount of code. Less noise reduces risk of human oversight. Arguing against complexity is much harder, similar to arguing against a suffocating bureaucracy: the cost is much more deferred, global, and often is only evident when it’s too late. Death by a thousand paper cuts.

And even with the most elaborate type acrobatics, we still have to validate things “manually”, so we need a “standard process” anyway. So while reducing boilerplate can be worthwhile, it’s not sufficient.

In order to argue language complexity, imo, you need truly compelling and recurring real-world examples. Rust strikes many heavy-weight birds here, but has some sources of immense complexity that yield very meager returns, like async Send, Sync, Pin gymnastics and function coloring. Go otoh suffers some very repetitive boilerplate, like the `if err != nil` 3-liner, for instance, which was elegantly solved in Rust with `?` etc.

This is interesting. I think though that the first option eventually leaks though, and that (unfortunately) you will have to spend time debugging things, sometimes at great cost for really sophisticated abstractions. The tradeoff, and the tipping point are clearly not obvious though!
You can't get away from some debugging, but I've certainly seen massive differences in how much debugging I've had to do between different codebases!
This is a very interesting discussion. I agree with you. I’m an application developer but I’ve had to debug what I consider to be very hard issues ( network driver bugs, kernel bugs, compiler bugs, hardware bugs, etc ), every time in what I thought to be perfect aabstractions. I mean, the compiler should compile properly, the kernel shouldn’t lie, and my memory stick should behave. The thing is, it eventually fails. So I have a maybe abnormally high level of appreciation for debuggable systems :-)
I'm not sure exaclty what he's talking about with Serde, in Go when you deserialize data into a struct it is strongly typed. It's not up to the dev to check that you received a string or an int.
I have not done deserialization in Go, but my understanding of what he meant via context was that because everything has default values, it is possible to end up with a struct even if the JSON (or whatever) isn't actually well-formed. I don't know how true that is or to what extent it causes issues.
Not to my knowledge, for example if it's json data, if the json is malformed it will error out, if you have etra field you can error out as well: https://pkg.go.dev/encoding/json#Decoder.DisallowUnknownFiel...

For input that does not have the fields of the destination well you can use other libraries that check that for you or use pointers.

> For input that does not have the fields of the destination well you can use other libraries

Right, this is what I was thinking, but that this is the case is the point being made here, and it's consistent with the rest of the post. The default option doesn't really check things for you.

Unless you deserialize into a type whose properties are interface types, in which case you're definitely checking...
I don't see how creating abstractions reduces the cognitive load. You create new concepts before you even need them + once you notice that the abstraction was wrong, now it's much harder to reverse.
Abstractions reduce cognitive load because they let us consolidate multiple bits of information into a single concept.
I think the fundamental disconnect here between people who appreciate abstraction and those who are seemingly in perpetual fear of it is that we rarely guide engineers on how to make good abstractions.

You hear complaints ad nauseam about how abstractions are always leaky, they are confusing, etc. And yet here I am typing this into a text box displayed in a web browser running on top of an operating system and countless libraries, rendered from a mix of content of HTML, Javascript, and CSS source which was delivered over TLS-wrapped HTTP using TCP sockets routed over an IPv4 connection which was repeatedly translated back and forth into Ethernet frames, all on a machine which schedules the execution of binary blobs compiled from a multitude of languages to the AArch64 instruction set onto any number of virtual cores while carefully managing resources like permanent storage, processor cache, random-access memory, guided by electrical signals from a keyboard which are decoded into meaningful glyphs according to my configured layout and character set, all displayed upon an OLED film based upon an HDMI-encoded signal transmitted over a bundle of wires.

Which is to say we spend every day comfortably resting upon a truly mind-boggling tower of abstraction layers which—for the most part—work pretty well. So not only clearly can it be done, but it also must be done in order to provide anything like the computing experiences we expect and rely on in day to day life.

Rather than shy from abstraction because bad abstractions are bad, we should spend more effort on learning how to design and promote good abstractions, since they are something upon which our entire profession is inescapably built.