
- •Table of Contents
- •About the Author
- •About the Technical Reviewer
- •Acknowledgments
- •Software Entropy
- •Clean Code
- •C++11: The Beginning of a New Era
- •Who This Book Is For
- •Conventions Used in This Book
- •Sidebars
- •Notes, Tips, and Warnings
- •Code Samples
- •Coding Style
- •C++ Core Guidelines
- •Companion Website and Source Code Repository
- •UML Diagrams
- •The Need for Testing
- •Unit Tests
- •What About QA?
- •Rules for Good Unit Tests
- •Test Code Quality
- •Unit Test Naming
- •Unit Test Independence
- •One Assertion per Test
- •Independent Initialization of Unit Test Environments
- •Exclude Getters and Setters
- •Exclude Third-Party Code
- •Exclude External Systems
- •What Do We Do with the Database?
- •Don’t Mix Test Code with Production Code
- •Tests Must Run Fast
- •How Do You Find a Test’s Input Data?
- •Equivalence Partitioning
- •Boundary Value Analysis
- •Test Doubles (Fake Objects)
- •What Is a Principle?
- •KISS
- •YAGNI
- •It’s About Knowledge!
- •Building Abstractions Is Sometimes Hard
- •Information Hiding
- •Strong Cohesion
- •Loose Coupling
- •Be Careful with Optimizations
- •Principle of Least Astonishment (PLA)
- •The Boy Scout Rule
- •Collective Code Ownership
- •Good Names
- •Names Should Be Self-Explanatory
- •Use Names from the Domain
- •Choose Names at an Appropriate Level of Abstraction
- •Avoid Redundancy When Choosing a Name
- •Avoid Cryptic Abbreviations
- •Avoid Hungarian Notation and Prefixes
- •Avoid Using the Same Name for Different Purposes
- •Comments
- •Let the Code Tell the Story
- •Do Not Comment Obvious Things
- •Don’t Disable Code with Comments
- •Don’t Write Block Comments
- •Don’t Use Comments to Substitute Version Control
- •The Rare Cases Where Comments Are Useful
- •Documentation Generation from Source Code
- •Functions
- •One Thing, No More!
- •Let Them Be Small
- •“But the Call Time Overhead!”
- •Function Naming
- •Use Intention-Revealing Names
- •Parameters and Return Values
- •Avoid Flag Parameters
- •Avoid Output Parameters
- •Don’t Pass or Return 0 (NULL, nullptr)
- •Strategies for Avoiding Regular Pointers
- •Choose simple object construction on the stack instead of on the heap
- •In a function’s argument list, use (const) references instead of pointers
- •If it is inevitable to deal with a pointer to a resource, use a smart one
- •If an API returns a raw pointer...
- •The Power of const Correctness
- •About Old C-Style in C++ Projects
- •Choose C++ Strings and Streams over Old C-Style char*
- •Use C++ Casts Instead of Old C-Style Casts
- •Avoid Macros
- •Managing Resources
- •Resource Acquisition Is Initialization (RAII)
- •Smart Pointers
- •Unique Ownership with std::unique_ptr<T>
- •Shared Ownership with std::shared_ptr<T>
- •No Ownership, but Secure Access with std::weak_ptr<T>
- •Atomic Smart Pointers
- •Avoid Explicit New and Delete
- •Managing Proprietary Resources
- •We Like to Move It
- •What Are Move Semantics?
- •The Matter with Those lvalues and rvalues
- •rvalue References
- •Don’t Enforce Move Everywhere
- •The Rule of Zero
- •The Compiler Is Your Colleague
- •Automatic Type Deduction
- •Computations During Compile Time
- •Variable Templates
- •Don’t Allow Undefined Behavior
- •Type-Rich Programming
- •Know Your Libraries
- •Take Advantage of <algorithm>
- •Easier Parallelization of Algorithms Since C++17
- •Sorting and Output of a Container
- •More Convenience with Ranges
- •Non-Owning Ranges with Views
- •Comparing Two Sequences
- •Take Advantage of Boost
- •More Libraries That You Should Know About
- •Proper Exception and Error Handling
- •Prevention Is Better Than Aftercare
- •No Exception Safety
- •Basic Exception Safety
- •Strong Exception Safety
- •The No-Throw Guarantee
- •An Exception Is an Exception, Literally!
- •If You Can’t Recover, Get Out Quickly
- •Define User-Specific Exception Types
- •Throw by Value, Catch by const Reference
- •Pay Attention to the Correct Order of Catch Clauses
- •Interface Design
- •Attributes
- •noreturn (since C++11)
- •deprecated (since C++14)
- •nodiscard (since C++17)
- •maybe_unused (since C++17)
- •Concepts: Requirements for Template Arguments
- •The Basics of Modularization
- •Criteria for Finding Modules
- •Focus on the Domain of Your Software
- •Abstraction
- •Choose a Hierarchical Decomposition
- •Single Responsibility Principle (SRP)
- •Single Level of Abstraction (SLA)
- •The Whole Enchilada
- •Object-Orientation
- •Object-Oriented Thinking
- •Principles for Good Class Design
- •Keep Classes Small
- •Open-Closed Principle (OCP)
- •A Short Comparison of Type Erasure Techniques
- •Liskov Substitution Principle (LSP)
- •The Square-Rectangle Dilemma
- •Favor Composition over Inheritance
- •Interface Segregation Principle (ISP)
- •Acyclic Dependency Principle
- •Dependency Inversion Principle (DIP)
- •Don’t Talk to Strangers (The Law of Demeter)
- •Avoid Anemic Classes
- •Tell, Don’t Ask!
- •Avoid Static Class Members
- •Modules
- •The Drawbacks of #include
- •Three Options for Using Modules
- •Include Translation
- •Header Importation
- •Module Importation
- •Separating Interface and Implementation
- •The Impact of Modules
- •What Is Functional Programming?
- •What Is a Function?
- •Pure vs Impure Functions
- •Functional Programming in Modern C++
- •Functional Programming with C++ Templates
- •Function-Like Objects (Functors)
- •Generator
- •Unary Function
- •Predicate
- •Binary Functors
- •Binders and Function Wrappers
- •Lambda Expressions
- •Generic Lambda Expressions (C++14)
- •Lambda Templates (C++20)
- •Higher-Order Functions
- •Map, Filter, and Reduce
- •Filter
- •Reduce (Fold)
- •Fold Expressions in C++17
- •Pipelining with Range Adaptors (C++20)
- •Clean Code in Functional Programming
- •The Drawbacks of Plain Old Unit Testing (POUT)
- •Test-Driven Development as a Game Changer
- •The Workflow of TDD
- •TDD by Example: The Roman Numerals Code Kata
- •Preparations
- •The First Test
- •The Second Test
- •The Third Test and the Tidying Afterward
- •More Sophisticated Tests with a Custom Assertion
- •It’s Time to Clean Up Again
- •Approaching the Finish Line
- •Done!
- •The Advantages of TDD
- •When We Should Not Use TDD
- •TDD Is Not a Replacement for Code Reviews
- •Design Principles vs Design Patterns
- •Some Patterns and When to Use Them
- •Dependency Injection (DI)
- •The Singleton Anti-Pattern
- •Dependency Injection to the Rescue
- •Adapter
- •Strategy
- •Command
- •Command Processor
- •Composite
- •Observer
- •Factories
- •Simple Factory
- •Facade
- •The Money Class
- •Special Case Object (Null Object)
- •What Is an Idiom?
- •Some Useful C++ Idioms
- •The Power of Immutability
- •Substitution Failure Is Not an Error (SFINAE)
- •The Copy-and-Swap Idiom
- •Pointer to Implementation (PIMPL)
- •Structural Modeling
- •Component
- •Interface
- •Association
- •Generalization
- •Dependency
- •Template and Template Binding
- •Behavioral Modeling
- •Activity Diagram
- •Action
- •Control Flow Edge
- •Other Activity Nodes
- •Sequence Diagram
- •Lifeline
- •Message
- •State Diagram
- •State
- •Transitions
- •External Transitions
- •Internal Transitions
- •Trigger
- •Stereotypes
- •Bibliography
- •Index
Chapter 4 Basics of Clean C++
advisable to start with C++14, which was essentially a bugfix release of C++11, or even start with a higher standard right away.
Now let’s explore the key elements of clean and modern C++, step by step.
Good Names
“Programs must be written for people to read, and only incidentally for machines to execute.”
—Hal Abelson and Gerald Jay Sussman, 1984
The following piece of source code is taken from Apache OpenOffice version 3.4.1, a well-known open source office software suite. Apache OpenOffice has a long history, which dates back to the year 1984. It descends from Oracle’s OpenOffice.org (OOo), which was an open sourced version of the earlier StarOffice. In 2011, Oracle stopped the development of OpenOffice.org, fired all developers, and contributed the code and trademarks to the Apache Software Foundation. Therefore, be tolerant and keep in mind that the Apache Software Foundation has inherited a nearly 30-year-old ancient beast and vast technical debt.
Listing 4-1. An Excerpt from Apache’s OpenOffice 3.4.1 Source Code
// Building the info struct for single elements SbxInfo* ProcessWrapper::GetInfo( short nIdx )
{
Methods* p = &pMethods[ nIdx ];
//Wenn mal eine Hilfedatei zur Verfuegung steht:
//SbxInfo* pResultInfo = new SbxInfo( Hilfedateiname, p->nHelpId ); SbxInfo* pResultInfo = new SbxInfo;
short nPar = p->nArgs & _ARGSMASK; for( short i = 0; i < nPar; i++ )
{
p++;
String aMethodName( p->pName, RTL_TEXTENCODING_ASCII_US ); sal_uInt16 nInfoFlags = ( p->nArgs >> 8 ) & 0x03;
66
Chapter 4 Basics of Clean C++
if( p->nArgs & _OPT ) nInfoFlags |= SBX_OPTIONAL;
pResultInfo->AddParam( aMethodName, p->eType, nInfoFlags );
}
return pResultInfo;
}
I have a simple question for you: What does this function do?
It seems easy to give an answer at first sight, because the code snippet is small (less than 20 LOC) and the indentation is okay. But in fact, it is not possible to say at a glance what this function really does, and the reason for this is not only the domain of office software, which is possibly unknown to us.
This short code snippet has many bad smells (e.g., commented-out code, comments in German, magic literals like 0x03, etc.), but a major problem is the poor naming. The function’s name GetInfo() is very abstract and gives us at most a vague idea of what this function actually does. Also the namespace name ProcessWrapper is not very helpful. Perhaps you can use this function to retrieve information about a running process? Well, wouldn’t RetrieveProcessInformation() be a much better name for it? The comment on the first line (“Building the info struct...”) indicates that something is created.
After an analysis of the function’s implementation you will also notice that the name is misleading, because GetInfo() is not just a simple getter as you might suspect. There is also something created with the new operator. In other words, the call site
will receive a resource that was allocated on the heap and the caller must take care of it. To emphasize this fact, wouldn’t a name like CreateProcessInformation() or
BuildProcessInfoFromIndex() be much better?
Next, take a look at the parameter and the return value of the function. What is SbxInfo? What is nIdx? Maybe the argument nIdx holds a value that is used to access an element in a data structure (that is, an index), but that would just be a guess. In fact, we don’t know exactly.
Developers very often read source code, usually more often even than they write new code. Therefore, source code should be readable, and good names are a key factor of readability. If you are working on a project with multiple people, good naming is essential so that you and your teammates can understand the code quickly. If you have to edit or read a piece of code you wrote a few weeks or months later, good module, class, method, and variable names will help you recall what you meant.
67

Chapter 4 Basics of Clean C++
Note Any entity in a source code base, e.g., files, modules, namespaces, classes, templates, functions, arguments, variables, constants, type aliases, etc., should have meaningful and expressive names.
When I’m designing software or write code, I spend a lot of time thinking about names. I am convinced that it is well-invested time to think about good names, even if it’s sometimes not easy and takes five minutes or longer. I seldom find the perfect name for a thing immediately. Therefore, I rename often, which is easy with a good editor or an Integrated Development Environment (IDE) with refactoring capabilities.
If finding a proper name for a variable, function, or class seems to be difficult or nearly impossible, that might indicate that something else is wrong. Perhaps a design issue exists and you should find and solve the root cause of your naming problem.
The next section includes a few bits of advice for finding good names.
Names Should Be Self-Explanatory
I’ve committed myself to the concept of self-explanatory code. Self-explanatory code is code when no comments are required to explain its purpose (see the following section on comments and how to avoid them). Self-explanatory code requires self-explanatory names for its namespaces, modules, classes, variables, constants, and functions. See Listing 4-2.
Tip Use simple but descriptive and self-explaining names.
Listing 4-2. Some Examples of Bad Names
unsigned int num; bool flag;
std::vector<Customer> list; Product data;
Variable naming conventions can often turn into a religious war, but I am very sure that there is broad agreement that num, flag, list, and data are really bad names. What is data? Everything is data. This name has absolutely no semantics. It’s as if you boxed
68
Chapter 4 Basics of Clean C++
your goods into moving boxes and, instead of writing on them what they really contain, for example, “cookware,” you wrote write the word “things” on every single carton. When the cartons arrive at the new house, this information is completely useless.
Listing 4-3 shows an example of how we could better name the four variables in the previous code example.
Listing 4-3. Some Examples of Good Names
unsigned int numberOfArticles; bool isChanged; std::vector<Customer> customers; Product orderedProduct;
One can now argue that names are better the longer they are. Consider the example in Listing 4-4.
Listing 4-4. A Very Exhaustive Variable Name
unsigned int totalNumberOfCustomerEntriesWithIncompleteAddressInformation;
No doubt, this name is extremely expressive. Even without knowing where this code comes from, the reader knows quite well what this variable is used for. However, there are problems with names like this. For example, you cannot easily remember such long names. And they are difficult to type if you don’t use an IDE that has auto completion. If such extremely verbose names are used in expressions, the readability of the code may even suffer, as shown in Listing 4-5.
Listing 4-5. Naming Chaos, Caused By Too Verbose Names
totalNumberOfCustomerEntriesWithIncompleteAddressInformation = amountOfCustomerEntriesWithIncompleteOrMissingZipCode + amountOfCustomerEntriesWithoutCityInformation + amountOfCustomerEntriesWithoutStreetInformation;
Too long and verbose names are not appropriate or desirable when trying to make our code clean. If the context is clear in which a variable is used, shorter and less descriptive names are possible. If the variable is a member (attribute) of a class, for instance, the class’s name usually provides sufficient context for the variable. See Listing 4-6.
69