共计 3411 个字符,预计需要花费 9 分钟才能阅读完成。
C++ 代码提供了足够的灵活性,因而对于大部分工程师来说都很难把握。本文介绍了写好 C ++ 代码须要遵循的 10 个最佳实际,并在最初提供了一个工具能够帮忙咱们剖析 C ++ 代码的强壮度。原文:10 Best practices to design and implement a C++ class
1. 尽可能尝试应用新的 C ++ 规范
到 2022 年,C++ 曾经走过了 40 多个年头。新的 C ++ 规范实际上简化了许多令人丧气的细节,提供了新的古代办法来改良 C ++ 代码,但让开发人员意识到这一点并不容易。
以内存治理为例,这可能是 C ++ 中受到最多批评的机制。多年来,对象调配都是由 new 关键字实现的,开发人员肯定得记住在代码的某个中央调用 delete。“古代 C ++”解决了这个问题,并促成了共享指针的应用。
2. 应用命名空间模块化代码
古代 C ++ 库宽泛应用命名空间来模块化代码库,它们利用“Namespace-by-feature”办法,按性能划分命名空间来反映功能集,将单个个性 (且仅与该个性) 相干的所有内容放到单个命名空间中。从而使得命名空间具备高内聚性和高模块化,并且耦合最小,紧耦合的我的项目被放在了一起。
Boost 是按个性分组的最佳示例,其蕴含数千个命名空间,每个命名空间用于对特定的个性进行分组。
3. 形象
数据抽象是 C ++ 中面向对象编程最根本和最重要的个性之一。形象意味着只显示根本信息而暗藏细节,数据抽象指的是仅向内部世界提供对于数据的根本信息,暗藏背景细节或实现。
只管许多书籍、网络资源、会议演讲者和专家都举荐这种最佳实际,但在很多我的项目中,这条规定依然被忽略了,许多类的细节并没有被暗藏。
4. 类越小越好
具备多行代码的类型应该被划分为一组较小的类型。
须要很大的急躁重构一个大的类,甚至可能须要从头从新创立所有货色。以下是一些重构倡议:
- BigClass 中的逻辑必须被分成更小的类。这些较小的类最终可能成为嵌套在原始 God Class 中的公有类,God Class 的实例对象由较小嵌套类的实例组成。
- 较小的类划分应该由 God Class 负责的多个职责驱动。要确定这些职责,通常须要查找与字段的子集强耦合的办法的子集。
- 如果 BigClass 蕴含的逻辑比状态多,一个好的抉择是定义一个或几个不蕴含动态字段而只蕴含纯静态方法的动态类。纯静态方法是一种只依据输出参数计算结果的函数,它不读取或调配任何动态或实例字段。纯静态方法的次要长处是易于测试。
- 首先尝试保护 BigClass 的接口,并委托调用新提取的类。最初,BigClass 应该是一个没有本人逻辑的纯接口,能够为了不便将其保留,也能够将其扔掉,并开始只应用新类。
- 单元测试能够提供帮忙: 在提取办法之前为每个办法编写测试,以确保不会毁坏性能。
5. 每个类尽量提供起码的办法
蕴含 20 个以上办法的类可能很难了解和保护。
一个类有许多办法可能是实现了太多责任的症状。
兴许所面对的类管制了零碎中太多的其余类,并且曾经超出了应有的逻辑,成为了一个无所不能的类。
6. 增强低耦合
低耦合是现实状态,能够在利用中进行较少的更改实现程序的某个变更。从久远来看,能够缩小批改、增加新个性的大量工夫、精力和老本。
低耦合能够通过应用抽象类或泛型类和办法来实现。
7. 增强高内聚
繁多责任准则规定一个类不应该有多于一个更改的理由,这样的类被称为内聚类。较高的 LCOM 值通常能够意味着类的内聚性较差。有几个 LCOM 指标,取值范畴为 [0-1]。LCOM HS (HS 代表 Henderson-Sellers) 取值范畴为[0-2]。LCOM HS 值大于 1 时须要产生警觉。上面是计算 LCOM 指标:
LCOM = 1 — (sum(MF)/M*F) \
LCOM HS = (M — sum(MF)/F)(M-1)
其中……
- M 是类中办法的数量(包含静态方法和实例办法,它还包含构造函数、属性 getter/setter、事件增加 / 删除办法)。
- F 是类中实例字段的数量。
- MF 是类拜访特定实例字段的办法数量。
- Sum(MF)是该类所有实例字段的 MF 之和。
这些公式背地的根本思维能够表述如下: 如果一个类的所有办法都应用它的所有实例字段,那么这个类就是齐全内聚的,这意味着 sum(MF)=M*F,而后 LCOM = 0 和 LCOMHS = 0。
LCOMHS 值大于 1 就须要警觉了。
8. 只正文代码不能表白的内容
鹦鹉学舌的代码正文没有为读者提供任何额定的货色。代码库中充斥着嘈杂的正文和不正确的正文,促使程序员疏忽所有的正文,或者采取踊跃的措施暗藏它们。
9. 尽量不要用反复的代码
家喻户晓,反复代码的存在对软件开发和保护有负面影响。实际上,一个次要毛病是,当为了修复 bug 或增加新个性而更改反复代码的实例时,所有对应的代码必须同时更改。
产生反复代码最常见的起因是复制 / 粘贴操作,这种状况下,类似的源代码呈现在两个或多个中央。许多文章、书籍和网站都正告不要采纳这种做法,但有时实际这些倡议并不容易,开发人员还是会抉择简略的解决方案: 复制 / 粘贴大法。
应用适当的工具能够容易的从复制 / 粘贴操作中检测到反复代码,然而,在某些状况下,克隆代码很难被检测到。
10. 不变性有助于多线程编程
基本上,如果对象在创立之后状态不变,那么这个对象就是不可变 (immutable) 的。如果一个类的实例是不可变的,那么该类就是不可变的。
不可变对象极大简化了并发编程,这是反对应用它的重要理由。想想看,为什么编写适当的多线程程序是一项艰巨的工作?因为同步线程拜访资源 (对象或其余操作系统资源) 是很艰难的。为什么同步这些拜访很艰难?因为很难保障多个线程对多个对象进行的屡次写访问和读拜访之间不会呈现竞争条件。如果不再有写访问会怎么样?换句话说,如果被线程拜访的对象的状态没有扭转会怎么样?就不再须要同步了!
对于不可变类的另一个益处是它们永远不会违反里氏替换准则(LSP, Liskov Subtitution Principle),以下是维基百科对 LSP 的定义:
Liskov 的行为子类型的概念定义了可变对象可替换性的概念,也就是说,如果 S 是 T 的子类型,那么程序中 T 类型的对象能够被替换为 S 类型的对象,而不扭转该程序的任何冀望属性(例如,正确性)。
如果没有公共字段,没有能够更改其外部数据的办法,并且派生类办法无奈更改其外部数据,那么援用对象类就是不可变的。因为值不可变,所以在所有状况下都能够援用雷同的对象,不须要复制构造函数或赋值操作符。出于这个起因,倡议将复制构造函数和赋值操作符设为公有,或者从 boost::noncopyable 继承,或者应用新的 C ++ 11 个性“显式默认和删除非凡成员函数”[2]。
如何增强对这些最佳实际进行查看?
CppDepend[3]提供了名为 CQLinq[4]的代码查询语言,能够像数据库一样查问代码库。开发人员、设计人员和架构师能够自定义查问,以便轻松找到容易呈现 bug 的状况。
通过 CQLinq,能够联合来自代码度量、依赖关系、API 应用和其余模型的数据来定义十分高级的查问,以匹配容易呈现 bug 的状况。
例如,剖析 clang 源代码后,能够检测到大类:
检测到有大量办法的类:
或者检测到内聚性较差的类:
References: \
[1] 10 Best practices to design and implement a C++ class: https://issamvb.medium.com/10-best-practices-to-design-and-im…,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_a… \
[3] CppDepend: http://www.cppdepend.com/ \
[4] CQLinq: https://www.cppdepend.com/cqlinq你好,我是俞凡,在 Motorola 做过研发,当初在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。\
微信公众号:DeepNoMind
本文由 mdnice 多平台公布