C++ code provides enough flexibility that it is difficult for most engineers to grasp. This article describes 10 best practices to follow to write good C++ code, and finally provides a tool that can help us analyze the robustness of C++ code. 10 Best practices to design and implement a C++ class
By 2022, C++ has gone through more than 40 years. The new C++ standard actually simplifies many frustrating details, providing new modern ways to improve C++ code, but it’s not easy for developers to recognize this.
In the case of memory management, this is probably the most criticized mechanism in C++. For years, object assignments have been done by the new keyword, and developers must remember to call delete somewhere in the code. “Modern C++” solves this problem and facilitates the use of shared pointers.
Modern C++ libraries make extensive use of namespaces to modularize code bases, using the “Namespace-by-feature” method, dividing namespaces by function to reflect the set of functions, and putting everything related to a single attribute (and only that attribute) into a single namespace. This makes the namespace highly cohesive and highly modular, and the minimally coupled, tightly coupled items are put together.
Boost is the best example of grouping by attribute, which contains thousands of namespaces, each of which is used to group specific attributes.
Data abstraction is one of the most fundamental and important features of object-oriented programming in C++. Abstraction means showing only basic information and hiding details, and data abstraction refers to providing only basic information about data to the outside world, hiding background details or implementations.
Although many books, web resources, conference speakers, and experts recommend this best practice, in many projects, this rule is still ignored and many class details are not hidden.
Types with multiple lines of code should be divided into a smaller set of types.
It takes a lot of patience to refactor a large class and may even need to recreate everything from scratch. Here are some refactoring suggestions:
Classes with more than 20 methods can be difficult to understand and maintain.
A class with many methods may be a symptom of achieving too much responsibility.
Perhaps the class in question controls too many other classes in the system and has gone beyond the proper logic to become an omnipotent class.
Low coupling is ideal to implement a change in the program with fewer changes in the application. In the long run, you can reduce a lot of time, effort, and cost of modifying and adding new features.
Low coupling can be achieved by using abstract classes or generic classes and methods.
The principle of single responsibility stipulates that a class should not have more than one reason for change, and such a class is called an inner cluster. Higher LCOM values can often mean poorer cohesion of the class. There are several LCOM indicators with values ranging from [0-1]. LCOM HS (HS stands for Henderson-Sellers) takes a value range of [0-2]. Vigilance is required when the LCOM HS value is greater than 1. Here is the calculation of the LCOM indicator:
LCOM = 1 — (sum(MF)/M*F) LCOM HS = (M — sum(MF)/F)(M-1)
Thereinto……
The basic idea behind these formulas can be expressed as follows: If all methods of a class use all of its instance fields, then the class is fully cohesive, which means sum(MF) = M*F, then LCOM = 0 and LCOMHS = 0.
LCOMHS values greater than 1 require vigilance.
Parrot’s code comments don’t provide anything extra for the reader. The codebase is full of noisy comments and incorrect comments, prompting programmers to ignore all comments or take proactive steps to hide them.
It is well known that the presence of duplicate code has a negative impact on software development and maintenance. In fact, a major drawback is that when you change an instance of duplicate code in order to fix a bug or add a new feature, all corresponding code must change at the same time.
The most common cause of duplicate code is a copy/paste operation, where similar source code appears in two or more places. Many articles, books, and websites warn against this approach, but sometimes it’s not easy to practice these suggestions, and developers choose a simple solution: copy/paste Dafa.
Duplicate code can be easily detected from copy/paste operations with the appropriate tools, however, in some cases, clone code is difficult to detect.
Basically, if the state of an object does not change since it is created, then the object is immutable. If an instance of a class is immutable, then the class is immutable.
Immutable objects greatly simplify concurrent programming, which is an important reason to support its use. Think about it, why is writing a proper multithreaded program a daunting task? Because synchronization threads access resources (objects or other operating system resources) are difficult. Why is it difficult to synchronize these accesses? Because it is difficult to guarantee that there will be no race condition between multiple write and read accesses to multiple objects by multiple threads. What happens if there is no longer write access? In other words, what if the state of the object accessed by the thread does not change? No more syncing!
Another benefit about immutable classes is that they never violate the Liskov Subtitution Principle (LSP), and here’s what Wikipedia defines LSP:
Liskov’s concept of behavioral subtypes defines the concept of mutable object fungibility, that is, if S is a subtype of T, then an object of type T in a program can be replaced with an object of type S without changing any desired property of the program (e.g., correctness).
If there are no public fields, no methods that can change their internal data, and derived class methods cannot change their internal data, then a reference object class is immutable. Because the value is immutable, the same object can be referenced in all cases without the need to copy the constructor or assignment operator. For this reason, it is recommended to make the copy constructor and assignment operator private, either inherit from boost::noncopyable, or use the new C++11 attribute “explicitly default and remove special member functions”[2].
CppDepend[3] provides a code query language called CQLinq[4] that queries the codebase like a database. Developers, designers, and architects can customize queries to easily find bug-prone situations.
With CQLinq, very advanced queries can be defined by combining data from code metrics, dependencies, API usage, and other models to match bug-prone situations.
For example, after analyzing the clang source code, large classes can be detected:
A class with a large number of methods was detected:
Or a class with poor cohesion is detected:
References: [1] 10 Best practices to design and implement a C++ class: https://issamvb.medium.com/10-best-practices-to-design-and-implement-a-c-class-4326611827e1#:~:text=10%20Best%20practices%20to%20design%20and%20implement%20a,class%20as%20you%20can.%20…%20More%20items…%20 [2] Explicitly defaulted and deleted special member functions: http://en.wikipedia.org/wiki/C%2B%2B11#Explicitly_defaulted_and_deleted_special_member_functions [3] CppDepend: http://www.cppdepend.com/ [4] CQLinq: https://www.cppdepend.com/cqlinq
Hello, I am Yu Fan, did research and development in Motorola, and now does technical work in Mavenir, and has always maintained a strong interest in communications, networks, back-end architectures, cloud native, DevOps, CICD, blockchain, AI and other technologies, usually like to read, think, believe in continuous learning, lifelong growth, welcome to exchange and learn together. WeChat public account: DeepNoMind