Unhinged rant about software

Epistemic status: wail of anguish

I’ve given this rant twice recently, so will unwisely write it down to avoid giving it a third time.

Most people seem bent on self-flagellation

Subtitle: And I Don’t Think They Realise How Hard They’re Making It For Themselves.

Python

Python is touted as “simple”. What people really mean by is that library developers put in large amounts of effort to make it concise. Python itself is pretty complicated (indeed, by design you essentially can’t make any static guarantees), and its extreme dynamism makes it in practice extremely complex.

I recently mentioned Pyright to a professional Python developer. This person tried it out, and stopped, saying “it’s giving me all these false positives”. He then described two such broad classes of false positives, both of which were in fact true positives: they were indicating bugs in the code. (I’m not talking about logic bugs here, revealed by an attempt to impose types; these are bugs arising due to the use of actual language features.)

A professional full-time Python developer did not know that they were bugs! This is not his fault: Python is really hard to understand!

(I shall not mention the dependency management situation because everyone already knows what a flaming mess that is.)

.NET and ASP.NET

Idiomatic .NET code seems to be bizarrely full of galaxy-brained object orientation. ASP.NET is a particularly bad example. Throughout the standard libraries and chunks of the ecosystem, Microsoft and everyone else goes to extreme lengths to construct a system that’s as hard as possible to understand. For example:

  • ASP.NET specialises in converting all compile-time errors to runtime errors. Its total reliance on runtime dependency injection means you simply have to run your server to discover whether it even has a chance of working. This is the part of working on ASP.NET stuff that causes me literal physical pain (in my legs, if you’re interested). As far as I know, you can’t opt out of this insanity. There’s a correct way to do dependency injection, which is type-checked and compile-time-safe, but of course C# can’t express it.
  • Convention Over Configuration, i.e. “don’t worry your pretty little head about how this thing works; just trust Daddy Microsoft”. All abstractions leak, and Microsoft’s approach makes it almost completely impossible to work out what’s happening when ASP.NET leaks.
  • No human is capable of writing an ASP.NET service correctly. The middleware system is notoriously brittle: it depends fundamentally and silently on chaining together all the middleware in the correct order. GitHub is full of issues describing people getting this wrong, or asking for better documentation. All common .NET languages are strongly- statically- typed; why did they decide to throw away all the help the computer can give you?
  • ASP.NET has painted itself into such a corner that they have had to start putting some seriously ridiculous features into C#. I’m thinking here of interceptors, which they describe as “enabl[ing] exciting code patterns”. I do not want exciting code patterns! If you want to generate source code, then do so; give us a macro system if you like; but don’t bake this stuff into your language - you’re not a Lisp!
  • Also by the way you’re not Ruby. When I discovered Harmony in a dependency recently, I had several minutes of existential terror.
  • Why do the Microsoft identity libraries perform 80ms of reflection before decoding a JWT (we timed this)?

For a system which eschews compile-time safety in favour of “convention”, .NET sure has terrible docs. Why are they so big, and why do they contain so little useful information? Why are all these systems built to be impossible to use without a comprehensive knowledge of their implementation, such that I have to open the .NET source code every time I have a question about what something does? Why do I keep on having to raise docs PRs adding the most basic information? So many questions!

Golang

Another example of a “simple” language which it’s impossible to write correctly without a large suite of linters you don’t get out-of-the-box. Fasterthanli.me has written most of what I want to say about this.

I particularly enjoy the fact that you can identify a Golang program by the extremely cursed error messages you get out of it, where it dumps uninitialised memory to the console and that sort of thing. This happens with considerable regularity. I’ve even observed it in kubectl, so it’s not just that I’ve been unlucky; even the best Golang programmers can’t reliably avoid thinking garbage memory is a string. But apparently Golang error handling is great, so what would I know.

Boats:

You might also decide your language should have other classic features like null pointers, default constructors, data races and GOTO, for reasons known only to you.

Why did the creators not put algebraic data types into the language, if they were going to do errors-as-return-values?

Summary

The Jonathan Blow/Casey Muratori sphere of the Internet probably expresses this the most strongly, but they are completely correct. I think people are using really hard environments to solve usually very simple problems, and it makes their lives so much harder than necessary. Their work environment is absolutely full of incidental complexity.

To lightly paraphrase someone very senior at $WORK describing me, my schtick (a decent chunk of why I’m a “Principal Engineer” there) is to get annoyed at the existing solution to a problem and write a dumb replacement in F#, in four hours, that’s orders of magnitude faster, orders of magnitude less code, and requires less than one PR of maintenance a month (and usually much less). I’ve done this several times now. (There’s one thing, “Kraken” for those who know the G-Research internal environment, which doesn’t fit that pattern in that I did actual extended work to create it; I think that last received a PR nearly a year ago, which is to say that it is also essentially complete. That used engineering techniques no more complicated than diligently making sure all state was reified and using discriminated unions to tightly constrain the domains. None of this is rocket science!)

I believe I’m not a “10x engineer” but that most people are following practices which hold them back so much.

Copilot etc

As an encore, I believe I have an answer to “why does everyone seem to like the Copilot-like things so much, whereas I have to turn them off within five minutes of trying them?”.

Most people work in environments which have been built to resist understanding, for which you can’t get a good mental model without vast expenditure of effort, so most people are forced to write software by vibes. ChatGPT and friends are astonishingly powerful vibes engines, and in any given case they can often (usually?) vibe better than a human can; in particular, they can vibe code at a human level. Since humans are only vibing when they write code, ChatGPT is good enough to do a large chunk of the human job. A human can’t reliably do better because almost no human (very much including me!) can develop the superhuman systems understanding that is required to engineer a solution.

I am thinking of things like:

  • ASP.NET (see earlier).
  • Python (see earlier).
  • CSS, which is essentially a vast collection of special cases.
  • Javascript, about whose shortcomings enough words have certainly been written elsewhere.