What this blog is about
“Simplify C++!” is not an appeal to the ISO C++ committee to simplify the C++ language specification. The language as a whole may be complex and have its dark, dusty and sometimes frightening corners, but cutting down the complexity would cripple it’s strengths in one way or the other. The parts of C++ that are unnecessarily complex are well known and the committee puts in great efforts to reduce those black spots. There is no need for yet another blog full of counterproductive rants.
Instead, “Simplify C++!” is an appeal to many users of the language who tend to think too complicated when things could be done in a simple manner. It is about polishing the image of a language that is often seen as too hard to master by novice programmers and as an ancient, monstrous survivor of the old days by seasoned developers. It is about using advanced features where they are necessary and useful instead of everywhere where they are possible.
The KISS principle
KISS is an acronym for “Keep it simple, stupid” as a design principle noted by the U.S. Navy in 1960. The KISS principle states that most systems work best if they are kept simple rather than made complicated; therefore simplicity should be a key goal in design and unnecessary complexity should be avoided.
This quote from wikipedia tells us everything we need to know: simplicity is key, and engineers have known that since the beginning of time (at least of the times where C++ existed). It is especially true for a programming language like C++ that purposely enables you to do complicated and nasty things without any saftey net.
For C++ programmers it means don’t leave the beaten path if you don’t have to. Don’t try to be fancy, and only do what needs to be done, not more. This is one of the things I still need to learn myself – somtimes I get carried away by pure academic interest and try out things that involve complicated tempate metaprogramming and the like.
To be clear, I don’t want to discourage academic curiosity. It’s ok to play around and ask and solve tricky questions on stackoverflow. But leave that fancy template metaprogramming technique out of your production code, please, unless you really need it.
I often see programmers jump through hoops because they consider language or library features that never get used in the context they are programming for. For example, a simple function that works on a certain container in your application and is called only once or twice and only for that container does not need to be generalized by making it a template, taking arbitrary iterators and maybe a predicate like the functions in `<algorithm>`.
On the other hand, if you write a loop that can be expressed by a simple call to a standard algorithm, then use the algorithm instead of a handcrafted loop. After all, code is more often read than written or modified, so making it simple to read is the most important simplification.
Don’t pay for what you don’t use
This is one of the key phrases in C++ since it’s early days. It is often related to performance, meaning that a feature should not consume execution time or memory unless it is actually needed. But in my opinion it should be related to any resource, including the most precious resource a programmer has: brain capacity.
C++ provides a lot of flexibility and low-level functionality. It comes for a price, however: One has to dig into the low-level thinking and keep in mind when something is not used the usual way. Use the wellknown features the language provides.For example, using smart pointers lifts the burden of having to mentally track object lifetimes and checking for every possible path of execution to avoid resource leaks.
On the other hand, don’t use features that are not as well known. With “well known” I mean well known to the average C++ programmer who will come across your code. A seasoned C++ developer might know the table of operator precedences by heart, but others will not. So it’s better to use parentheses to make the precedence clear to the reader. (You can erase that table from your memory now, you will only need it when you write that C++ parser you always wanted to write).
Don’t get carried away. C++ is known to enable performant code, and sometimes you can get a few percent out of a certain piece of code if you apply the proper micro-optimizations. On stackoverflow people often ask how they can tweak a perfectly readable implementation to optimize it. Don’t do that. 90% of your code does not run in a performance critical part of your program, and you should strive for readability and maintainability as a default, not for optimal code. Therefore the answer to all of those performance questions usually is “Ask your profiler if that code really is a performance bottleneck, and only if it is, ask the profiler if version XY of your function really makes it so much faster”.
This blog will contain posts that cover pitfalls of the language and how to avoid them, als well as features everybody should know, and how to use them.
Simplification through Clean Code
Writing clean code means simplifying it. If you stick to short and concise functions along with a clear set of responsibilities and other principles that are considered as “clean code”, then reading and maintaining your code is a no-brainer. If you have to maintain a function of several hundred lines with tons of conditionals, a multitude of parameters and a mix of interleaved responsibilities, you are more likely to get mentally exhausted and make errors. Sometimes you don’t have the choice, because that ugly function is already in the code base and is used all over the place and you can’t just throw it away and rewrite it from scratch.
This blog will contain posts that deal with writing clean code in the first place and refactoring legacy code to clean it up. I definitely recommend reading Uncle Bob Martin’s “Clean Code” and Michael Feathers’ “Working Effectively with Legacy Code” if you are interested in those topics.
Maintainability and Usability – Two Sides of a Medal
Aside from what I have written above, writing the simplest code that does the job is not always the best way to go. The functions and libraries that you write are going to be used, and they tend to be used more often than written and maintained. Writing a function in the simplest way possible will often not give you the best result in terms of usability.
On the other hand, providing a very convenient and easy to use interface often requires some dirty work to hide the fiddly bits and tangles behind that interface, so users of the interface don’t have to bother. This means you have to make some tradeoffs when it comes to the simplicity of library implementations, in order to simplify their use.
Sometimes we even want to make a special library stand out and have a special look and feel in the code (because the library is special, not because we want to feel special!). In those cases, it can be appropiate to have the library represented by a Domain Specific Language (DSL). C++ provides good tools to write embedded DSLs (i.e. the DSL consists of special objects, functions and operators but is C++ code) as well as libraries to write external DSLs (usually plain text, parsed and interpreted by your program).
While DSLs require the user to learn a bit more than just a bunch of classes and functions, they provide a clear separation between the library or domain they cover and the rest of the world, so they can increase the structure and understandability of an otherwise complicated piece of code. DSLs range from just “oddly” used functions to C++ code that does not look like C++ at all due to overloaded operators to fullgrown complex text based languages. Martin Fowler’s “Domain Specific Languages” is a great book on that topic.
In this blog you will see posts about interface design for functions, classes and libraries and some posts about what is needed to write DSLs, e.g. operator overloading, parsing etc.