C++ 类设计的实践与理解

news2024/12/23 7:00:32

前言

C++代码提供了足够的灵活性,因此对于大部分工程师来说都很难把握。本文介绍了写好C++代码需要遵循的最佳实践方法,并在最后提供了一个工具可以帮助我们分析C++代码的健壮度。

 

1. 尽可能尝试使用新的C++标准

到2023年,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特性“显式默认和删除特殊成员函数”。

如何加强对这些最佳实践进行检查?

CppDepend[3]提供了名为CQLinq[4]的代码查询语言,可以像数据库一样查询代码库。开发人员、设计人员和架构师可以自定义查询,以便轻松找到容易出现bug的情况。

通过CQLinq,可以结合来自代码度量、依赖关系、API使用和其他模型的数据来定义非常高级的查询,以匹配容易出现bug的情况。

例如,分析clang源代码后,可以检测到大类:

检测到有大量方法的类:

或者检测到内聚性较差的类:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628570.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何进行微服务测试?

微服务测试是一种特殊的测试类型,因为它涉及到多个独立的服务。以下是进行微服务测试的一般性步骤: 1. 确定系统架构 了解微服务架构对成功测试至关重要。确定每个微服务的职责、接口、依赖项和通信方式。了解这些信息可以帮助您更好地规划测试用例和测…

day09——线性回归

线性回归 一、什么是线性回归1,定义与公式2,线性回归的特征与目标的关系 二、线性回归的损失和优化原理1,损失函数2,优化算法 三、API四、实操:波士顿房价预测1,数据来源:scikit-learn2&#xf…

Acgis中实现栅格经纬度和行政区关联

写在前面 我是一个Acgis小白,写这篇博客是为了记录完成过程,如果有更高效的办法欢迎分享~ 我用的是Arcgis10.2。 需求描述 目前已有意大利的shp文件,希望将意大利划分成0.1*0.1经纬度的栅格,并且关联每个栅格中心点所属的省份信…

DBeaver复制数据库(数据库表结构以及内容)

一、 首先先建立一个数据库 (已有请忽略此步骤) (名字 字符集等按需要自己填写) 二、选择要复制的数据库 2.1右键选择 工具->转储数据库 2.2选择要导出的数据 2.3 选择要导出的路径 2.4 点击开始,等待导出完…

Revit中绘制弯曲的靠背栏杆和生成过梁

一、Revit中怎么绘制弯曲的靠背栏杆 栏杆通常我们见过位于在阳台处,但是在我们的古建筑中很常见到一种靠背栏杆,例如凉亭里面就很常见这种栏杆。那么如何绘制呢? 利用公制栏杆——支柱进行绘制 要运用放样工具进行绘制,设置一个工作平面&…

中国电子学会2023年05月份青少年软件编程Scratch图形化等级考试试卷四级真题(含答案)

2023-05 Scratch四级真题 分数:100 题数:24 测试时长:90min 一、单选题(共10题,共30分) 1. 下列积木运行后的结果是?(B)(说明:逗号后面无空格)&#xff…

mysql join 与 拆分成单表查询如何选择

参考以下文章,不错 数据库联表查询时,是直接使用join好还是分别查询到数据后自己处理较好? - 知乎 一,声明 1,数据量 首先场景是多个表数据量比较大,可能达到百万级 2,结论:最…

springboot项目启动指定对应环境的方法

1. 多环境准备 今天教大家一种多环境profile的写法,当然也可创建多个yml文件。如下所示: spring:application:name: cms-discovery-eureka-ha --- # 区分多环境 spring:profiles: peer1 server:port: 9092 eureka:in…

十、ELK安装ElastAlert 2插件飞书机器人告警(docker)

实现效果 1.创建相应挂载目录和文件 可任意位置,挂载对上就行,方便直接在宿主机修改配置。 /data/feishu-alert/config.yaml /data/feishu-alert/rules 2.编写config.yaml配置文件(/data/feishu-alert/config.yaml) #指定告警文件存放目录 rules_fo…

从C语言到C++_16(list的介绍和常用接口函数)

目录 1. list 介绍和简单使用 1.1 list介绍 1.2 list简单接口函数 1.3 push_back 和遍历 1.4 list常规接口函数使用 2. list 的其它接口函数 2.1 splice 接合 2.2 remove 删完一个值 2.3 sort和reverse 本章完。 list是个双向带头循环链表。 带头双向循环链表我们在…

21份软件测试全流程文档模板(标准版)

1、需求说明书 2、功能测试计划 3、功能测试用例 4、业务流程测试用例 5、系统安装配置说明书 6、阶段功能测试报告 7、性能测试计划 8、性能测试用例 9、性能测试报告 10、系统功能测试报告 11、需求变更说明书 12、用户建议说明书 13、验收测试报告 14、产品发布说明书 15、系…

redis下载安装

本文主要介绍如果在Centos7下安装Redis。 1.安装依赖 redis是由C语言开发&#xff0c;因此安装之前必须要确保服务器已经安装了gcc&#xff0c;可以通过如下命令查看机器是否安装&#xff1a; <span style"color:#000000"><span style"background-c…

Matlab论文插图绘制模板第101期—人口金字塔图

在之前的文章中&#xff0c;分享了Matlab双向柱状图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一种特殊的双向柱状图&#xff1a;人口金字塔图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自…

Lecture 17 Machine Translation

目录 Statistical MTNeural MTAttention MechanismEvaluationConclusion Machine translation (MT) is the task of translating text from one source language to another target language why? Removes language barrierMakes information in any languages accessible t…

Polarion工作流插件(自定义)

创建插件命名插件配置插件Condition&Function package com.polarion.capital.example.conditions;import com.polarion.alm.tracker.model.IWorkItem; import com.polarion.alm.tracker.workflow.IArguments; import com.polarion.alm.tracker.workflow.ICallContext; impo…

TIME_WAIT的处理方式

TIME_WAIT 是什么 TIME_WAIT 是指在 TCP 连接关闭时&#xff0c;等待所有分组确认被接收的状态&#xff0c;这个状态会持续 2MSL&#xff08;Maximum Segment Lifetime&#xff09;的时间&#xff0c;以确保所有分组都被接收。在这段时间内&#xff0c;该连接不能被重用。MSL …

Jmeter 实现 grpc服务 压测

一、Jmeter安装与配置 网上有很多安装与配置文章&#xff0c;在此不做赘述 二、Jmeter gRPC Request 插件安装 插件下载地址&#xff1a;JMeter Plugins :: JMeter-Plugins.org 将下载文件解压后放到Jmeter安装目录下 /lib/ext 然后在终端输入Jmeter即可打开 Jmeter GUI界面…

搞定“超超超难”剑桥面试数学题番外篇:ARM64汇编

0. 概览 在 有趣的小实验&#xff1a;四种语言搞定“超超超难”剑桥面试数学题 那篇博文中&#xff0c;我们使用 4 种语言&#xff08;x64汇编、C、Swift 以及 Ruby&#xff09;实现了一道算法题。 不过&#xff0c;其中的汇编语言对应的是 intel CPU 上的 x64 指令集&#x…

手把手教你如何把系统的前端页面改成vue 脚手架项目,实现前后端分离

这篇文章从零开始&#xff0c;介绍怎么把现有的一个商城的页面移动到vue脚手架项目上&#xff0c;实现前后端代码的完全分离。 首先&#xff0c;我们需要新建一个vue的脚手架的项目&#xff0c;在此之前&#xff0c;需要安装node.js 打开IntelliJ IDEA或者winR&#xff0c;输入…

【Java基础学习打卡03】计算机中数据的表示、存储与处理

目录 前言一、数据的表示1.数据与信息2.计算机中的数据3.计算机中数据的单位 二、数据的存储三、数据的处理1.进位计数值2.进制间转换 四、字符编码总结 前言 本小节主要介绍在计算机中数据的表示、存储与处理。要知道计算机内部使用二进制数据&#xff0c;也就是0和1组成的数…