![](/user_photo/_userpic.png)
- •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 7 Functional Programming
for (const auto& value : std::views::iota(0, 100) | std::views::transform(toSquare)
| std::views::filter(isAnEvenNumber)) { print(value);
}
return 0;
}
Clean Code in Functional Programming
No doubt, the functional programming movement has not stopped with C++, and that’s basically good. Many useful concepts have been incorporated into our somewhat aged programming language during the last decade, starting with C++11.
But code that is written in a functional style is not automatically good or clean code. The increasing popularity of functional programming languages during the last few years could make you believe that functional code is per se better to maintain, to read, to test, and is less error prone than, for instance, object-oriented code. But that’s not unconditionally true! On the contrary, nifty elaborated functional code that is doing non-trivial things can be very difficult to understand.
Let’s, for example, look at a simple fold operation that is very similar to one of the previous examples:
// Build the sum of all product prices
const Money sum = std::accumulate(begin(productPrices), end(productPrices), 0.0);
If you read this without the explaining source code comment…is this intention revealing code? Remember what you learned in Chapter 4 about comments. Whenever you feel the urge to write a source code comment, you should first think about how to improve the code so that the comment becomes superfluous.
So, what we really want to read or write is something like this:
const Money totalPrice = buildSumOfAllPrices(productPrices);
333
![](/html/75672/2303/html_cw6SR1shwt.1WUZ/htmlconvd-KksEvx344x1.jpg)
Chapter 7 Functional Programming
You prefer the functional programming style over OO? Okay, but I’m sure that you will agree that KISS, DRY, and YAGNI (see Chapter 3) are also very good principles in functional programming! Do you think that you can ignore the single responsibility principle (see Chapter 6) in functional programming? Forget it! If a function does more than one thing, it will lead to similar problems as in object orientation. I hope do not have to mention that good and expressive naming (see Chapter 4 about good names) is also enormously important for the understandability and maintainability of code in a functional environment. Always keep in mind that developers spend much more time reading code than writing code.
Note The principles of good software design still apply, regardless of the programming style you use!
Thus, we can conclude that most design principles used by object-oriented software designers and programmers can also be used by functional programmers.
Personally, I prefer a balanced mix of both programming styles. There are many design challenges that can be solved perfectly using object-oriented paradigms. Polymorphism is a great benefit of OO. We can take advantage of the dependency inversion principle (see the eponymous section in Chapter 6), which allows us to invert source code and runtime dependencies.
Instead, complex mathematical computations and algorithms can be better solved using a functional programming style. And if high and ambitious performance and efficiency requirements must be fulfilled, which will inevitably require a parallelization of certain tasks, functional programming can play its trump card.
Regardless of whether you prefer to write software in an object-oriented way, or in a functional style, or in an appropriate mixture of both, you should always remember the following quote:
“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.”
—John F. Woods, 1991, in a post to the comp.lang.c++ newsgroup
334
CHAPTER 8
Test-Driven Development
“Project Mercury ran with very short (half-day) iterations that were time boxed. The development team conducted a technical review of all changes, and, interestingly, applied the Extreme Programming practice of test-first development, planning, and writing tests before each micro-increment.”
—Craig Larman and Victor R. Basili,
Iterative and Incremental Development: A Brief History. IEEE, 2003
In Chapter 2, “Build a Safety-Net,” we learned that a suite of well-crafted and fast unit tests can ensure that our code works correctly. So far, so good. But what is so special about test-driven development (TDD) so that this topic justifies a dedicated chapter?
Especially in recent years, the discipline of test-driven development, a so-called test- first approach, has gained in popularity. TDD has become an important ingredient of the toolbox of software craftspeople. That’s a little bit surprising, because the basic idea of test-first approaches is nothing new. Project Mercury, which is mentioned in the opening quote, was the first human spaceflight program of the United States and was conducted under the direction of NASA from 1958 through 1963. Although what was practiced in that project about 60 years ago as “test-first development” certainly is not exactly the kind of TDD as we know it today, we can say that the basic idea was present quite early in professional software development.
But then it seemed that this approach was forgotten for decades. In countless projects with billions of lines of code, the tests were postponed at the end of the development process. The sometimes-devastating consequences of this right-shifting of the tests in the project’s schedules are known: if time is getting short in the project, one of the first things usually abandoned by the development team are the important tests.
335
© Stephan Roth 2021
S. Roth, Clean C++20, https://doi.org/10.1007/978-1-4842-5949-8_8
Chapter 8 Test-Driven Development
With the increasing popularity of agile practices in software development and the coming up of a new method called eXtreme Programming (XP) at the beginning of the 2000s, test-driven development was rediscovered. Kent Beck wrote his famous book Test -Driven Development: By Example [Beck02], and test-first approaches like TDD experienced a renaissance and became increasingly important tools in the toolbox of software craftspeople.
In this chapter, I not only explain that although the term “test” is included in test- driven development, it is not primarily about quality assurance. TDD offers many more benefits than just a simple validation of the correctness of the code. Rather I explain the differences between TDD and what is sometimes called plain old unit testing (POUT), followed by the discussion of the workflow of TDD in detail, supported by a detailed practical example that shows how to do it in C++.
The Drawbacks of Plain Old Unit Testing (POUT)
No doubt, as we’ve seen in Chapter 2, a suite of unit tests is basically a much better situation than having no tests in place. But in many projects the unit tests are written somehow parallel to the implementation of the code to be tested, sometimes even completely after finalization of the module to be developed. The UML activity diagram depicted in Figure 8-1 visualizes this process.
336
![](/html/75672/2303/html_cw6SR1shwt.1WUZ/htmlconvd-KksEvx347x1.jpg)
Chapter 8 Test-Driven Development
Figure 8-1. The typical process flow in traditional unit testing
This widespread approach is occasionally also referred to as plain old unit testing (POUT). Basically, POUT means that the software will be developed “code first”, and not test first; meaning that the unit tests are written always after the code to be tested has been written. And to many developers this order appears to be the only logical sequence. They argue that to test something, obviously the thing to be tested needs to have
337