目录
一、问题背景
二、代码规范方面的解决方案
2.1 拆分头文件
2.2 拆分巨型类
2.3 使用前置声明
2.4 避免在头文件中包含实现
2.5 避免头文件重复包含
2.6 将常用且变动较少的独立到一个文件
三、代码业务重构方面经验
3.1 使用PIMPL(Pointer to Implementation)技术
3.2 单例模式的使用
3.3 模板模式的使用
3.4 工厂模式的使用
3.5 备忘录模式的使用
3.6 职责链模式的使用
3.7 适配器模式的使用
3.8 插件的使用
四、总结
一、问题背景
在日常停车管控软件开发中,随着不同人员添加新的功能、接入新的设备;程序代码越来越臃肿,每一次全量编译都将近一个小时,甚至修改极少的代码编译也几乎要近一个小时,严重影响开发效率。
二、代码规范方面的解决方案
为了提升编译速度,主要从以下几个方面对代码进行了部分重构和优化,优化后的项目全量编译约二十分钟,极大提升了开发进度。
2.1 拆分头文件
随着项目需求的不断递增、新设备、新平台、新服务接口等不断接入,项目组成员之间负责功能模块不同,逐渐出现了一些巨型头文件,且这些巨型头文件被包含在近乎所有的模块中,导致这种文件每新增一些内容就会引发几乎所有模块重新编译。
本着功能模块的原则,将这些巨型头文件按照模块进行拆分,分别归入到不同的功能模块下,并规范后续开发新增模块功能需自增头文件,避免出现包含内容过多的头文件。
2.2 拆分巨型类
在优化项目过程中发现使用频次很高的类成员特别多,功能特别全面;许多新增的需求也会有人关联到这种常用类中,这也导致这种类由于功能过于全面,项目中到处都是,稍微修改即会引发大量相关文件重新编译。
本着将功能模块尽量细分的原则,一个类的功能尽量单一,将这种较大的巨型类逐渐细化拆分,降低不同文件之间的依赖。
2.3 使用前置声明
由于项目组里不同人员开发习惯不同,一部分开发人员定义的头文件包含了过多的头文件,导致文件之间的依赖性和编译时间恶化。
在项目优化中逐渐优化头文件的包含文件,去除不相关的头文件,其余相关的头文件大部分头文件实际上也没必要包含,只需要声明即可,在实现文件中在包含相应的头文件可以极大的降低头文件与其他文件之间的依赖关系。如下面代码所示:
// 在头文件中,尽量使用前置声明,而不是 #includeclass ClassA; // 前置声明
class ClassA;
class ClassB
{ public:ClassA* myObject; // 使用指针或引用,不需要完整定义
};
2.4 避免在头文件中包含实现
项目组中存在开发人员因为所新增的功能及其简单,遂将实现的实际细节逻辑一并写在头文件中,后续随着需求的复杂化和多样化后续开发人员逐渐在该基础上进行逻辑补充,导致该头文件剧烈变化,从而几乎每次新down下的代码重新编译都会因为这种头文件变动导致依赖的文件均重新编译,一定程度拖慢整体开发效率。
后续逐步将程序中头文件中的细节实现移动到对应的实现cpp文件中,将项目中头文件的定义与实现细节拆分;后续以此为规范,代码评审环节进行检测。
2.5 避免头文件重复包含
头文件通常包含在多个源文件中,规范头文件代码具有防止重复包含的设计,可以使用#pragma once或者宏。
// 使用 include 守卫
#ifndef _CLASSA_H_
#define _CLASSA_H_
class ClassA { // 类定义
};#endif // _CLASSA_H_
2.6 将常用且变动较少的独立到一个文件
项目中有一些与业务不想关的代码,如sdk的启动加载、设备的登录登出、一些必要库的加载,一些工具类的函数等等,将这些代码抽离到一个固定的头文件中,可以避免其他业务代码的变动引起这些比较固定的相关代码的重新编译。
三、代码业务重构方面经验
除却以上几种之外,实际项目中最复杂的还是业务部分代码,该部分代码随着设备的升级、新增,需求的升级新增、接入平台、开放接口功能等等急剧变化,不但引起了整体代码框架的不稳定,同时也会逐渐增加文件之间的依赖,日积月累,积重难返编译时间也会越来越长。
在程序优化过程中逐步对各个模块进行梳理,引入设计模式对业务代码进行优化,以下是一些优化经验。
3.1 使用PIMPL(Pointer to Implementation)技术
对于一些业务变动比较剧烈的函数,使用PIMPL技术后者直接上d指针进行变动业务的重构,将具体的业务细节代码隐藏在实现中,对外仅仅是一个比较稳定的指针接口,可以大幅的降低由于业务代码变动引起的相关文件跟着变动,从而导致编译时间拉长、代码框架不稳定等你问题。具体参见以下博文。
https://blog.csdn.net/WSTONECH/article/details/143989953文章浏览阅读282次。Pimpl(pointer to implementation, 指向实现的指针)是一种用来对“类的接口与实现”进行解耦合的方法。就是将真正的实现细节的Implementor从类定义的头文件中分离出去,公有类通过一个私有指针指向隐藏的实现类,是促进接口和实现分离的重要机制,可以避免在头文件中暴露私有细节。Pimpl 并不是严格意义上的设计模式(它是受制于 C++ 语言特定限制的变通方案),这种惯用法可以看作的一种特例。私有成员完全可以隐藏在共有接口之外,尤其对于闭源API的设计尤其的适合。_d指针https://blog.csdn.net/WSTONECH/article/details/143989953
3.2 单例模式的使用
在梳理业务项目过程中,各个模块不同程度的使用了单例模式;但由于是不同人员开发,实现方式也各不相同,导致代码中充斥着各种参差不齐的单例实现,有的甚至并非线程安全。对此在优化过程中结合模板技术,引入单例模板,逐步替换掉各个功能模块的单例实现,不仅使代码更加简洁清晰,也提高了程序中单例的可维护性,具体参见以下博文。
创建型模式-----(单例模式)-CSDN博客文章浏览阅读1.2k次,点赞7次,收藏6次。本文主要描述了设计模式中单例模式的两种形式,着重介绍了懒汉式单例的几种实现方式和优劣,最后介绍了一种通过局部静态变量实现的单例模板,以及该模板在项目中的应用。https://blog.csdn.net/WSTONECH/article/details/143158151
3.3 模板模式的使用
在梳理业务项目过程中,发现不少代码功能相似的代码,如不同省份的ETC接入扣费模块,虽然各个省份的接入协议具体接口数量、类型和通信方式不尽相同,但是从业务上划分基本均可分为定时上报信息相关、实时过车上报信息相关、实时扣费相关等,据此引入模板模式重构此部分代码,不仅提升了代码的可维护性,避免不同省份不同开发人员可能引入的与其他无感支付竞态问题,而且极大缩减了代码量,一定程度提升编译时间性能,具体参考以下博文。
组件写作-----模板模式-CSDN博客文章浏览阅读801次。模板模式利用虚函数的多态和稳定的任务流程模板,可以在需求变化时,只需要继承模板,将开发精力集中到具体的实现步骤即可。https://blog.csdn.net/WSTONECH/article/details/143634388
3.4 工厂模式的使用
在梳理设备相关业务过程中,从代码改动痕迹可以看的出,新型设备逐年接入,设备类型、接入方式越来越复杂,导致程序里面设备相关代码相当混乱,无论是定制修改设备相关功能或者增加新的设备接入都会引起一大堆文件的变动,代码可维护性较差,而且改动会引起想打一部分文件重新编译,一定程度拖慢编译时间,本着将业务变化与框架不变进行隔离,将业务变化控制在一定范围的原则,该部分代码引入工厂模式进行重构,具体参考以下博文。
创建型模式-----工厂模式_abstractdevice-CSDN博客文章浏览阅读946次。工厂模式主要是定义一个创建对象的接口,通过虚函数让子类决定实例化具体的对象,使得类的实例化延迟到子类中,从而达到解耦的目的。_abstractdevicehttps://blog.csdn.net/WSTONECH/article/details/143208105
3.5 备忘录模式的使用
在项目车道相关的代码梳理中发现不少的代码冗余,且界面代码与业务代码关联过于密切,导致维护改动不易,且改动容易引发连锁反应导致一部分文件跟着变动;梳理该部分业务,对车道界面和车道设备业务代码进行分离引入备忘录模式,所有车道公用一套车道UI代码,将车道上设备配置等业务信息以不同的状态存储,从而达到UI和设备配置相关代码分离的目的,提高了代码的可维护性,降低耦合度,一定程度提高了编译效率,具体可参考以下博文。
项目优化之备忘录模式-CSDN博客文章浏览阅读1k次。在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便于在不同时机可以切换到不同的状态中。https://blog.csdn.net/WSTONECH/article/details/144675792
3.6 职责链模式的使用
在项目支付相关的模块存在多种无感支付方式,之前的代码为了避免无感支付的竞态将多个无感支付方式轮询同步调用,不同支付方式之间耦合度极高,若现场新增或要求支付优先级与设定方式不同则需要修改几乎所有支付方式之间耦合部分代码,可维护性较差且耦合度高不利于代码的编译效率。为了降低各个无感支付之间的耦合度以及该部分的可维护性,优先级的可控从而引入职责链模式,配合优先级配置项可以实现各个支付方式之间解耦合,优先级可调,并且一定程度上提高编译效率,具体可参考以下博文。
项目优化之职责链模式应用-CSDN博客文章浏览阅读684次。职责链模式,是一种行为型模式,使多个对象构成对象链,将一个请求传递给对象链,哪个对象适合处理这个请求就让哪个对象来处理,可避免请求的发送者和接收者之间的耦合关系。如果请求在对象链上节点处理后仍可以往下传递,这种每个节点只处理一部分的职责链即是功能链,类似于流水线每个站点只处理任务的一部分,到流水线尽头任务即完成。职责链模式的优点发送者和接收者不需要知道彼此的具体细节。可以动态地添加或修改处理链,而不影响其他部分。请求可以被多个对象处理,不必绑定到特定的处理者。https://blog.csdn.net/WSTONECH/article/details/144859256
3.7 适配器模式的使用
对于一些类型的设备,设备型号、类型等不断更新换代,但是作为停车软件使用这些设备的基本功能基本稳定,如LED设备的显示,使用场景和方式比较固定,但是程序中针对不同设备的功能接口不统一,一方面不便于业务代码中使用,另一方面代码耦合较高,改动容易影响编译效率。因此根据业务功能制定统一的接口,结合适配器类型将不同类型型号的LED设备进行接口适配,将设备的底层代码和对外业务接口隔离,降低了代码之间耦合,提高可维护性,一定程度也提升了编译效率,具体可参考以下博文。
结构性设计模式-----适配器模式-CSDN博客文章浏览阅读894次,点赞8次,收藏2次。将一个类的接口转换为客户希望的另一个接口,Adapter模式使得原本由于接口不兼容的类不能一起工作的类可以一起工作。https://blog.csdn.net/WSTONECH/article/details/144660558
3.8 插件的使用
对于一些停车场实际并不需要对接云或者开放接口,这部分功能由于接口相对稳定,在项目优化过程将其与主程序隔离,使用插件技术在插件中完成该部分逻辑迁移。一方面对接接口的改动不影响主程序,另外一方面对接功能与主程序剥离便于插件在不同版本程序上迁移,最后由于是分开编译,一般只要不修改对接云和开放接口部分均不需要编译这两部分代码,很大程度的提高了编译效率,具体可参考如下博文。
Qt中插件的使用_qt插件-CSDN博客文章浏览阅读913次。插件是一种(遵循一定规范的应用程序接口编写出来的)程序,定位于开发实现应用软件平台不具备的功能的程序。插件必须依赖于应用程序才能发挥自身功能,仅靠插件是无法正常运行的;相反地,应用程序并不需要依赖插件就可以运行,这样一来,插件就可以加载到应用程序上并且动态更新而不会对应用程序造成任何改变(热更新)。插件就像硬件插卡一样,可以被随时删除、插入和修改,所以结构很灵活,容易修改,方便软件的升级和维护。_qt插件https://blog.csdn.net/WSTONECH/article/details/143802965?sharetype=blogdetail&sharerId=143802965&sharerefer=PC&sharesource=WSTONECH&spm=1011.2480.3001.8118
四、总结
提高程序编译效率一方面从代码文件管理方面不断降低不同文件之间的依赖关系,另外一方面从代码的业务层面剥离剧烈变动的业务逻辑和相对稳固的代码框架,尽可能的将变化部分小范围可控,尽可能的降低耦合度,这方面设计模式的使用是一个不错的选择。此外一些编译器上的配置如开启多核编译等以及一些加速编译的工具也有一定的效果,此处不详谈这方面。