5 Questions To Ask Before Choosing A New Programming LanguageBenjamin HustonBlockedUnblockFollowFollowingJun 6Image by Free-Photos from PixabayAs a programmer who tries to make pragmatic and rational decisions, I’m very rarely pragmatic or rational about choosing a programming language.
I don’t think this is uncommon.
It’s easy to defend a language you’re familiar with by painting a dystopian picture of the maintenance hell of polyglot systems and the Faustian pact of technical debt when adopting something new.
On the other hand, it’s far too easy to over-optimistically claim that a new programming language will solve all of your problems: it’s more performant, easier to test, and it’ll keep you on your toes.
If you need convincing that software development can be a spectator sport, indulge yourself with a good read through the comment sections.
It’s my belief that we can be more critical with how we discuss the benefits of different programming languages and I think it’s important we recognise quite how extensive and difficult these discussions can be.
Below, I suggest five questions as a loose framework for evaluating and presenting a new language option for you or your team.
That said, please note that this is not a guide for new developers looking for their first language to learn as this should be influenced by very different considerations.
Should I even be considering a different language?A programming language is only a part of software development and, as with all decisions in technology, it is important to consider the different ways in which software decisions can impact a business.
You should think about stopping reading if:You already have a large codebase to maintain which is written in a specific language and your team has valuable experience with its tooling, libraries and frameworks.
You have deployment experience with a particular language ecosystem and redeployments might be complex, risky or expensive.
You might be limited in hiring or on-boarding future developers for an uncommon language, especially if you plan to use niche frameworks or design patterns.
You are proposing a language which is used by few other teams in the domain, meaning you will need to consider scalability, deployment and security by yourself.
Remember:Developers are more valuable than servers.
A positive developer experience is one of the most important considerations in good software architecture.
As much as possible, allow other teams to make your mistakes before you do.
Good, now let’s do the geeky stuff.
How will the language design make my life easier?Language design is complex and compromises are always inevitable.
Different languages have different priorities and intentions, and this should be celebrated.
Here are two of the biggest considerations concerning language design.
Type CheckingAs you will probably know, different languages check what types you are using in your code at different times.
Many statically typed languages have types which are first checked at compile-time while many interpreted languages have types which are checked at runtime.
For example, Java won’t compile if you declare a boolean variable and then attempt to assign to it the value from a method which has a return signature of another type.
Conversely, Python won’t ask for explicit types in your source code and it will complain at runtime if a particular usage of a type is impossible or has been used accidentally.
Having a compiler which is aware of the intended data types and signatures in your program can be a great advantage as it can often spot a host of basic but common errors in your code before you’ve even had to run the program.
On the other hand, dynamic type systems make patterns such as duck typing and monkey patching in testing much easier.
In my opinion, static type checking options can significantly speed up development.
This need not limit you to classic compiled languages: Node and Python 3 have static type checking options with Typescript or the Python typing package.
ConcurrencyWanting to run parts of your program concurrently or in parallel is very common, but finding a comfortable strategy for this is a real minefield.
It’s worth considering the issues below when evaluating a language or one of its libraries for concurrency.
How safe are the concurrency primitives offered?.Dealing explicitly with threads can be painful and requires the use of concurrency features which prevent two threads accessing the same object in memory.
This can be a challenge to design and debug.
Do you need a multithreaded environment?.A single threaded event loop allows for performant non-blocking IO without exposing the perils of threads to your team.
However, parallel execution of expensive CPU-bound tasks can be tough or require a form of multiprocessing.
What abstractions can I use to make concurrency easier?.Languages which have adopted syntax for channels, coroutines and tasks offer simpler structures for dealing with concurrent actions with as small a performance overhead as possible.
However, the implementation of these tools is rarely standardised between languages and it is worth researching whether they are using thread pools, some kind of green threads, or even event loops behind the scenes.
What actually happens to make my code run?So you’ve looked into the language design and how it may or may not make your life easier.
But however good the language design is, programs have to run somehow.
Rather than jump into the mindless foray of benchmarking, here are some more pragmatic things to think about.
The Advantages of Virtual Machines and InterpretersLanguages which run code in virtual machines, software environments, or are run by interpreters can (but not necessarily do) provide portability benefits.
This was a key argument made at Java’s inception: you could write your code once but run it on a number of device architectures due to the Java Virtual Machine (“JVM”).
Furthermore, virtual/software environments and some interpreters are often worked on by incredibly talented individuals who aim to make your code more performant without you knowing it.
It’s also worth looking into whether such environments provide security advantages beyond portability and optimisations.
Managing Compilation StepsCompilation is an important component in many languages, regardless of the what the source code is actually compiled into.
However, it can be a painful process.
If you’ve used a language with a significant compilation step, you’ll appreciate that your everyday experience with a compiler is one of the defining features of your relationship with a language.
Compilation speed as projects grow could be easily overlooked, as can managing target profiles for different architectures if that is necessary.
How easy is it to extend the language with performant extensions?.If you really want something to run fast, native extensions can be a powerful pattern.
Python can be computationally slow with its own data structures but provides tooling for creating libraries with native extensions for optimised calculations.
Similarly, Node enables native extensions in C++ and projects like Neon provide incredibly quick bindings to Rust.
What load can the language sustain?.Particularly with server development, it’s no good to be fast if the language environment does not make it easy to sustain numerous requests at once.
For example, spinning off a new thread for every request can be hard to sustain at extreme load, leading to some of the other concurrency primitives discussed above.
Does the language support design patterns which will scale?Language design and implementation has a direct impact on how easy it is to structure and scale large and complex applications.
One particularly illuminating difference between languages is whether dependency injection is encouraged.
Languages which require or recommend the use of classes for encapsulation (such as Java or C#) often result in complex class dependencies which can be most clearly resolved by an application container which uses this pattern.
In these contexts, Object Orientated Programming (“OOP”) design patterns can be very powerful at scale.
Languages like Kotlin provide incredible support for complex OOP patterns including extensions and delegation.
There are textbook solutions for architecting big projects, but they are not always easy and can be verbose.
Many interpreted languages provide strong support for stubbing functions in tests and encapsulating functionality in modules rather than classes.
In these contexts, traditional OOP patterns are possible but not required, and sometimes feel less idiomatic.
This gives more scope for different approaches but may require more thought about how to structure large applications.
The flip side, of course, is that testing can be easier in these environments.
This is certainly the philosophy of languages such as Ruby and Python, which prioritise testability and the rate of development over program performance metrics.
How might tooling and standards increase the time to ship code?To my mind this is one of the most important points: you may have a beautiful and performant language but if it’s tough to actually write code, it won’t last long.
Here are some essentials:What tools can I use when writing code?.IDE support, widely-adopted linters, and debuggers should not be undervalued.
How do I manage dependencies?.I think there are some excellent languages out there which fall down at this hurdle.
Effective dependency tooling should be standardised, have built-in security features, and have a clear philosophy about version management.
How opinionated is the community?.Do not underestimate the power of official style guides, code review tips, and open source projects which can be used as examples.
Does the language consider security?.Cybersecurity considerations are an essential part of a language and its environment.
SummaryAs mentioned at the beginning of this article, it’s essential that we talk about programming languages with reference to the broad spectrum of issues involved.
There is no one language to rule them all and we can only ever talk about the philosophy behind a language and whether its adoption would give us a meaningful advantage in solving the problem we are facing.