![](/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 4 Basics of Clean C++
It is the title of a movie, so obviously it is a string and not an integer! Do not include the type of a variable or constant in its name. In a following section on Hungarian notation, I will take up this topic again.
Avoid Cryptic Abbreviations
When choosing a name for your variables or constants, use full words instead of cryptic abbreviations. There should only be rare exceptions to this rule and only in the case that an abbreviation is very well known in a certain domain, for example, IBAN (short for International Bank Account Number) in the financial world.
The reason is obvious: cryptic abbreviations reduce the readability of your code significantly. Furthermore, when developers talk about their code, variable names should be easy to pronounce.
Remember the variable named nPar on Line 8 from our OpenOffice code snippet? Neither is its meaning clear, nor can it be pronounced in a good manner.
Listing 4-10 shows a few more examples of Dos and Don’ts.
Listing 4-10. Some Examples of Good and Bad Names
std::size_t idx; |
|
|
// Bad! |
|
std::size_t index; |
|
|
// Good; might be sufficient in some cases |
|
std::size_t customerIndex; |
// To be preferred, especially in situations where |
|||
|
|
|
// several objects are indexed |
|
Car rcar; |
// Bad! |
|
|
|
Car rentedCar; |
// Good |
|
|
|
Polygon ply1; |
|
// |
Bad! |
|
Polygon firstPolygon; |
// |
Good |
|
|
unsigned int nBottles; |
|
// |
Bad! |
|
unsigned int bottleAmount; |
// |
Better |
||
unsigned int bottlesPerHour; // |
Ah, the variable holds a work value, |
|||
|
|
|
// |
and not an absolute number. Excellent! |
const double GOE = 9.80665; |
|
// Bad! |
const double gravityOfEarth = 9.80665; // More expressive, but misleading. The constant is
74
Chapter 4 Basics of Clean C++
// not a gravitation, which would be a force in physics.
const double gravitationalAccelerationOnEarth = 9.80665; // Good. constexpr Acceleration gravitationalAccelerationOnEarth = 9.80665_ms2; // Wow!
Look at the last line, which I have commented with “Wow!” That looks pretty convenient, because it is a familiar notation for scientists. It looks almost like teaching physics at school. And yes, that’s really possible in C++, as you will learn in one of the sections about type-rich programming in Chapter 5.
Avoid Hungarian Notation and Prefixes
Do you know Charles Simonyi? He is a Hungarian-American computer software expert who worked as a Chief Architect at Microsoft in the 1980s. Maybe you remember his name in a different context. Charles Simonyi is a space tourist and has made two trips to space, one of them to the International Space Station (ISS).
He also developed a notation convention for naming variables in computer software, named the Hungarian notation, which has been widely used inside Microsoft and later also by other software manufacturers.
When using Hungarian notation, the type, and sometimes also the scope, of a variable are used as a naming prefix for that variable. Listing 4-11 shows a few examples.
Listing 4-11. Some Examples of Hungarian Notation with Explanations
bool fEnabled; |
// |
f = a boolean flag |
int nCounter; |
// |
n = number type (int, short, unsigned, ...) |
char* pszName; |
// |
psz = a pointer to a zero-terminated string |
std::string strName; // |
str = a C++ stdlib string |
|
int m_nCounter; |
// |
The prefix 'm_' marks that it is a member variable, |
|
// |
i.e. it has class scope. |
char* g_pszNotice; |
// |
That's a global(!) variable. Believe me, I've seen |
|
// |
such a thing. |
int dRange; |
// |
d = double-precision floating point. In this case |
|
|
it's |
|
// |
a stone-cold lie! |
75
![](/html/75672/2303/html_3iAzqyDIia.Sav3/htmlconvd-3gbB7Q88x1.jpg)
Chapter 4 Basics of Clean C++
Note Do not use Hungarian notation, or any other prefix-based notation, by encoding the type of a variable in its name!
Hungarian notation was potentially helpful in a weakly typed language like C. It may have been useful at a time when developers used simple editors for programming, and not IDEs that have a feature like “IntelliSense.”
Modern and sophisticated development tools today support the developer very well and show the type and scope of a variable. There are no good reasons anymore to encode the type of a variable in its name. Far from it, such prefixes can impede the train of readability of the code.
At worst, it may even happen that during development the type of a variable is changed without adapting the prefix of its name. In other words, the prefixes tend to turn into lies, as you can see from the last variable in the previous example. That’s really bad!
Another problem is that in object-oriented languages that support polymorphism, the prefix cannot be specified easily, or a prefix can even be puzzling. Which Hungarian prefix is suitable for a polymorphic variable that can be an integer or a double? idX? diX? How do we determine a suitable and unmistakable prefix for an instantiated C++ template?
By the way, meanwhile even Microsoft’s so-called general naming conventions stress that one should not use Hungarian notation anymore.
If you want to mark the member variables of a class, I recommend you use an appended underscore instead of prefixes like the widely used m_..., as in this example:
#include <string>
class Person { //...
private: std::string name_;
};
Avoid Using the Same Name for Different Purposes
Once you’ve introduced a meaningful and expressive name for any kind of software entity (e.g., a class or component), a function, or a variable, you should ensure that its name is never used for any other purpose.
76