One of the most critical decisions that a developer is required to make during the onset of a project is the language, or set of languages, that will be used to implement the system. This decision not only effects the implementation of the system, but also the design. For example, should we use an object-oriented language or a procedural language? While this decision has long-term repercussions for the project, or the life-time of the program the project is a part of, many times, we select a language based on some pretty fickle factors and usually without much thought: It's the language I've always used to implement this type of system; it's the one I know the best; it's my favorite language and I enjoy programming in this language; etc.
With such deep and long-term results of this decision, should we not be more pragmatic about the decision? Many times, we are blind to the biases we have about the languages we select. Whatsmore, sometimes the reasons we have for disliking a language choice may be exactly the reason why we would want to select the language.
If we are open to our options and are honest with ourselves about the biases we hold, we can alleviate some of the pain of fitting a square peg into a round hole. While there is no secret path to selecting the perfect language for a project, there are some principles we can follow that can lead to a better and more fitting language choice.
There is No Perfect Language
This should be no surprise to anyone with even a novice level of experience, and many of us are willing to admit, "Of course this language is not a perfect language," but at the same time, many of us still say, "This language is the best programming language." The key to saying a language is the best language for a project is the context of the project; i.e., a language is only best in a certain context. This leads to our first rule:
There is no perfect language: Every language has its advantages and disadvantages.
For example, many developers who commonly use run-time languages, such as Java or Python, will claim that C or C++ is stifling in the onus it places on the developer to care for such low-level details as memory management or care for the strict granularity of compile-time type checking. This is true, so long as the project we are developing for does not care about seemingly trivial tasks such as memory management or the number of copy-assignments that take place in a single loop.
If instead, we were working on a project, or a portion of a project, that requires extreme prejudice in how efficient our code should be or is safety-critical is nature, these seemingly burdensome details may be exactly the level of granularity we are looking for. In this new context, the run-time nature of Java or Python may seem too blasé or too abstracted. Instead, we would wish to have the strictest control over when memory is allocated and deallocated, how many move-assignments or copy-assignments are executed, and catching as many errors during compile-time as possible, rather than letting errors seep through to run-time (or manifest themselves as run-time exceptions).
While this first point may sound obvious in theory, our behavior as developers is often detached from this concept: We say we know our favorite language is not perfect, but we continue to use the language on every project we develop, regardless if it fits the bill. Furthermore, when another developer questions our choice of language, we become dogmatic in defending our choice, rather than seeing the merit in his or her counter-argument. Remember: Every language has its advantages and disadvantages. Understand these pros and cons of the languages you know (as as we will see, languages you do not currently know) and make your selection accordingly.
The Reason You Don't Like a Language May Be The Reason You Should Use It
This may seem counter-intuitive at first, but sometimes, the reason we dislike a language may be exactly the reason to use a language. Following the above example, in my experience as a C++ developer, there are many times where there are so many different concepts to keep track of (memory management and object life-times, implementing the Rule of Three, etc.) that is can be become tedious to complete a simple feature for a project. After weeks of this type of development, using Python, Java, or another "higher-level" language can seem like a blessing; but is it really?
Sometimes, the reasons why we may dislike a language may be exactly why we should use that language. If I am developing a driver or some safety-critical, real-time system, the exact reasons I expressed above as being burdensome are some of the greatest advantages of the language. For example, the fact that C++ provides a mechanism for expressing the logic executed when an object is copied is invaluable when efficiency or preciseness is in order.
While this may all seem well and good, it is difficult to figure out when a dislike may be advantageous in a certain context. So, how do we decide when those dislikes are helpful? This question leads to our second rule:
Be honest with yourself: Understand why you do not like a language and do not be dogmatic about your dislikes.
For example, in the C++ example above, the reason I dislike programming in C++ for extended periods of time is that the language requires such a preciseness of thought that it can be easy to make a mistake or get trapped in the weeds (focusing too much on the trees, rather than the forest). This preciseness can hamper a developer into questioning decisions such as, "Do I want this object to be created on the stack or the heap, or part of it on the stack and another on the heap?" or "Do I want this class to be extensible through template parameterization or through inheritance?" In other languages, a developer would simply create an object or use object-oriented inheritance to complete these tasks, respectively, and move onto the next feature, since the language (or, more correctly, the compiler or interpreter) takes care of these details.
But, if I am honest with myself, I will recognize that the reason I dislike these features of C++ is because it puts the responsibility on me for expressing these details. In other languages, not only am I not responsible for these details, but I am incapable of expressing these details: They are abstracted away from the developer. In a context in which these details are essential, the reason I dislike C++ is exactly the reason I should be using the language.
Does that mean that we should be miserable and use these features that annoy us about the language? Not necessarily. Maybe a change in perspective is in order: Instead of viewing these features as disadvantages, maybe we should embrace them as essential to us completing our task. Instead of saying, "This is miserable," we should be saying, "Thank goodness that I can actually do this in this language." Just remember: In some contexts, those feature will be a Godsend, an in others, they will be combursome. Just be honest with yourself on why you do not like the features of a language.
The More Familiar You Are With Other Languages, The Better
For this point, we will start with our third rule:
If the only tool you have is a hammer, every problem will look like a nail.
This rule is not particular to software engineering, but it is poignantly manifested in many software development situations. Many times, we select a language, or the tools that a language supports (e.g., JMS for Java, asyncio for Python, Rails for Ruby, etc.) because we know they exist. If the only language we are familiar with is Java, then we will try to fit every problem we encounter in the context of Java. For example, "I need to create a routing framework for a communications application. How can I do this in Java?" This limits the tools at our disposal and artificially restrict our ability to select the right tool for the job.
The solution to this problem is to expand your horizons and learn about the capabilities and intricacies of other languages. As suggested by Andrew Hunt and David Thomas in The Pragmatic Programmer, a good rule of thumb is to learn a new language each year. This is not as easy as it sounds, since learning a language will mean different things to different people.1 Compounding this problem is the reality that we will often be using a single language for the project we are conducting, making learning another language seem useless. For example, if I am an Android developer using Java on a daily basis, learning C# may appear to be an inapplicable waste of time.
As suggested by the third rule above, this is a false appearance. The advantage of learning other languages manifests itself in our ability to see problems from a different perspective and apply the tool that best suits the problem. In order to do this, we must learn the caveats of other languages and the way developers using these other languages solve problems. For example, if a developer wants to perform metaprogramming in C++, he or she may use Template Metaprogrammming (TMP) in C++, but he or she may use reflection in Java. Understanding how other languages can solve similar problems reduces the risk that we think within a confined box.
As a trailing example, if we need to be able to change the run-time characteristics of a class, a C++ developer deeply familiar with the intricacies of C++ may try to concoct a solution that stretchs the bounds of this compile-time language. Another C++ developer, who also has some familiarity with Java, has the ability to say, "I like C++, but Java's run-time reflection is a better suit to solve this problem."
There are countless programming languages at the disposal of a developer, and therefore, it is important to prioritize which languages we learn. A good place to start is the most popular languages in use today (i.e., see the most popular languages on Github, Language Trends on Github, The 9 most popular computer languages, according to the Facebook for programmers, etc.).
1A good rule of thumb is to not only learn the syntax of the language, but also the idiosyncrasies of the language: Look up the most popular questions for that language on Stackoverflow and join the most popular community boards or forums for that language.
Languages Are a Means Not an End
This fourth and final rule is the likely the most philosophical, but arguably the most important:
A programming language is a means, not an end.
Unless you are a language standards author or a compiler writer, a programming language is a means to an end, where the end is the completion of your project: The end goal is to complete the project, not to use a specific language. This does not mean every developer is not entitled to his or her likes or dislikes (in actuality, if we are honest with ourselves, these likes and dislikes can be adventageous to us; see rule two above), but we should not be delude ourselves into making decisions such as, "This is a good chance for me use this feature of that language," unless the feature of the language truly suits the needs of the project.
It is important to keep in mind, a language is simply a means of expressing a solution to the problem at hand: Make sure you select the language that best expresses the solution to the problem domain.
Other Ideas to Consider
Here are some supplemental ideas to consider when selecting a language:
Consider how some languages may interact with others. For example, if you decide that Python is the best language to complete the majority of your project, but there is a well-defined component in your project that requires a extreme level of granularity or efficiency (something that may be suited to C or C++), this does not mean that you cannot use Python on this project. Instead, consider using Python, writing the particular component of interest in C or C++, and then interfacing with this component using the Python C API. Note that to devise such a solution, we would need to know Python has a C API; therefore, it is beneficial to learn about some of these features of a number of the most popular languages.
Middleware may allow multiple languages to be used. For example, if there are two applications that must communicate, such as mobile device and a server application, this does not mean that they must both use the same language (although they could, if in your judgment, this was the best decision). If the mobile device is an Android phone, and the server application was well suited as a Python application, then using a message broker, such as RabbitMQ, can allow you to use these two languages while still communicating: The Android application could use the Java RabbitMQ API, while the server application could use the Python RabbitMQ API.
Embrace the quirks of other languages. If you are a Java developer, you use packages to separate logical units of source code; if you are a Python developer, you use Python's package structure to do the same; if you are a C++ developer, you use namespaces or prefixed class names (i.e., "DZone_MyClassName"). Understand the particulars of the language you are using and embrace them: When in Rome, be as the Romans. Doing otherwise is like speaking German in an Italian accent, since you are more fond of how words are pronounced in Italian. It is likely that if a feature of a language is long-standing, there is a good reason: Make sure you understand the reason.