Designing Low Upkeep Software
jefftk.com
jefftk.com
Like, why don't we just let projects be "done"? Things don't need to be maintained and updated for eternity.
In my mind, the best software engineering is where you solve a problem once and your solution just works and needs no configuration or maintenance or updates.
This of course has a very low chance of happening if your system has to exist as a part of "ecosystem" where you expect/assume the presence of some external service that can change its API on a whim (or just disappear).
Yes, to a certain degree it's impossible to design software that does not exist as a part of an ecosystem, which is why I put it in quotes.
Some APIs are stable and are guaranteed to continue to exist for a very long time: CPU instruction sets, networking protocols (IP/UDP/TCP), operating systems (AFAICT: Linux (the kernel) works hard to not break user programs, and Windows is kind of known for bending over backwards to maintain compatibility with old programs), file systems, etc.
What I'm advocating for here requires that your program be compiled into a native executable binary file, and it must embed all its library dependencies (aka static linking).
2) Static linking does a good job of protecting against when upstream introduces new bugs or security holes.
I like writing low-dependency software, but what that means is that I pick a distribution to build on and use their packages. If a library is not packaged, it has to be really good for me to depend on it.
Sure, it might break once in a blue moon depending on how stable the distribution you use is, and you'll need to refresh your stuff every 4-9 years when a new version of the base OS is released and the old one goes EOL, but most of the time the result will be extremely low-maintenance.
I get this desire, but likely it guarantees more work for yourself and probably more undiscovered bugs, because with only one set of eyes, to invert Linus’ law, many bugs may lurk deep.
I mean, are you speaking from experience, or just theorizing?
Here's something to keep in mind: when you depend on a huge library that has 40 features, it's likely your program only needs 5 of them.
So, while the huge library benefits from "many eyes", your particular program would benefit much more from not depending on a library with a lot more features than you need.
Also I don't know if you've seen how open source works, but chances are the number of people actively looking at the source code for these libraries to uncover bugs or security holes is very small to non-existent.
Sure, you can limit them, but you can’t live without them. That’s not theorizing, that’s the whole industry’s collective wisdom.
> That’s not theorizing, that’s the whole industry’s collective wisdom.
There's no wisdom there. The industry is too young to have any meaningful wisdom to speak of. It's mostly fashion driven.
> An OS is a dependency. A GUI library is a dependency. A database is a dependency.
Well, I did make a distinction in my original post about stable dependencies, and I included the OS as a stable dependency.
I also never said you should have "zero" dependencies. My point is about minimizing dependencies as much as you can.
> Have you actually tried to reduce dependencies?
> I mean, are you speaking from experience, or just theorizing?
I asked this question because your comment does not jive with my experience. So before countering your point I wanted to confirm whether you had some experience that was opposite to mine or not.
Where is the condescension? In my mind I'm being polite and not making assumptions.
If I wanted to be condescending I would tell you that you have no idea what you're talking about.
“Have you actually tried X?” is condescending, because it places you in a position of superiority, framed as an “honest” question. If in your mind that is a polite and honest conversation, your mind is clouded, as me an other people have now let you know.
This is generally why I opt for "single-file" libraries that do one simple task well. The smaller the library, the more likely it is "done". For example, do I want some insanely complex image library that handles every file format under the sun, or do I just want some basic one that allows me to output a simple JPEG?
I often find myself referring to "single_file_libs" repository: https://github.com/nothings/single_file_libs
Looking at the open issues, it doesn't appear to be actively maintained but it's still an incredibly good resource for "completed" projects.
I'm not sure if this is intentionally ironic or not, but it does seem like if your small libraries aren't getting updated regularly because they are done, you at least want the meta library (in this case the single file libs repository) to be updated regularly with new small libraries.
It's the meta-library collection repository I was talking about, not the single-file libraries.
There really is nothing that can be considered completely stable.
Maybe they used to, but good luck running anything made for Windows XP on current version of Windows. But you will have a better luck if you run it with Wine on Linux.
I was genuinely pleased for the client as they are nice people running an essential service on a budget that is always under threat, and this result means not needing to pay for a silly 'fresh' version of the application.
I would point out, though, that even when you can just continue to use a single pinned version of a Docker image forever (because the program itself is stable, or is part of a stable system that only uses the program in precise ways), people still value regular releases, and see non-upgrading images as “rotting” — because they can contain vendored deps, and those deps can have security vulnerabilities discovered in them over time.
At least with Docker, when you write or download a docker file for the first time, it's just a description of stuff that needs to be downloaded and maybe commands that need to be executed.
This kind of thing is qualitatively much worse than a statically linked executable binary in terms.
> a stack that can create standalone static binaries.
Yea, a language that has the notion of a compiler is a MUST if you actually care about this kind of thing.
But, I agree with your sentiment. Endless feature bloat is not necessary for vast majority of software. The expectation that every software must remain updated forever is just not sustainable.
This a heuristic that generally works. Most projects that don't have recent commits really are abandoned. And when you open a project on github, one of the first things you see is when every file was last updated. Maybe github should offer other heuristics, or of it already has them, display them more prominently.
People are too quick to assume that all Github repos are meant for public consumption. Can only speak for myself really but most of what I've pushed there was designed with only me in mind. If others find it useful that's nice but don't expect me to accept pull requests.
This is what open source is really about for me.
Adoptopenjdk comes to mind. https://adoptopenjdk.net/
Mine will always contain: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ...
And then maybe we should add something about that people can actually use it if they want to: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files ...
Should try to start this is an effort for real!
It's also not like it's a lot of work, a 30 seconds one-time investment in my time is less time than I spend on most HN comments.
The page says:
> Open Source is rewarding- but it can also be exhausting.
> The linking project’s code is provided as-is, and is not actively maintained.
Basically the attitude here is "I'm tired! Leave me alone!".
The attitude I'm advocating for is "This is feature complete! We only update this project for bug fixes or security patches."
Also the website automatically handles payments and sending license keys.
The only work I do is when PayPal integration breaks because they make some minor change on their end.
Unfortunately, Windows 11 removed the IDeskBand interface I rely on so I might actually have to do some work.
Protocols change, programming languages change, human languages change, boarders change, definitions of time change, laws change, sensors change, and on and on.
It's like asking why a person can't be just done learning so they can live their life. Well, they can, but pretty soon the world is inaccessible to them. They can't use a computer or a phone because they stopped learning in 1995 and they're utterly dependant on others to do things for them.
But I will say this, I've long been thinking that there should at least be a programming language and OS that does its best to not change. A sort of whole environment where every single piece that's out of beta commits to minimal interface changes over time. Fixing security bugs and supporting new emojis, fine. We have to do stuff like that. But everything else is just as frozen as can be. It would be useful for super long term software. If we have buildings still around from 100 years ago we should be able to build for 100 years from now without a team around for constant maintenance. Though I do not think it can ever be maintenance free.
At the risk of this being interpreted as trolling; don't we have this, for crucial OS interfaces at least? Linux is famous for "we do not break user space" (yes, I know it's not 100% true, but it's closer than anything else I've seen), and afaik the posix standards are pretty stable too.
The C language also has a stable ABI, which is basically the ffi for most languages, and most static libraries that were developed decades ago can link just fine.
In my mind, the problem is caused by at least two things:
- Languages that are not compiled to machine code have no incentive to have a stable API or ABI. This has the effect that code reuse now necessitates replicating the state of the machine it was developed on (Which might be using a different language version, runtime, etc).
- Programming culture has progressed to a "get it done quickly, just grab a library" culture. This is not to say you should develop everything in-house, but on a spectrum, I have the hunch that this easy accumulation of dependencies induces a culture that does not vet the stability of the dependencies. Once one of your dependencies is unstable, the project on which it depends cannot be stable.
you've heard of this thing called Windows?
Most 10 year old games still runs on modern windows even without patches etc.
[1] http://ptgmedia.pearsoncmg.com/images/9780321440303/samplech...
Yes, it says "now," in command form. And the app, VirtualBox, works just fine in Windows 10.
Doesn't seem very backwards compatible these days.
I imagine COBOL is similarly stable but I have no experience there.
Minecraft, in particular, has always worked fine with Java 9+, ime: if you know the flags to apply (and delete the jar that checks the Java version), it more or less just worked. I’ve been running it for a year+ on new JVMs so I could use the Shenandoah GC and take advantage of more of the 128GB RAM in my desktop.
This is the last step before they get fully removed.
I haven't programmed in COBOL, but it is quite stable. One of the really nice things in COBOL is the "Environment Division", which has a Configuration Section, which provides information about the system on which the program is written and executed. It consists of two paragraphs − – Source computer: System used to compile the program. – Object computer: System used to execute the program.
Another remarkably stable language is Ada, I have used it to compile non-trivial programs from 30+ years ago, on a different architecture than it was originally written for (granted, without system dependency) using a modern compiler without anything more than (1) renaming identifiers which had been made keywords, (2) splitting files due to GNAT's implementation limitation regarding compilation-units.
K&R C won't compile in modern compilers, not is allowed as per ISO C2X upcoming standard.
gets was removed.
And if you are using optional Annexes, they might not even exist in ISO C compliant compilers.
Similarly, that Java code will die if it uses internals made private in Java 9, inherits from JDBC and has methods with names that were later added to more recent versions, uses deprecated methods that were finally removed around Java 10 time,...
For crucial OS interfaces, yes. For the ecosystem of libraries and packages, no. But ultimately Linux is more than just crucial interfaces. The ecosystem of applications and libraries that we need to get anything really useful done does constantly change, and it would be nice if there was a OS + programming language + culture for "forever apps" that are designed to work for centuries without a material risk of an auto-update breaking anything.
Sort of how Rust is designed around safety, that's what would be nice. I know it wouldn't be perfect, for the reasons I listed above, but for the areas where we are at least trying to have things work for good I think it would materially help.
I'd argue that you can get lot of useful things done with plain POSIX/C
> a OS + programming language + culture for "forever apps"
Isn't POSIX + C exactly that? Sure, not many people stick within those bounds, but those who do tend to care a lot about not breaking stuff.
C is honestly terrible to target/use as FFI, doing so precludes doing things correctly, or more advanced things like... say arrays that "know their own length" or numeric-types that are range-constrained.
There's a fair amount of C code that does just that and does it for a long time.
>or numeric-types that are range-constrained.
Those don't need astral and can be passed through C ABI just fine.
>
> There's a fair amount of C code that does just that and does it for a long time.
No, there isn't.
There can't be because of how arrays in C degenerate into pointers/addresses; see Walter Bright's "C's Biggest Mistake" -- here: https://www.digitalmars.com/articles/C-biggest-mistake.html
>> or numeric-types that are range-constrained.
> Those don't need astral and can be passed through C ABI just fine.
No, they can't.
If you're passing a "Positive" through C's ABI you lose the fact that the value can be neither negative, nor zero. (Unless you mean "passed through" as in, "not mangled", but this is setting the bar so low as to be laughable.)
The parallel to breaking working code while updating it to follows modern standards isn’t too far fetched.
I don't know if the British were just exceptionally bad church builders and contemporary European churches are doing just fine but I would hazard a guess that no, they also require spectacularly large amounts of maintenance to keep the weather out too.
you can fire up QBasic on dosbox just fine
You only need to update dependencies that interact with things that change autonomously.
For the types of programs I write, that describes very few, sometimes none, of my dependencies.
Continuous updating of dependencies is valuable in some circumstances. Not all.
Believe it or not this is a core design goal of the Urbit ecosystem. The core bytecode should be simple (and stable) enough that some future archaeologist can implement an interpreter in a weekend and run programs. They take it so far that their version numbers run _backwards_, with the idea that once they reach version 0 nothing will ever change again: https://urbit.org/blog/toward-a-frozen-operating-system
I have 17 years old Windows desktop product that I've long abandoned. But it is exactly this: single exe with everything compiled in. It uses DirectX 9 and Directshow. It still runs fine without any tweaking. It even compiles fine from source code. So from a practical standpoint it is maintenance free.
A passable implementation of Lorie's UVC concept[1] is sitting right in our lap, it's just hardly ever put to use in this way, and it's considered by some not to be very sexy—or rather, it's popular to dump on it for various reasons. But you're probably using it right now to read this comment, and the reason is that its availability and reliability is a foregone conclusion by every stakeholder in this conversation—whether that be me, you, or our hosts involving Y Combinator &co. And with respect to the article, it's a foregone conclusion that it will also be involved in the tech tree of the example projects given. Jeff even mentions its longevity explicitly—in a comparison to Python, where Python is considered the worse of the two. How about just not depending on Python at all?
If you click through to Jeff's "makerss.py" script and look at the kinds of machinations that it does, consider whether this stuff could instead be taken care of directly in a "README.html" in the repo root. No implicit[2] assumptions about what software is installed on the machine in question—since it's using the UVC after all—and none of the Python 2/Python 3 fuss. (It'd have to forego the symlinking that makerss.py does near the end (currently line 1717 and 1719), but that should arguably already be handled elsewhere, anyway.)
I'm not sure what you mean?
> If folks were really committed to improving the developer experience, then instead of what we do now [...] development would work like this: ¶1. Download the project source tree ¶2. Open README.html ¶3. Drag and drop the project source onto README.html
If this is still unclear, please let me know. (Please also let me know if it is clear. I'm very interested getting this explanation "right", so it's easy to understand.)
Seeing no updates in 3 months is one thing. Seeing no updates on a project that describes itself as a work in progress is another thing.
See this: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.26....
I mean, kind-of. One of the problems with CI/CD though is that they're poorly designed for the job they do; see this: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.26....
The TL;DR here is that by structuring your code and storing it in a database structured hierarchically you can solve CI/CD in a MUCH nicer manner: your root node's history becoming a history of the project in a compilable state.
On the other hand, there are the big libraries that, for convenience, pull one or two small things from several others -- their surface area becomes so large they are practically guaranteed to generate more work in terms of security updates, dependency conflicts, and the like. I experience this at times developing in Clojure, whose hosted nature means issues can emerge from the Java ecosystem, which is obviously enormous and dynamic.
We recently had a defect at work relating to a conflict between transitive (Java) dependencies from a third party library used to serialize exceptions prior to transmission to their service, and another tool used only for generating "literate"-style code documentation. While not a production defect, it cost us some time and could have been avoided (both by us and the libraries' authors) by more hard-headed evaluation of the tradeoffs involved in introducing certain dependencies in the first place.
Don't tell me that Lisp ever achieved cool-kid status that a thousand different libs wouldn't pop up basically overnight.
I’m doing a big embedded project and all the code is self contained! The only inputs are physical user inputs.
Network protocols? No, HTTP/3 is here.
OS? Kernel, sure, but everything else no. E.g. most Linux OSs recently changed their entire init system.
CPU architectures? ARM was irrelevant a few years ago. Faster CPU instructions are arriving every year. You say, "okay, well just recompile." Which brings us to....
Build tools? These are a nightmare to make stable in the best of circumstances.
It's all fiction. Code rot is real.
A lot of the things that you describe as changing don't really need to change.
Operating Systems? Windows XP was probably the best windows, and it's from nearly 20 years ago. Looking at Windows 10, what exactly does it bring to the table? It's a much worse experience overall.
Compilers don't need to change either.
If yes, just what the hell are you making?
As for the second, code can still work and yet be obsolete.
The argument is simple. The world changes all the time and most software interacts with the world. Therefore, software needs maintenance in order to keep up with the changing world.
However, my experience is that the building blocks for making even large software systems are increasingly more stable and Debian GNU/Linux is a prime example of this.
Consider biological systems: they're constantly evolving, constantly in flux - and yet, all living things we can see with unarmed eyes are fixed in the scope of a human lifespan. Most of those don't show meaningful changes withing lifetime of a human society. Cooking recipes of our great grandparents are still valid today, because fruits and vegetables and meat didn't change that much[0] over the last century. Wood still works the same way it did 1000 years ago.
Being able to change much faster is good - as long as the changes tend to lead in useful direction. Software seems to be changing way faster than it's needed, because it's changing for the wrong reasons - instead of improving the value it provides, it's usually about one-upping competition, or forcing recurring payments, or forcing competitors to waste money (e.g. by introducing incompatibility on purpose). Software industry resembles a rain forest, in terms of both diversity and frequency of senseless murder (er, "survival of the fittest"). But as humans, we've dominated this planet by being smarter than that, by being more efficient than natural evolution.
--
[0] - Despite being optimized directly by our industrial processes.
I suppose then that a benefit of pure functional programming is that you can statically analyze whether a given piece of software is plausibly "done" or simply unmaintained, depending on whether and to what extent it uses monads.
As the Buddha taught, desires are numberless. I don't think I've ever even heard of a piece of software that hit 1.0 and people were like, "Welp, it's perfect, never change a thing." I've certainly never worked on one. Indeed, the biggest sign of project success to me is releasing an initial version and people being shocked, shocked at the obvious features I missed. And I love it, as that's proof that they're really trying to use it for their real-world needs.
I agree with this but
> native executable binary file
This is hard because new architectures, instructions, optimizations show up every few years.
the super example of this is justine's project
https://github.com/jart/cosmopolitan
it's pr fucking cool
> Cosmopolitan Libc makes C a build-once run-anywhere language, like Java, except it doesn't need an interpreter or virtual machine. Instead, it reconfigures stock GCC and Clang to output a POSIX-approved polyglot format that runs natively on Linux + Mac + Windows + FreeBSD + OpenBSD + NetBSD + BIOS with the best possible performance and the tiniest footprint imaginable.
In that time, the service-oriented code which has significantly outlasted anything else, with nearly no maintenance, has maybe surprisingly or unsurprisingly been: AWS Lambda functions.
We have quite a few Lambda functions (written in Node) that I believe pre-date even my tenure at the company; the only modifications in the interim, maybe a few lines of obscure bug fixes, updating the lambda node version, we switched from JS to TS everywhere a few years ago, and keeping the CI updated on those repos (which is a rather ironic and wasteful investment of resources given they've only changed a half dozen times in something like six years).
Checking in on those dozens of functions handling millions of invocations every month, and seeing them just chug along with no maintenance, has convinced me, personally, that if a problem domain can be solved in a FaaS-like way, its the way to do it. Not every problem can, and I'm not specifically advocating for Lambda vs Azure FaaS vs an open source kubernetes-based solution.
More-so that, every deployment artifact has to draw an "operations" line, to the left of that is the rote, daily operations banality, and to the right is the ever-changing business problem space. The further right you can draw that line, to push as much as possible into operations, the better; it gives you optionality to outsource that left part, maybe to automated software, maybe to AWS, maybe to an in-house operations team. FaaS draws that line roughly as far right as I think a general purpose programming system can; further right would approach things like Excel, Retool, etc.
- scope constrained
- surrounded by APIs only (e.g. no cheating by having a whole POSIX system)
- infrastructure is maintained solely by a giant company that you pay to not have to think about it
I haven't used any "serverless" stuff, so I don't really know.
Per the article's recommendation, I have noticed at work that resilient software is often a 400 line python script with no other dependencies; or, a medium sized piece of C++ infrastructure that depends only on the OS, the compiler, and a hand-written build system.
Please don‘t. It will be fine as long as a project is very small, but get out of hand quickly. Rewriting the build system after the fact will not be budgeted and likely never happen.
Interesting, AWS lambda only launched in November, 2014[0]. So those more-than-six-year-old functions must have been written for a brand new platform.
Some of that can be blamed for misusing lambdas, but I hate FaaS concept. AWS ECS, google app engine and k8 where you deploy container are imo better and give you more control.
Edit not hundreds but probably close to 70 in total.
really though, in my opinion that should be a part of the core AWS solution. Lambda itself is otherwise "low level".
I agree Lambda is too low level, but it probably also explains its longevity.
The name “serverless framework” is idiotic and not googleable. It is like naming server side framework “api framework”.
In my opinion, Lambda should only be for small, event based functionality between services. People that sold the idea of full-fledged app development to clueless architecture astronauts should have a special place in hell.
I'm no fan of Serverless Framework either, but accessing the generated CloudFormation template which will be used to deploy the AWS resources is actually pretty straightforward:
sls package
cat .serverless/cloudformation-template-update-stack.jsonI don't think I can only credit postgresql functions with the low maintenance though. I suspect that the additional overhead / time to write them has forced me to really make sure they are written correctly (and yet I'm sure there's a lot of optimization I could still do).
Certainly, complex C code is a risk across OS versions but ... perl5 version 8.1 was released just over 18 years ago and after multiple years of annual major releases of perl we're now on version 34. Many of my chosen cpan dependencies produce the exact same output when on a 5.8.1 install as they do on a 5.34.0 install, and I can move them to a new machine/OS via rsync or tar of the local::lib structure (virtualenv like thing configured by setting a couple of environment variables) and it Just Works.
Am I weird here? Is the author? Is python unusually bad? Is perl unusually good? This all seems quite strange to me.
I have a long standing semi-shitpost opinion that if faced with a choice of technologies, it's often best to pick the one that's been "declared dead/dying" because that means it's stable and in wide use.
Meanwhile, I have Perl programs deployed that have been running with no changes for over 15 years.
How is that an additional dependency?
At this point, this is nitpicking.
Tkinter I would understand, but not sqlite. Even django doesn't work without it.
Also the FreeBSD Python package doesn't come with sqlite by default.
If you do that, you are assumed to have removed the safety belt and know what you are doing.
I know we are on HN, but you have to remember half the python coders are not even dev. They are geographers, mathematicians, biologists, students, teachers, data analysts. And of course they are on mac or windows.
Even I, and I started to code in python 2.4, never have to compile python. I do it for fun from time to time, but I don't use that professionnally.
If you use a platform where sqlite is not there, you are an exeception in a niche of minority.
I drive my car assuming there will be breaks on it. It can be missing, but it's not a great assumption.
Pyenv is not a "common tool". It's used by a very small minority of devs since:
- it's used only by dev, and half of the python coders are not devs
- it works only on unix. Most people install python on windows (https://discuss.python.org/t/python-download-stats-for-may-2..., and that doesn't take in consideration anaconda or the appstore). Unless you count the obscure fork used by even less people.
- And yet on mac, if they don't use the official installer, they use mostly brew, not pyenv. On Linux, the officials repos or things like deadnsnake or epel. And of course docker. Pyenv is a tiny fractions of that, because any alternative solution is better known and easier.
- hard to use because it compiles things, fails easily in non obvious ways and assume you manage the path correctly, so it's really, really, popular only with people that want to deal with that. In fact the setups instructions are huge: https://github.com/pyenv/pyenv#how-it-works
- that's just what we see in the field. I go to a lot of different shops every years because I'm a freelancers, and I encountered pyenv twice in 10 years.
> That it happens to compile under the hood is an implementation detail that users may not even be aware off.
Of course not. The setup docs themself tell you to "Install Python build dependencies before attempting to install a new Python version.".
So not only you have to be aware of it, but you must be capable of setting up a compilation environment, and if you fail to do so, it will not work.
pyenv is not standard python. It's not "a common tool". It's a tool for a niche of specialists with domain specific knowledge.
Yeah well that just means mindlessly copy pasting a command into your terminal before moving onto the next step in the installation instructions. But now that I read what the command does, it turns out it does install sqlite so yeah..
The initial argument implied that SQLite was totally embedded in Python itself, which is not quite true. It still depends on the availability of separately-maintained & compatible SQLite libraries.
I'm surprised that some developers think constant breaking changes is "fun".
Following those two points, I've been able to use whatever languages/tools tickle my interest with little impact on maintenance effort. Dependencies are few, but not minimal, pinned to exact versions. FYI, these have run the range of Vue/TypeScript, Node, Go, Java/Kotlin, Clojure, F#, and Php, with typically MySQL (sometimes multi-master).
Great advice. Minimise the energy barrier to being able to ship a patch or a new feature after neglecting the service for months/years and forgetting how you set everything up.
I always have a bit of a laugh after patching my hobby project and doing a handful of iterative automated deploys while making small changes, then switching back to the day job where aspiring to do a single deploy will typically require hours of meetings and general bureaucratic overhead.
Another possible tip is: understand your deployment environment and learn how to take advantage of what it offers out of the box without adding additional layers or dependencies. E.g. I deploy a hobby webapp to my own debian server. Debian comes with apt for package management and systemd for managing services, so I use both of those (yes, even apt for python package dependencies).
My python code without dependencies just works. My ruby code breaks all the time. My Java code never breaks but my Java code has very few dependencies.
I wish there was a way to abstract the interface of mechanism in a promise that other people can implement and evolve and outsource decision making to someone else who has more skill or nuance in a way that is looser and more reliable than a particular API.
APIs want to evolve over time but a promise to fulfil a high level goal is different. An API is a mechanism like a particular bottle whereas a bottle mould captures the essence of a bottle but is not a bottle itself. Interfaces (such as in Java) are just APIs themselves so they don't really solve the problem. For example, I want to specify routes that correspond to the URL of my React app or I want to encrypt data with the most secure algorithm. These two example mechanisms evolve all the time. I kind of want to abstract the intent and input. An API doesn't have to be a function call, it can be a data structure. The mechanism doesn't matter to me, but I want the mechanism to adapt with evolution. How do you decouple the method/mechanism with the goal? Evolution in nature tries to find a variable to mutate that can produces better survivability. Like higher body temperature in cats which must have an evolutionary advantage for the mutation to have survived.
>On the server I run Ubuntu LTS until it's near the end of its supported lifetime. Every two years I run do-release-upgrade and move to the next one. This cost is shared over many jefftk.com projects, so it isn't too bad.
I was surprised by this part because running your own server seems like a big maintenance burden. I'd expect that you have to regularly upgrade your packages to protect yourself from security vulnerabilities.
I've avoided this by using PaaS solutions like AppEngine or static hosting + cloud functions. The closest thing I have to a VPS is publishing Docker containers, but that's basically my last resort.
[0] https://mtlynch.io/projects/
[1] https://mtlynch.io/retrospectives/2021/09/#legacy-projects
There's few user space packages I imagine even benefiting from a reboot. Restarting a long running daemon is pretty painless. Also on a warm running system you've got an active page cache so all the unchanged files that service wants to open are likely cached in RAM rather than read from disk.
While I appreciate the flexibility low effort VMs provide I think they've negatively affected the views of Linux users. Long uptime is fine and restarting a whole machine is rarely necessary. At the same time modern systems with crazy fast SSDs make a reboot take little longer than just restarting a service.
Lots of hardware servers spend several minutes initializing themselves on boot. a RHEL 8 VM on a reasonably powerful host reboots in less than 5 seconds; a service restart causes approximately the same downtime that a reboot does.
$ apt install unattended-upgrades
# /etc/apt/apt.conf.d/99unattended-upgrades-custom
Unattended-Upgrade::Sender "Root at servername.domain.tld <servername.domain.tld@servicesdomain.tld>";
Unattended-Upgrade::Mail "services@servicesdomain.tld";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "05:00";
$ sudo systemctl edit apt-daily.timer
# Opens a new file /etc/systemd/system/apt-daily.timer.d/override.conf, paste this content:
[Timer]
# Reset the system calendar config first
OnCalendar=
# Set a new calendar timer with a 60 minute threshold
OnCalendar=*-*-* 03:00
RandomizedDelaySec=30m
$ sudo systemctl edit apt-daily-upgrade.timer
# Opens a new file /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf, paste this content:
[Timer]
# Reset the system calendar config first
OnCalendar=
# Set a new calendar timer with a 60 minute threshold
OnCalendar=*-*-* 04:00
RandomizedDelaySec=30m
.. and are auto-updated. When a reboot is needed after an update, it gets rebooted automatically.The second backend that I bootstrapped on was x86-darwin. That worked great for about 7 years, until Apple decided to deprecate first 32-bit, and now, I guess, x86 altogether. Thankfully I ported to x86-linux soon after, as that syscall ABI is solid as a rock.
But you know, things keep changing. To keep up, I finally had to bite the bullet and write an x86-64-linux backend. And I guess at somepoint now arm64 if I ever want to work natively on a mac again.
It's an endless cycle. Thankfully now, emulators are pretty good, but I am still struggling with getting QEMU to run fast on MacOS on the M1, so it's just more futzing with enormous emulation stacks just to run old stuff.
We use SQLite for nearly 100% of business object persistence in our product today. The performance & scalability arguments fall on some very deaf ears after years of experience optimizing and succeeding with this stack in production.
[0] https://www.sqlite.org/fasterthanfs.htmlNumber of reasons why. Most don't stay static, though. People mod them. They need to be something that can be handed off to a new team.
I find documentation is key.
I write about that here: https://littlegreenviper.com/miscellany/leaving-a-legacy/
I've never touched it again and I've found out recently that I now have a user base there and everything still works :) my longest living software ever was made in a couple hours. A little frustrating, but awesome nonetheless.
I use this to my advantage by writing software AS IF it has already been working for 20 years, meaning I use only languages and technologies which have not introduced breaking changes in 20 years or so.
It's worked out quite well so far!
I use ASCII/textfiles, HTTP, CGI, SSI, access.log, Perl, Bash, and small subsets of PHP, HTML, JS, CSS, and almost no third-party libraries. Certainly no JQuery or anything similar.
I'm not hardcore enough for C or C++, but I know that a program written 20 years ago in these languages would most likely compile today.
Not without continuous attention over those 20 years.
I'm currently struggling to get a C program last touched 10 years ago to compile.
I also have an (abandoned) website using a markdown to html converter. I've switched converters two times because they eventually broke over the years, and finally moved to plain pandoc + script the last time. Even then, the CSS somehow broke and I haven't fixed it yet.
Maybe my next project will be to convert it to html-only and actually add updates instead of just dreading to pick it up and find another broken tool.
Also +1 to using files as a DB for small-scale projects. Something I've done a few times is keep a single in-memory JS object as the source of truth, and just write it to disk as JSON on a certain cadence/read it from disk at startup. It's wonderful having your little one-off server not need to be a distributed system from day 1.
A simple integer or a date would work just as well and would be much easier to automate.
Things like this are best explained by a very grey neckbeard who's been through a lot.
Author is not particularly old, author programs in python (something that has broken compatibility once)... "stick to the standard library" is about at ground level an opinion on this subject that a typical HN probably thought of as they clicked the link.
1) need a stable, mature, secure language
Stable eliminates Rust, Python, Perl. Go hasn't been around long enough.
Secure eliminates C, C++, probably D and most compiled languages
No dependencies: yikes there goes Java because of the JVM dependency.
Assembly isn't portable.
...Seriously, are we left with Ada? Secure, strict, mature, reasonably portable. And virtually unknown outside of government/military.
2) UIs age, but CLIs really don't
So make it a CLI, and use unix philosophy
3) OS churn
Hopefully with the practical cessation of gigahertz scaling, the industry will settle down. Then again, the ARM hordes are approaching to wipe out all the x86 stability in desktops and servers.
Perhaps the best thing for this in the industry has been Windows subsystem for Linux. If we could get an OSX subsystem for Linux, that would be a real boon.
Even Linux on it's umpteenth release is still changing quite a lot, and NIH reinvention like Wayland and Systemd and toss-it-all-out Gnome 3. Well, it kind of keeps us employed. I'd adhere to assuming a unix-ish os, like maybe POSIX.
So: Ada, CLI/text, POSIX compatibility. That's not limiting at all, is it?
Sigh.
> Stable eliminates Rust, Python, Perl. Go hasn't been around long enough.
> Secure eliminates C, C++, probably D and most compiled languages
> No dependencies: yikes there goes Java because of the JVM dependency.
Common Lisp, then?
I loved its bare-bones UI. It was definitely one of the most usable webapps. I assume its a11y was nearly perfect as well.
If anyone would like to see it, here's an archived snapshot:
https://web.archive.org/web/20170622033104/https://www.jefft...
I also admire https://diskprices.com/ for the same reasons.
https://github.com/jeffkaufman/webscripts/blob/b6a004790b464...
Writing something maintenance free should be done in a compiled language.
I agree. I could have been talked into getting onboard with Python for long-term low-maintenance projects if only they didn't burn everybody with the backwards compatibility. I understand of course why the changes were made, but it doesn't make it any easier. It's not impossible that 3 -> 4 will do the same.
I was wondering the other day whether something that is already dead might make a better scripting language. For example, you haven't got to worry about any random changes to the COBOL language! I suspect something like JS is pretty much at maturity now with WebASM poised to replace it.
Would a wordpress blog really be harder to maintain then this? If so, I think a better solution would be a mailto link, so that the email contains some basic text like this:
> My comment on blog.com/article3:
> ...
Then maybe you could automate adding the email as a comment to the article in some way.
And sometimes someone will add a new feature, and include an entire library for that single method call, instead of trying to write a little JavaScript.
I almost think there should be a non-trivial process at most businesses for including new libraries so that web devs would try to do it without adding a new library, so they didn't have to deal with red tape!
Three.js, one of the most popular JS libraries, has the explicit policy of ignoring backward compatibility.
https://github.com/mrdoob/three.js/wiki/Migration-Guide
I find it not only irresponsible but arguably mean to other devs time and lives but apparently I'm in the minority.
Every time I have to spend an afternoon or evening updating stuff instead of doing something new or visiting friends etc I silently curse the devs
You could choose not to use such libraries, but as long as they're up front about it I don't see how you can hold it against them.
It's not just time using the library. Every volunteer that wrote docs or a tutorial their work is now out of date. every answer on S.O. is more than 3-4 months old is wrong. Every new user now has the burden of trying to learn where any content they find is practically guaranteed not to work correctlt with the current version.
it's mean in the extreme to piss on so many people
I'd argue Three.js beats native to make low upkeep & long lived end user (=portable) 3D apps, given all the churn, constant driver breakages necessitating new workarounds, proprietary-ness and fragmentation in 3D APIs. With Three you update to newer versions on your own time (if ever) and stuff doesn't break from under you. And in the decades long view WebGL 1 / WebGL 2 / WebGPU is used behind the scenes.
[1] example: https://github.com/mrdoob/three.js/pull/21777
For client-side I can just throw together some html and javascript to make a simple app. But if I want to use javascript on the server it seems like I need to download a bunch of npm dependencies to do anything useful.
The problem is that Node's http lib isn't super productive and you gotta write some "solved" stuff like cookie handlers. Also, there is no database standard library. If you want to persist data, you could use files. If you need a database (even SQLite), you gotta use third-party stuff.
In comparison, PHP has a lot of stuff build-in and you can be more productive with zero dependencies in PHP.
But then, maybe it doesn't really matter if you include a well-maintained npm package or if you have a more powerful standard library that has some features deprecated after a while. It's still "other people's code", although npm packages require some kind of installation, whereas a standard library usually doesn't.
[0]: https://www.section.io/engineering-education/pure-node-js-no...
I used to be more on the minimalist side, but over time I have switched to the opposite, today I can really appreciate the bloated nature of PHP, everything from large projects to small scripts just becomes easier to handle.