- •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++
I think it is pretty obvious that using the same name for different purposes can be puzzling and can mislead readers. Don’t do that. That’s all I have to say about that topic.
Comments
“If the code and the comments disagree, then both are probably wrong.”
—Norm Schryer, Computer Scientist and Division
Manager at AT&T Labs Research
Do you remember your beginnings as a professional software developer? Do you still remember the coding standards of your company during those days? Maybe you’re still young and not long in business, but the older ones will confirm that most of those standards contained a rule that professional code must always be properly commented. The absolutely comprehensible reasoning for this rule was so that any other developer, or a new team member, could easily understand the intent of the code.
On first sight, this rule seems like a good idea. In many companies, the code was therefore commented extensively. In some projects, the ratio between productive lines of code and comments was almost 50:50.
Unfortunately, it was not a good idea. On the contrary: This rule was an absolutely bad idea!
It is completely wrong in several respects, because comments are a code smell in most cases. Comments are necessary when there is need for explanation and
clarification. And that often means that the developer was not able to write simple and self-explanatory code.
Do not misunderstand: there are some reasonable use cases for comments. In some situations, a comment might actually be helpful. I present a few of these rather rare cases at the end of this section. But for any other case, this rule should apply, and that’s also the heading of the next section: “Let the Code Tell the Story”.
Let the Code Tell the Story
Just imagine watching a movie that’s only understandable when individual scenes are explained using a textual description below the picture. This film would certainly not be a success. On the contrary, the critics would pick it to pieces. No one would watch such a bad movie. Good films are therefore successful because they tell a gripping story only through the pictures and the dialogues of the actors.
77
Chapter 4 Basics of Clean C++
Storytelling is basically a successful concept in many domains, not only in film production. When you think about building a great software product, you should think about it as telling the world a great and enthralling story. It’s not surprising that Agile project management frameworks like Scrum use phrases called “user stories” as a way to capture requirements from the perspective of the user. And as I’ve explained in a section about preferring domain-specific names, you should talk to stakeholders in their own language.
Note Code should tell a story and be self-explanatory. Comments must be avoided whenever possible.
Comments are not subtitles. Whenever you feel the desire to write a comment in your code because you want to explain something, you should think about how you can write the code better so that it is self-explanatory and the comment is therefore superfluous. Modern programming languages like C++ have everything that’s necessary to write clear and expressive code. Good programmers take advantage of that expressiveness to tell stories.
“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.”
—Martin Fowler, 1999
Do Not Comment Obvious Things
Once again, we take a look at a small and typical piece of source code that was commented extensively. See Listing 4-12.
Listing 4-12. Are These Comments Useful?
customerIndex++; // Increment index
Customer* customer = getCustomerByIndex(customerIndex); // Retrieve the |
customer |
|
|
at the given index |
|
CustomerAccount* account = customer->getAccount(); |
// Retrieve the |
|
|
customer's account |
|
account->setLoyaltyDiscountInPercent(discount); |
// Grant a 10% discount |
78
Chapter 4 Basics of Clean C++
Please don’t insult the reader’s intelligence! It is obvious that these comments are totally useless. The code itself is largely self-explanatory. They don’t add new or relevant information. Much worse is that these useless comments are a kind of duplication of the code. They violate the DRY principle discussed in Chapter 3.
Maybe you’ve noticed another detail. Take a look at the last line. The comment speaks literally of a 10% discount, but in the code there is a variable or constant named discount that is passed into the function or method setLoyaltyDiscountInPercent(). What has happened here? Remember the quote by Norm Schryer from the beginning of this section? A reasonable suspicion is that this comment has turned into a lie because the code was changed, but the comment was not adapted. That’s really bad and misleading.
Comments defy any quality assurance measure. You cannot write a unit test for a comment. Thus, they can become misleading and outright wrong very quickly without anyone noticing.
Don’t Disable Code with Comments
Sometimes comments are used to disable a bunch of code that should not be translated by the compiler. A reason often mentioned by developers for this practice is that one could possibly use this piece of code again later. They think, “Maybe one day ... we’ll need it again.” What could happen then is that from time to time you’ll find a stone-old piece of code from ancient times, commented out and forgotten for years, as shown in Listing 4-13.
Listing 4-13. An Example of Commented-Out Code
// This function is no longer used (John |
Doe, 2013-10-25): |
|
/* |
|
|
double calcDisplacement(double t) { |
|
|
const double goe = 9.81; |
// |
gravity of earth |
double d = 0.5 * goe * pow(t, 2); |
// |
calculation of distance |
return d; |
|
|
} |
|
|
*/ |
|
|
A major problem with commented-out code is that it adds confusion with no real benefit. Just imagine that the disabled function in the example in Listing 4-13 is not the
79