- •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 6 Modularization
Listing 6-38. The Module Interface Unit of the financialmath Module
export module financialmath;
export namespace financialmath {
long double calculateCompoundedInterest(const long double initialCapital, const long double rate,
const unsigned short term);
}
Listing 6-39. The Module Implementation Unit of the financialmath Module
module;
#include <cmath> module financialmath;
namespace financialmath {
long double calculateCompoundedInterest(const long double initialCapital, const long double rate,
const unsigned short term) {
return initialCapital * pow((1.0 + rate / 100.0), term);
}
}
Of course, this division also has some disadvantages. As with the well-known separation of header and source code files, the separation of a module into an interface and implementation unit violates the DRY principle (see Chapter 3).
Okay, that was modules in a nutshell. There is much more to tell about modules, such as module partitions, or the creation of submodules, but that would go far beyond the scope of this book. Now let’s take a look at what this new concept means to clean code developers, and what impact it has on the architecture of a software.
The Impact of Modules
The most frequently mentioned advantage of C++20 modules is that with this new language feature, the compilation speed is increased. This is basically true and is also good news, but it is only a very small, and I think not the most interesting aspect.
290
Chapter 6 Modularization
I believe that C++20 modules will have a greater impact on the whole C++ ecosystem than any other feature added after C++98. Modules have the potential to reduce—and eventually eliminate—the preprocessor and to get rid of most or all C-style macros. They change how C++ projects are compiled, i.e., they will have an impact on build systems and CI/CD tool chains. And even if software architecture is much more than just defining components and structuring the code, modules also have an impact on how C++ projects are organized and structured.
Modules offer true encapsulation, i.e. the information hiding principle that we know from Chapter 3 is greatly supported. You can explicitly specify a module’s interface that should be exported; thus, you can define what is publicly accessible and what not. We can bundle a bunch of modules into a bigger module, this enables you to build a logical structure, for instance, a hierarchical breakdown structure even on component level as depicted in Figure 6-1. All these features can significantly increase the understandability, maintainability, and extensibility of large and complex C++ development projects. And from a clean code developer’s point of view, we can get rid of a lot of ugly, C-style macros.
291
CHAPTER 7
Functional Programming
For the past several years, a programming paradigm has experienced a renaissance that’s often viewed as a kind of counterdraft to object orientation. We are talking about functional programming.
One of the first functional programming languages was Lisp (The uppercase “LISP” is an older spelling, because the name of the language is an abbreviation for “LISt Processing”). It was designed by the American computer scientist and cognitive scientist John McCarthy in 1958 at the Massachusetts Institute of Technology (MIT). McCarthy also coined the term “artificial intelligence” (AI), and he used Lisp as the programming language for AI applications. Lisp is based on the so-called Lambda Calculus (λ calculus), a formal model that was introduced in the 1930s by the American mathematician Alonzo Church.
THE LAMBDA CALCULUS
It is difficult to find a painless introduction into the lambda calculus. Many essays on this subject are scientifically written and require a good knowledge of mathematics and logic. I will not try to explain the lambda calculus here, because it is not the main focus of this book. But you can find countless explanations on the Internet; just use the search engine of your trust, and you will get hundreds of hits.
The lambda calculus can be regarded as the simplest and smallest programming language possible. It consists of two parts: one single function definition scheme and one single transformation rule. These two components are sufficient to create a generic model for the formal description of functional programming languages, like LISP, Haskell, Clojure, etc.
293
© Stephan Roth 2021
S. Roth, Clean C++20, https://doi.org/10.1007/978-1-4842-5949-8_7
Chapter 7 Functional Programming
In fact, Lisp is a family of computer programming languages. Various dialects of Lisp have emerged. For instance, everyone who has used a member of the famous Emacs text editor family, such as GNU Emacs or X Emacs, knows the dialect Emacs Lisp that is used as a scripting language for extension and automation.
Noteworthy functional programming languages developed using Lisp include:
•\ Scheme: A Lisp dialect with static binding that was developed in the 1970s at the MIT Artificial Intelligence Laboratory (AI Lab).
•\ Miranda: The first purely and lazy functional language that was commercially supported.
•\ Haskell: A general-purpose, purely functional programming language named after the American logician and mathematician Haskell Brooks Curry.
•\ Erlang: Developed by the Swedish telecommunication company Ericsson with a main focus on building massive scalable and high reliable real-time software systems.
•\ F# (pronounced F sharp): A multiparadigm programming language and a member of the Microsoft .NET Framework. The main paradigm of F# is functional programming, but it allows the developer to switch to the imperative/object-oriented world of the .NET ecosystem.
•\ Clojure: A modern dialect of the Lisp programming language created by Rich Hickey. Clojure is purely functional and runs on the Java virtual machine and the Common Language Runtime (CLR; the runtime environment of the Microsoft .NET Framework).
Functional programming languages are still not as widely used as their imperative relatives, such as the object-oriented ones, but they are increasing in dissemination. Examples are JavaScript and Scala, which admittedly are both multiparadigm languages (i.e., they are not purely functional). They have both become increasingly popular, especially in web development, in part due to their functional programming capabilities.
This is reason enough to dive deeper into this topic and to explore what this style of programming is all about, as well as discuss what modern C++ has to offer in this direction.
294