写出可复用代码的基本思想与实践
引言
在 “代码可复用性问题兼谈团队协作 ” 一文中,谈到难以写出可复用代码的一些不好的习惯和阻碍因素。本文讲讲写出可复用代码的基本技巧和实践。
代码可复用性,关键在于发现业务逻辑里的通用性部分。同时,能够发现业务逻辑里的通用部分,并能提取出来,有助于做出更好的设计,提升研发效率(一次编写,多次使用)。
基本思想
写出可复用代码的基本思想:抽离共性(抽象与标准化)、拆分逻辑(单一职责)、定义接口和API(封装)、函数编程(编程技艺)。
写出可复用代码的基本思想,分为战略和战术两层。
“战略”层面,则需要对逻辑共性和差异的思考和提炼。通常包括两层:
业务无关的技术性逻辑:“将通用性的技术逻辑与差异性的业务逻辑相分离” ;
业务核心能力抽象:可复用的业务基础设施和领域组件。
战术层面,就是逻辑拆分和接口定义。拆分粒度越细,可复用的可能性越高。通过拆分成一系列有层次的、细粒度的函数或方法,就能建立一个可复用的代码基础。
在逻辑良好拆分的基础上,为每一段逻辑定义易用的接口,做良好的封装,则成功一大半。
最后,还需要掌握一些有用的编程技巧。泛型和函数式编程是一对强大的组合,有助于写出精炼、可复用的代码。 可阅: “一次代码重构的思考及探索”。
实践
工具类
工具类是不依赖外部服务的“输入-输出”型函数。工具类最适合于做成可复用的,也容易写单测。工具类和单测都可以通过ChatGPT自动生成。
比如:“构造与使用分离:命中内容高亮及合并的展示问题解决实现”
比如:“精练代码:一次Java函数式编程的重构之旅 ” 重构得到的一系列 Util。
业务辅助类
业务辅助类是不依赖于外部服务的业务辅助工具类。这种类一般很小,能够涵盖很多业务点。而正是业务点的串联,构成了主体业务逻辑。比如:
/**
* 从 agentDetection 获取可疑脚本信息
*/
public static ScriptDTO getScript(AgentDetection agentDetection) {
if (AgentDetectionHelper.isInadequateAgentDetection(agentDetection)) {
return null;
}
List<ScriptDTO> scripts = agentDetection.getAgentDetectionDetail().getDetail().getScripts();
if (CollectionUtils.isNotEmpty(scripts)) {
return scripts.get(0);
}
return null;
}
业务组件类
业务小组件类,依赖外部服务,提供单一的功能。 这种一般粒度比较小。封装成小组件类很适合业务复用。这种业务小组件通常散落在各种 service 的 private 方法里,而导致难以复用。
@Component
public class HostInfoHelper {
private static final Logger LOG = LogUtils.getLogger(HostInfoHelper.class);
private final HostProviderClient hostProviderClient;
@Autowired
public HostInfoHelper(HostProviderClient hostProviderClient) {
this.hostProviderClient = hostProviderClient;
}
public HostDto getHost(String agentId) {
if (agentId == null) {
LOG.error("agentId is empty");
return null;
}
HostDto host = hostProviderClient.getHost(agentId);
if (null == host) {
LOG.warn("No host found by agentId: {}", agentId);
return null;
}
return host;
}
}
统一机制的封装
比如 ssdeep 检测。 webshell 和 可疑脚本的 ssdeep 检测的逻辑基本相同。都是一段算法 + 样本检测缓存库 + 样本检测内存缓存 。 可以封装成统一的。公司源代码,就不便公开了。
统一机制需要指明调用来源,方便在必要的时候做差异处理。统一机制封装有点类似微服务提供的外部接口。
制订标准数据格式
比如很多检测能力都要用到文件上传。进程执行可疑脚本检测需要先上传文件,webshell 检测也需要先上传文件,病毒检测也需要先上传文件。如果我要封装一个业务功能,那么首先要封装出这个业务功能所需信息的标准数据格式。然后根据这个数据格式来构建业务功能。
/**
* 文件上传所需信息
* 目前文件上传下载主要依赖文件的两个信息:
* 1. md5
* 2. fpath: 对于容器来说,fpath 是容器在宿主机里的路径
*
* Created by qinshu on 2022/1/11
*/
@Getter
@Setter
@Builder
public class IdsFileUploadInfo {
/** 文件MD5 */
private String md5;
/** 文件 sha256 值,未来会用这个值 */
private String sha256;
/** 文件路径 */
private String fpath;
/** 业务来源,用于文件上传回调 */
private String app;
/** 操作系统平台,不同平台使用不同上传脚本 */
private int osType;
/** 上传优先级 */
private int priority;
/** 文件保存天数,可选 */
private int keepDays;
/** 主机ID, 可选 */
private String agentId;
/** 客户ID,可选 */
private String comId;
/** 业务对象ID,用于查询关联业务表, 可选 */
private String bizId;
/** 文件上传后是否需要回调,一般是需要回调,更改任务表状态 */
private boolean callback = Boolean.TRUE;
}
共享库封装
更大层面复用的就是共享库封装。比如入侵检测流程,基本可以看成是一系列组件的执行。每一个组件都可以是可复用的。
小结
本文提炼了写出可复用代码的一些基本技巧及实践。 要写出可复用的代码,基本思想是逻辑拆分。泛型和函数式编程是一对强大的组合,有助于写出精炼、可复用的代码。具体的工程技术手段有:
- 工具类
- 业务辅助类
- 业务小组件
- 统一机制封装
- 制订标准数据格式
- 共享库。