Language Features vs. Usefulness and Understandability

Most modern programming languages have a ton of features, and some of the hottest languages are adding features at high rate in the hope that it will make the language better. But, how does the number of features of a programming language relate to its usefulness and understandability?

Usefulness

Let’s consider a modern and popular programming language, such as JavaScript, Java, C, C++ or C#. All of these support the feature of adding a value into a variable directly, a shorthand assignment: x += 5; We could remove this feature. We would have to write this instead: x = x + 5; It sure is less convenient, but it does not reduce the usefulness of the language by a lot.

Imagine instead that we removed functions. That would certainly be annoying and most would now struggle to write code with the language. This would reduce the usefulness of the language by a lot.

Imagine that we removed floating point variables. It would be less of a problem than functions, but a considerable problem compared to the shorthand assignment discussed above.

We can assign a usefulness value to each feature in a programming language. Functions would get 100 (for example) while the shorthand assignment discussed above would get 0.1. If we order the features by their usefulness, we would get a list of decreasing numbers. Imagine creating a programming language from the N most useful features, and let us consider assembly a language with zero features. The usefulness of these languages could then be plotted, and it would look like this:

Language Features vs. Usefulness

Note that the number of features on the x-axis is not to be taken literally. The y-axis is the degree of usefulness, from 0 being useless and 1 being very useful.

Notice a few interesting things about this graph. The first features increase the usefulness of a programming language a lot, then the curve starts flattening. It then becomes almost completely flat. On the flatter part, even though the next feature is useful, it does not contribute a lot. Examples from Java are the shorthand assignment, static class initializers, labeled break and continue, short-circuiting "and" and "or", double-brace initializers, octal number literals, break, continue, finally, inner classes. Examples from C are the comma-operator, the shorthand assignment, octal number literals, single statement blocks, trigraphs, goto, ternary operator, assignments as expressions.

This does not mean that adding a new feature to a programming language makes it just a little more useful. The new feature might be very useful and thus affect the beginning of the plot.

Also notice that no added feature makes the programming language less useful. The basic reason is that one can choose not to use the feature. (Some languages contain features that limit the use of other features, for example Rust’s borrow checker, but it is not usual to introduce such features as mandatory later in a language’s evolution as it would break backwards compatibility.)

A language would remain very useful even if a lot of its features were removed. Actually, if you took a software module implemented in, say, Java, and decided to remove the use of a single, not-so-important feature, say "double-brace initializers", you would notice an interesting thing: It would not impact the code much, but reading and understanding the code requires knowing less language features. Thus, reducing the number of features used unnecessarily is improving the code: It now requires less of those who work with it without giving up anything important.

Understandability

If adding more features to a language makes it more useful, why not just pack it with all the features we can imagine? There is a tradeoff! As more features are added, and as developers start using those features, the understandability of the code goes down and the code eventually becomes unreadable, sometimes known as write-only code.

A programming language with very few features is also not understandable. The classic example is assembly code, but it is still very easy to use compared to using a Turing machine natively, which is probably one of the programming models known with the least features.

So, adding the first highly useful features to a programming language makes the code a lot more understandable quickly. At a certain point, the understandability starts dropping.

Often, software projects limit which features can be used. This might be enforced through code reviews, static code analysis or guidelines on how to write code. Also, very few recommend using most of a programming language’s features most of the time.

Again, imagine creating a programming language with the N most useful features, again considering assembly a language with zero features. The understandability of code in these programming languages can be plotted:

Language Features vs. Understandability

Again, please note that the number of features on the x-axis is not to be taken literally. Also, the y-axis is the degree of understandability, from 0 being very difficult and 1 being very easy.

It should be noted that writing code that is impossible to understand is quite easy in any programming language. This graph shows the difficulty of understanding code written in a good way using all available features.

Another reason why the graph looks like this is combinatorics. A new feature can potentially interact with all other existing features. Knowing, handling and understanding all these interactions grows more and more difficult.

Let us now place this graph on top of the other one. In the following graph, the red curve is the usefulness and the blue curve is the understandability.

Language Features vs. Usefulness and Understandability

A striped gray line is drawn where the understandability curve is at the highest. An interesting thing about such a programming language is that it contains few features, so it is easy to learn but a little less useful. Such a language would probably not be very popular since it lacks many useful features. The programming pros would laugh at it while they churn out difficult-to-understand one-liners with their feature rich programming language. But, very few consider the added cost of writing code that is difficult to understand.

Where on this graph can we place modern languages?

C is a quite feature-poor language, it is probably on the middle left of the graph.

Perl 5 is famous for being regarded as a write-only language, it is clearly on the far right if not off-the-charts.

C++ and Java add features at a high rate, shifting it to the right quickly.

A language sitting right at the gray striped line in progsbase, a programming language designed to have only the most useful features.

If we sorted the features of many programming languages by usefulness, we would see that most languages share the most useful features, for example functions, floating point, booleans, loops, ifs, arrays, data structures. However, the less useful features are often what makes a programming language special. Indeed, had they been regarded as very useful, the other languages would have to either adopt them or lose most of their user base. This is why the programming language progsbase can be translated between languages, because it contains the most useful features that are therefore common to most languages.

Programming languages with a low number of features have a tendency to last longer. Three languages are notable: COBOL, assembly and C. COBOL is a quite simple language often derided by developers. Yet, it keeps surviving decade after decade. There are two good reasons for this: 1) a programming language with a few features is easier to evolve, be it slowly. With many features, it is almost impossible to maintain backwards compatibility in all cases. 2) Code written in a more understandable language is easier to maintain over time. It requires less years or decades of experience to learn to write or read the code effectively.

Languages with many features have a tendency to be volatile. This is clearly visible in the rapidly changing ecosystem of modern, cool programming languages. This is further amplified by language versions, dialects, variants and subsets. Even if a language has a the same name, a new version might be sufficiently different to actually be a different language, for example, C++98 vs. C++20 and Java 4 vs. Java 15. Also, some languages are so feature rich that parts of the language becomes their own dialect or style, for example, procedural Java vs. functional Java.

In conclusion, adding features to a programming language is a tradeoff. Sometimes, being conservative and thinking deeply about which features to include can have long-term benefits in understandability and longevity of the programming language.