Different Programming Languages, Different Maintenance Costs
Not every programming language is the same
Choosing your programming language and platform determines the maintenance effort. Keeping a project working takes effort, even without adding features.
The main effort of maintenance is keeping dependencies either working or up to date. You have to upgrade your dependencies for various reasons, mostly because of security and bugs. Bugs and security problems are found in libraries you depend upon, they release new versions and you need to upgrade. Or you upgrade one library for a feature and then have to upgrade several of your other dependencies. Even without adding features to your application, there is a dependency upgrade cycle if you want to keep your product safe and sound.
Eduardo Rodriguez wrote about this in "Dependency management fatigue, or why I forever ditched React for Go+HTMX+Templ" and I agree with many of the points (I also think Go+HTMX+Templ+Alpine is a very easy and lean stack).
More general, the efforts to maintain your projects depend on
- the libraries
- the ecosystem
- the culture
- the programming language development
- the programming language standard libraries
- programming language features
Lets’ take a look at four ecosystems and languages, that I have a broader and deeper knowledge with because I have used them the past:
- Java
- Scala
- JavaScript/TS
- Go
These ecosystems have widely different dependency management costs. My experiences in these for languages range over longer stretches of time. For example, I have used Java from 1996 on for around two decades. I have used JavaScript from 1995 until today, sometimes more like as a frontend application and as a backend ecosystem around Node, sometimes less so to give some interactive touch with Alpine. I have used Scala as the better Java for some projects over the years. I have used Typescript in the startup of my wife and have been using Go for some years now for several private and open source projects, most lately it powers my startup Inkmi - Dream Jobs for CTOs. So some of those experiences are fresher and some are older, and might be outdated. The good and the bad in the examples are not complete. I want to use them to illustrate the point not to judge programming languages.
Java (~ 15 years ago)
- Bad
- Interfaces, you can’t change them without breaking all code that implements them
- Exceptions, you can’t add/remove them, without breaking all code that uses the method
- Good
- @Deprecated annotation, stretches out changes to several versions and gives warning for upcoming work
- JDK (Standard libraries) moves slowly, uses @Deprecated to make slow changes
- Language changes happen mostly in compatible ways to existing code
- Ecosystem and libraries move very slowly, do not eagerly adapt changes to the newest Java versions, keep compatibility with older Java versions
- Result
- Barely any problems when upgrading libraries, low effort to keep a system working for years
JavaScript/TS (~5 years ago)
- Bad
- High turnover of libraries and frameworks with incompatible changes (I look at you React) - there is often a different way to do things in newer version
- Frameworks have incompatible changes (See Angular)
- Good
- You can often use newer JS (TS) and compile it to older runtimes
- Result
- Large effort to keep projects working, because of major changes in dependencies
Scala (~10 years ago)
- Bad
- Culture of libraries and frameworks to use new versions immediately
- Radical changes in standard library
- Incompatible binary formats (class byte code) so you couldn’t use a library that had been compiled for a different Scala version (you depend on libraries for cross compiling) - older libraries often not cross compiled for newer Scala versions and other libraries not compiled for older Scala versions #Catch22
- Result
- Huge effort to upgrade dependencies, several times it took a whole day to just get everything working again from a library update
Go (now)
- Good
- Compiles to binary, binary format is the system format, not bytecode of any kind
- Programming language moves very, very slowly
- Errors are just strings, rare use of Sentinel errors
- Result
- Very low effort to keep projects working, upgrading dependencies is a routine task that most often just works
If upgrading dependencies is a long, risky and tedious job, developers will avoid it. This leads to older and older dependencies, to the point upgrades are stop-the-world for weeks. In high pressure environments like startups, this is the norm not the exception. Several of my clients over the years had been stuck on old dependencies, one notorious one was Angular.
I think of dependency management as signal vs. noise. Developing features is the signal, just keeping a project working is the noise. The larger the noise, the more difficult is it to have see the signal.
Developers underestimate the maintenance efforts of programming language platforms and ecosystems. As an engineering manager it is your job to make sure, the money the company invests in development (mostly developer salaries) is spent wisely. Spending effort to just keep the project working need to be offset by huge winnings in other areas - something often hard to demonstrate.
There are many criteria for choosing a programming platform, the effort to keep your app running is one of them.