Laws of Software Evolution
two-wrongs.com
two-wrongs.com
In the last decade I've swung the other way. While the code does indeed "not rust" it does decay.
Primarily this is because software now is more integrated. Integration means communication and there are changes happening all the time. SSLv3, TLSv1, went away. XML became JSON. And don't get me started on SaaS APIs that have a "new version" every couple of years. And yes, the elephant in the room, security.
Unfortunately lots of developers in my age group believe in "if it's not broken don't fix it". They are the hardest cohort to convince "update things even they're important. Updating when its urgent is no fun at all."
There's no habit as hard to break as a best-practice thats turned bad.
The lengths to which manufacturers have gone to "hit the numbers" has resulted in these major deficiencies. This has all formed out in the last decade. Previous to that, a lot of the emissions standards had a positive affect (in most cases).
The automotive manufacturers and the regulators are colluding without enough transparency and it has corrupted the process.
And I'll also add that at no point are you ever going to need to flash the ECU with updated firmware.
So while a cute example, it isn't really a practical example. Typically when people complain about "code rusting", it is in the context of a codebase that needs to adapt to the world around it; whether that is communicating with other systems or even just accepting new features within its own little fief.
Some of the modern ecosystems have gone entirely bonkers (think nodejs/npm): hundreds and thousands of dependencies for the simplest of things, basically an unmanageable "supply chain".
Sure, we can talk about what's good approach to update and dependency hygiene, how packages should "freeze" their dependencies, how should breaking changes be communicated through version numbers (or not), but we've seen the rise of the non-NIH that can't bother to implement 5 lines of code.
If you commit to update whenever there's a newer version of any dep you are using, you commit yourself to a world of pain and fighting even more bugs.
I actually believe LTS repos of public repos (PyPI, NPM, ...) could be a decent way to earn some money in today's dependency-heavy world.
You can really build and maintain only a single version of your product, forgetting about maintaining old versions. This means pushing backwards compatibility challenges onto your customers (eg. other developers for an API) — the fact that customers have accepted that tells us that the optimization is working, though I am very much not a fan.
I think we need to be looking at a better balance of backwards compatibility in the tools we use (both external systems and external libraries), understand the cost for importing almost-trivial dependencies, and I believe there might be even an opportunity for someone to start a business there ("I'll backport security fixes for your 10 dependencies for you").
You can't simply choose to never update any dependencies - the only question is how you decide when and which updates will get made, or delegate that decision to others.
Eg. all packages in Ubuntu LTS "main" archive, or RedHat releases, get supported for 5-10 years with security fixes, while maintaining backwards compatibility.
However, even Canonical has realized people will accept breakage, so "main" has been dwindling over time to reduce their costs. That also applies to snaps and flatpaks — no guarantees about them at all.
The problem is a lack of modularity and standardised interfaces where it matters (and modularity where it doesn't, causing additional useless complexity), and incentives that reward planned/forced obsolescence.
Also related: TLS 1.3 on Windows 3.11: https://news.ycombinator.com/item?id=36486512
I’d say that complexity is the other part of the problem. As developers, we like to organize functionality into packages, and version them based on the type of changes we make. We like layers of abstraction to contain classes of problems to work on them independently. But all software is a list of instructions, and all the ways that they can interact cannot be categorized in a Symantec version number, or a unit test. Subtle changes can break functionality in surprising ways.
I can change from ext2 to btrfs and my program won't die because open and read are a good interface.
There's a discussion to be had that's the volume of E2E vs unit tests, but that isn't this discussion.
The only solution is to have a crystal ball.
I think I am uniquely qualified to speak authoritatively on this subject as I regularly work in code bases dating back to the mid 1950s (nearly 70 years old if you are counting).
Code left alone absolutely does rust. Everything around the code changes, you can wax philosophically about how "technically the code didn't rust the world did", but at the end of the day that old code aged into a position where it no longer functions optimally or even correctly.
Just to tease with an example, the original machine some of this code was written for only had a few kilobytes of memory, half of which was taken up by the OS and compiler. In order to fit the problem you are working on into memory, they effectively wrote a custom swap allocator that looked and felt like you were using in-memory arrays but were actually using tape storage. Fast forward to today where we have a single compute node with 128 physical CPU cores and a terabyte of memory, the code still diligently uses disk storage to minimize RAM consumption but runs like dog shit on a single thread anyways. Not to mention all the integers used to store pointers have had to be widened over the years as you went from 48 bit words to 32 bit words to 64 bit words.
In principle, I want this to be true. But, in practice, I think products change because the teams that built a product need to justify their continued existence within a corporation. Slack's last few rounds of UI changes for instance, has been a net negative for me, the user. Why can't I split several channels in the same window anymore? Why did they add a thick vertical toolbar that I don't use and can't remove? Not for my benefit, that's for sure.
p.s. Kelley not Kelly is the correct spelling of his name.
If HN people stopped working for HN companies then they’d see that most of the behaviours they complain about, on HN, are not universal inevitabilities.
I would've thought a company centered around developer culture would understand the concept of not fixing what isn't broken. I guess pencil pushers have now fully taken them over.
This is an interesting perspective and will take me some more time to apply to my understanding (see what I did there?). A first thought is how this condemns designing and architecting for an unknown future.
For example, you may know for sure that some invariant is true in your market but not globally, so you know in advance that when(if!) your company expands, you'll need to change that thing in a specific way - but not in an arbitrary direction, that change is knowable even if you haven't yet spent the effort to know about its details.
This article is falling into a trap stating that 'requirement changes' are to blame for constant software rewrites. Good software architecture doesn't require full rewrites precisely because it anticipates requirement changes. That's the entire point of software architecture. Unfortunately, almost nobody has experienced good software architecture. Even among the very few who will encounter it, most of them won't recognize it because either they are not competent enough to appreciate it or they simply won't stay on the same project long enough to observe the benefits and flexibility of its architecture.
So yes, I think it definitely looks like a conspiracy. I've encountered many developers in my career who have had a tendency to over-engineer and create unnecessary complexity; they are often promoted within companies. Not only in agencies and consultancies (where the incentive to over-engineer is clear; more bugs = more repeat customers), but also in other large corporations and even non-profits.
There is a significant shortage of competence both on the developer side as they are unable to produce good architecture but also on the management side as they fail to recognize good architecture and accurately reward talent.
Most software developers and managers will outright deny the existence of 'good architecture'. They are such a large majority that their voices completely obscure the voices of the (guessing) 1% who know better. The voice of mediocre developers, propped up by short-term financial incentives, is much louder than the voice of excellent developers who pursue longer-term goals.
Unfortunately, if you're a top 1% software developer, the industry feels like Idiocracy, except for the crucial fact that, in Idiocracy, the idiots are just smart enough to identity who the intelligent people are.
Take a basic web application as an example. In the last 20 years, we went from n-tier, to generated frontend using some template framework, to rest server + js frontend, and recently generated frontends have become fashionable again.
Or for example enterprise architecture. We went from a bunch of monoliths with ad hoc interfaces, to service oriented architecture, to microservices, and now the trend goes to monoliths again.
There are some highly generic architectures like front end frameworks (e.g. VueJS, React, Angular) which are almost silver bullets but it's still a mistake to think of them in that way and they tend to be overused. But the fact that front end frameworks can handle an extremely broad set of front end requirements is a great example of how good architecture can handle requirement changes. It's unusual for someone to have to fork or swap out React due to front end requirement changes; it's usually an indication that React wasn't suitable for their use case to begin with. It would have to be a massive business pivot and you'd have to dump the entire codebase anyway if the requirement changes where that significant.
When discussing architecture in a broader sense, the front end is just one aspect. There have been many attempts to produce silver bullet full-stack frameworks (e.g. Meteor, SailsJS, DeepStream) and they are/were useful for a certain subset of projects, much smaller subset than what front end frameworks have achieved.
But there are many architectures that are heavily adapted to specific business domains and which are very far from silver bullets but which could be considered 'good architectures' nonetheless because they are good at handling domain-relevant requirement changes with minimal code changes necessary.
Also, it’s not just users and their environment that changes.
I have had software break, when Apple comes out with an OS update, and I had, for example, relied on a certain behavior that was not specifically dictated in the API.
The third explanation is practitioner incompetence and indifference. Regardless of whether we are talking apps or apis, it’s never been easier to keep around old versions and skip the bitrot but most people just don’t know how to do that. This doesn’t mean it’s hard, just that they’d rather screw over some subset of users than learn how to do proper versioning and releasing.
I phrase it like this because I think it’s a choice people make, and thinking about things like backwards compatibility and dependencies is seen as unsexy and just a thankless chore. If someone wants to dodge it then eventually some one else will get assigned to clean the mess. Next person will do just enough to escape the situation without really fixing it and so on.
The attitude is basically “It’s not like we’re the ones who have to use this crap! Ship it”, and it’s present everywhere from corporate to open source, projects big and small.
Part of it is a lack of pride/craftsmanship, but even senior folks are going to yolo their next release sometimes unless there’s some process in place that prevents it.
If you take any old C or C++ code, for example, it's extremely likely that it has at least one construct with Undefined Behavior. Many instances of that binary search issue had outright UB because of signed integer overflow, and most code has much more subtle UB than that.
The more time passes the more likely that UB manifests with a newer compiler, a new OS version (especially if it's Library UB rather than Language UB), a new CPU family, etc.
This is nobody's fault as such, UB wasn't well understood until compilers became aggressive enough to actually exploit the letter of the standard rather than the common intuitions. Even so, good luck finding old code that would pass modern scrunity.
In the unlikely case that a program had absolutely no defects of its own at the time it was written, it could still use a deprecated API, make a stale assumption that increasingly limits its interoperability and versatility, fail to utilize modern resources like many-core CPUs and RAM larger than entire enterprise disk arrays used to be, etc. Any number of things could merit improvement without a single bug or change to requirements as such.
Over the last few months I've been evaluating several POS solutions. They come in two varieties: fairly modern SAAS types, and super old school software (feels like it's from the nineties but the two we evaluated both seem to have been created early 2000s) with a local database that is synched online every so often.
I can tell you that while the old stuff works, it doesn't feel comfortable to use in a modern environment. One example took around 30 minutes to install on my modern workstation laptop with many steps (and the guy demoing commented in surprise how fast it was!). And that's what software used to be, twenty years ago. Enterprise software, I mean. Everything was clunky and needed a guy who understood it or at least following long complex documents and spending several hours working through the install.
Meanwhile, the SAAS varieties have zero install time because I can use the web interface instantly, or install a client app in around 3 minutes. It is much smoother and with a far lower barrier of entry. I've only dealt with the install but the UI is equally different and more intuitive on the modern apps, often with guided tutorials and setup wizards. Although to be fair, some of the modern apps are not great either. But I'm thinking about the best modern app vs the best old-school app while I write this.
Both of these styles would work as a POS system. But the old style feels very clunky and uncomfortable to use.
I still do deal with lots of clunky installs, databases, long technical documents. But that's because I'm a developer. Non-technical users are far less accepting of these things than they were twenty years ago and the software has to be upgraded to deal with that.
We chose one of the smooth and well documented SAAS options. I've been looking around at what other local businesses use and anecdotally it seems like 90% of them also did and only very few use the older style software - and maybe those have been using it for several decades.
I doubt anybody is losing customers over a 1s delay in the till opening or a POS server syncing the day’s transaction after close. But having worked in retail - the one time you get a call from head office is when there’s “loss of trading” - it’s a bigger issue than theft.
I remember there being an entire tourist town that was suffering economically because during peak season, the mobile phone tower was saturated and merchants could not process card payments. You can’t even use click-clack machines anymore with modern credit cards.
Now… working offline is entirely doable in a modern tech stack too - but I somehow doubt most modern POS products support it well.
All banking solutions - as well as the POS system - can work on mobile data and that usually is fine during an outage. The only time mobile data failed in recent years was after a 3 day power outage following a typhoon, when I guess their batteries failed. By that time our business was pretty much shut down due to supply issues anyway.
So basically, as long as the battery or generator lasts, all of these POS solutions will perform equivalently.
Edit: and to further clarify, I don't think there are any features that the old schools apps have that the new ones don't. Unless you consider a local database or not using a web browser as features (which is valid but not my view). While the newer ones tend to have a much stronger focus on accessibility (probably because they are basically web apps) and translation.
I have to install docker still, I have to configure networking to access the thing that is in the container because defaults work for demo, but in production env you still have to have bunch of stuff in front of docker.
If I have defaults for the system in database that I need to change so anything that is in docker actually works.
With docker/kubernetes I have one more thing to worry about and still lots of config that doesn’t go away “because docker”.