The "right tool for the job" fallacy
A famous saying among engineers goes
Choose the right tool for the job
and I wholeheartedly disagree. I would even go thus far as to say that this can be a dangerous attitude in many cases.
If an engineer tells you "choose the right tool for the job", they basically want to say "pick the programming language / infrastructure component / framework / library / … which is best suited to solve the problem you are targeting". And this is just wrong: There might be technical tools that are perfectly tailored to solve a certain problem and you should still not use them.
Down the Rabbit Hole ¶
Let's dig down the rabbit hole and see what comes with settling on a new programming language for your new project or product (or even just a service for an existing product or project).
Example: "Ballerina" language ¶
The Ballerina language is a rather new, open source programming language (released in 2022) that is advertised as a "cloud-native programming language optimized for integration". It should be especially useful for building APIs, micro services, web back ends etc.
I use this programming language as an example here, because it could very well come up in an engineering team as the right choice to implement their brand new web application project.
Disclaimer: I have never used Ballerina and did not even experiment with it. I'm sure it is a great piece of software and this article is by no means meant to discredit it. It is just a random new programming language that serves as an example here.
You can use any programming language / framework / infrastructure component / etc. that is fairly new and unknown to your team as a replacement.
Language Learning & Ecosystem ¶
I frequently heard the argument "if you know one object oriented language you
can write any" (functional languages accordingly). And the pure point is not
incorrect: Most languages that follow a certain programming style are
syntactically similar enough that you can learn the other syntaxes with little
effort. Change . to -> here, probably remove a $ somewhere, replace
fun with function or get used to some other quirks and hooray you can code
in a new language \o/. No, you cannot!
Of course the fundamentals of object orientation and the syntax of a specific language are the basics that need to be fulfilled to work with a language. But there is way more to learn:
Standard library APIs, for example. If you read this blog you are probably familiar with at least one of the following languages: PHP, JavaScript, Java. One is probably your home language. If you ever did, how did you feel when you needed to issue the first HTTP request in any of the other languages?
Here are the 3 languages in comparison. PHP:
$body = file_get_contents("https://api.example.com/data");
$data = json_decode($body, true);
JavaScript:
const res = await fetch("https://api.example.com/data");
const data = await res.json();
Java:
var client = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder(URI.create("https://api.example.com/data")).build();
var res = client.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
var data = new org.json.JSONObject(res.body());
And even if you know all of these languages by heart, here is how to do it in Ballerina:
http:Client clientEndpoint = check new("http://example.com");
http:Response response = check clientEndpoint->get("/api");
io:println(response.getTextPayload());
So, programming is not just syntax and programming paradigm. And this is just the beginning. Beyond standard library APIs there are external libraries and frameworks. You need to know which exist and what they are good for. All of these have different characteristics that make them a good choice in certain cases, but probably a bad choice in others. They have quirks and undocumented behavior. Their documentation varies and maybe some are even buggy. This is what we call the ecosystem of a programming language.
And it's even bigger: Which sources for extended documentation and trouble shooting do you trust? Where will you look for advice if you are stuck? Is the brand new library too hot to be used in production or is the maintainer known to be trustworthy and highly professional so you can risk it?
Some people might argue that this all becomes irrelevant with AI, but the opposite is correct: LLMs cannot judge if they show you old or new examples. AI does not have a model of trust. It just shows you what is (or was) most popular on the internet. And that is not necessarily the best choice for you. You need the experience to judge the output of AI to make it actually useful.
Debugging ¶
This might come as a shock to some of you, but writing code is only a fraction of an engineer's time per day. Way more often they read code and debug it. The time distribution between these tasks might vary, depending on various factors, but there is definitely much more than the pure creation of code lines in an editor.
And these tasks can be rather tricky for a newcomer in a language. Sure, if you have just 20 lines of code on a green field, debugging in a new programming ecosystem is a piece of cake. But debugging any sophisticated project that involves some libraries can easily become a challenge. Put "customer is in rage mode", "we are losing money!!1!" or "3am on-call after a day of investor pitching" on top and you have an excellent stress catastrophe.
If you ever tried to read a Haskell error message you experienced the extreme of what I talk about. But there is more than just reading error messages and stack traces. Debuggers behave slightly different, stepping works differently, jumping between contexts is different, … and how do you debug in production? How might your application behave differently in production than in development and staging? How do you debug issues that only occur under high load? What's your approach to edge case debugging, where an issue just occurs every 1M requests?
The example language used in this article, Ballerina, for example has some
pretty interesting (might be very useful, but at least different) concepts for
error handling using the check and checkpanic keywords and how these
influence errors. The CLI shipped with Ballerina has a built-in debugger and a
VSCode plugin exists, but IntelliJ support does not seem to be very
sophisticated. On top of that: It is unclear what the feature and quality status
of this extension is, compared to the language support built by JetBrains
itself.
Working with a new programming language brings way more learning effort than just writing down lines of code. And this is a field where even AI cannot be of much help.
Deployment, Production and Monitoring ¶
There are tools that you can probably transfer to your new language to save
learning efforts. I'm pretty sure you can use make to create build scripts
for every imaginable programming language. But does that reflect the best
practice of your new ecosystem? And what about all the tools that you cannot
transfer? Package management, automated testing, code optimization, linting,
formatting, dependency management, environment, security scans, … you can
probably implement (and debug!) a build and deployment pipeline in minutes in
your home ecosystem. How long will it take you to have it set up properly (and
scaling) in the new one?
Production hosting sounds as "simple" as Kubernetes (SCNR) or your favorite cloud/edge hosting service. But that's only true for small setups. As soon as you scale beyond the 101 tutorials, pitfalls like NAT issues, noisy neighbors, OOM kills, … will occur. You might have the intuition in your home bases to debug and deal with such issues, but does that also apply to your new ecosystem? A proper monitoring stack (hear hear "observability") is the bare minimum to detect and dig into these issues. But do you know how to vitally monitor your new stack? Are you aware of the quirks the new language includes that require explicit monitoring? And do you know by heart how to dig into the phenomena shown by your monitoring?
On top of the actual know-how required to set up deployment, production environment and monitoring there is a big chunk of "intuition" required to actually cope with what is going on in production. Surely, parts of the intuition you built up in your home base and projects outside are transferable. But what about the rest?
Scaling Engineers ¶
A purely non-technical part that is rarely considered when making technology choices is what was formerly called "human resources": Once you've built your new application or product on the shiny new technology stack, you will want to scale it to more people working on it. Probably you will split it into several modules (today services) to allow separate teams to work on them in isolation. You will want to extend the functionality by far and that needs people power.
But can you find enough people with experience to scale your teams fast and efficiently enough? If you are lucky, you've built your company on a good remote culture, which increases the talent pool to fish from by far. If not, you are stuck to your supported locations, where the talent pool is typically overfished anyway. I wonder how good the market situation is for skilled Ballerina engineers with more than 1 year of experience in the Munich or San Francisco areas?
If you cannot hire enough experienced people, you need to invest in training, which results in much longer on-boarding phases and much higher investment. Scaling fast to keep (or even increase) your advantage towards your competitors becomes much harder and more expensive. On top of that: What if your product later degrades in sales and business importance? Will you fire all the specialized engineers? Will you invest additional resources to teach them the technologies your other teams use?
Fostering a somewhat homogeneous technology landscape among your tech teams is an investment that is definitely worth fighting for. And, please don't get me wrong: I neither say you should build everything in Bash because you hired system engineers with a strong Bash background in the first place, nor do I recommend shifting people randomly between teams every month. But being able to shift people to a new team under hard circumstances is a plus for both - the company and the people.
What's There To Learn? ¶
If you took one thing from this article it's this: Regardless of how well suited a technical tool appears for the problem you are trying to solve, this should not be the only property by which you evaluate it.
There are tons of new/modern/shiny programming languages, frameworks, libraries, tools, … out there, which can be more or less perfectly suited to your exact problem. Still, there are also tons of reasons why you should not use any of them. When you evaluate a new technical artifact to be used, evaluate all the constraints in this article, too. And only when you see the technical perfection outweighs all the other factors together, go for it.
But, do you want me to stop using innovative technology?
That's a very valid question. The short answer is: no. If we always stayed on the same tech stack we would still pierce punch cards. The point is to understand the constraints under which adding a piece of technology to your stack makes sense and the side effects it brings.
Only that way you can make an elaborate trade-off (see my article on that topic) about if it makes sense or not.
The Cost of New Technology ¶
All of the above should have shown you that introducing a new piece of technology does not only come with benefits but also comes with costs. Most of these costs can be attributed to investment into humans, aka learning. Learn the ecosystem, learn debugging, production handling and eventually scaling that knowledge.
A second big factor is the maturity of the technology under inspection: How new is it actually? How big is the maintainer base? What's the size of the community around it? Who will help you when needed? And many more questions should be evaluated. But that could be a topic for another post.
Once you have a realistic view on that cost, you can compare it to the actual benefits of the technology and make an elaborate decision.
The Real Prices ¶
Throughout this article I mentioned "programming language / framework / library" several times. And of course, introducing each of the mentioned comes with the named cost. However, the actual price is different for each of the artifacts. It's much easier to include a new library into your existing stack than introducing a new programming language. So, the cost relation is basically:
library < framework < language
And of course, this should have a direct influence on the amount of care you take to evaluate the real cost factor of your pending decision.
When To Actually Consider Innovative Technology ¶
It's a bold statement, but from my experience I'd recommend staying in your home-base ecosystem as long as possible - again this bond is stronger in proportion to the cost. Boring technology helps you act fast, safe, and comfortable - enabling you to focus on the real task: to create business value.
But there are conditions that heavily push for a change in technology and I want to name two:
If your current stack entirely contradicts the needed runtime characteristics or execution model, this is a hard indicator for a change. For example: your home base is PHP. The language is clearly built for responding to HTTP requests. If your new requirement is shell scripting, you can safely keep PHP. If you need a long running process (a daemon) in a project, this is also possible in PHP - while maybe not the ideal choice for a pure daemon-based project. But if you need to implement real-time computation or media transcoding, PHP is clearly contradicting these requirements. Again, you can see that this is not a binary switch but a fluent spectrum.
When you clearly hit the end-of-life of your current stack, this is also a very hard indicator for a switch. Programming languages reaching end-of-life is a rare occasion, but COBOL, Flash, and Objective-C are examples of that. Death of a framework is also something that does not hit you every few years, but it happens (anyone from the PHP community remembers PEAR, eZ Components or Zend Framework?). And libraries in the JS/TS ecosystem might even just survive a couple of months (SCNR). If you witness end-of-life of your language or framework, there is only the choice to maintain it on your own (or paying someone $$$ to do so) or to pick up something new, while the earlier might really be a valid business choice for some time.
And outside of these edge cases? Trade-off, it's all about taking the right trade-offs.
But How? ¶
Congratulations! You managed to read through this entire article and are still with me. 💪 Your time investment is much appreciated. But this blog post is already quite long and I therefore postpone my tips and tricks to a later post.
Make sure to follow my blog to stay up to date on these upcoming posts!