JavaScript重构:消除依赖相似元素列表对象的代码重复
引言
假设有10个入参均为(uid, data)的异步函数:
const a = async (uid, data) => { ... }; const b = async (uid, data) => { ... }; const c = async (uid, data) => { ... }; const d = async (uid, data) => { ... }; const e = async (uid, data) => { ... }; const f = async (uid, data) => { ... }; const g = async (uid, data) => { ... }; const h = async (uid, data) => { ... }; const i = async (uid, data) => { ... }; const j = async (uid, data) => { ... };
以及4个业务键:"key1", "key2", "key3", "key4"
当前已定义结构固定的常量映射对象:
const OBJECT = { // 固定结构常量 key1: [a, b, c, d, e, f, h, i, j], key2: [a, b, c, d, f, i, j], key3: [a, b, c, d, e, f, g, h, i, j], key4: [a, b, c, d, e, f, h, i, j], }
现有代码的使用方式如下:
const uid = uuid.v4(); const dynamicData = { // 可能包含任意个key1-key4范围内的业务键 key1, key2, }; let selectedMethods = []; Object.keys(dynamicData).forEach((key) => { if (key in OBJECT) { selectedMethods.push(...OBJECT[key]); } }); // 借助lodash做数组去重 selectedMethods = _.uniq(selectedMethods); const promises = selectedMethods.map( (method) => method(uid, dynamicData) ); await Promise.all(promises);
问题描述
现有代码存在两个核心问题:
- 常量对象初始化存在大量重复的函数引用,维护成本高,容易出现漏改、错改
- 执行逻辑是散落在业务中的过程式面条代码,所有方法入参模式完全一致但没有做逻辑收敛,复用性差
需要给出针对性的优化方案,提升代码可维护性。
优化方案
一、常量定义层优化:消除重复,收敛维护入口
原始实现最大的痛点是同一个函数引用在多个数组中重复书写,调整公共方法时需要同步修改多处配置,极易出现不一致问题。优化核心思路是抽离公共部分,只维护差异配置:
- 统一收敛所有业务方法的定义入口,后续新增、删除、替换方法仅需修改一处
- 抽离所有业务键共有的基础方法集,不需要在每个key的配置中重复书写
- 每个业务键仅配置自身和基础集有差异的增量方法
- 最终常量映射由基础集+增量方法自动生成,生成后冻结保证不可变性,避免运行时意外篡改
优化后的常量定义代码:
// 1. 所有方法的统一维护入口 const METHOD_MAP = Object.freeze({ a, b, c, d, e, f, g, h, i, j }); // 2. 所有业务键共有的基础方法集 const BASE_METHODS = Object.freeze([a, b, c, d, f, i, j]); // 3. 每个业务键的专属增量方法配置,仅写和基础集的差异部分 const KEY_METHOD_CONFIG = Object.freeze({ key1: [e, h], key2: [], key3: [e, g, h], key4: [e, h], }); // 4. 自动生成最终常量映射,生成后冻结 const OBJECT = Object.freeze( Object.fromEntries( Object.entries(KEY_METHOD_CONFIG).map(([key, extraMethods]) => [ key, Object.freeze([...BASE_METHODS, ...extraMethods]) ]) ) );
该实现消除了90%以上的重复配置,后续调整公共方法仅需修改BASE_METHODS,调整单个key的方法仅需修改对应增量数组,维护成本大幅降低。
二、执行逻辑层重构:封装复用,收敛流程
原始执行逻辑是过程式代码,每次使用都需要重复写遍历、去重、执行的逻辑,且依赖外部lodash库做去重。优化核心思路是把整个执行流程封装为独立的可复用函数,业务侧仅需传入参数获取结果,不需要关心内部实现细节:
- 用原生
Set结构实现自动去重,移除对lodash的依赖 - 把uid生成、方法匹配、批量执行、异常边界判断的逻辑全部收敛到函数内部
- 保持原有执行逻辑不变,不会引入业务兼容问题
优化后的执行逻辑代码:
/** * 按动态配置匹配并执行对应方法 * @param {Record<string, any>} dynamicData 动态业务配置,key为业务键 * @returns {Promise<any[]>} 所有匹配方法的执行结果集合 */ async function runMatchedMethods(dynamicData) { const uid = uuid.v4(); // 用Set自动去重,避免重复执行同一个方法 const matchedMethods = new Set(); for (const key of Object.keys(dynamicData)) { const methodList = OBJECT[key]; // 不存在的key直接跳过,避免异常 if (!methodList) continue; methodList.forEach(method => matchedMethods.add(method)); } // 批量执行所有方法,统一等待结果 return Promise.all( Array.from(matchedMethods).map(method => method(uid, dynamicData)) ); }
业务侧调用时仅需一行代码即可完成原有逻辑:
const execResults = await runMatchedMethods(dynamicData);
可选扩展方向
如果后续业务复杂度提升,可以在现有分层结构上快速扩展,不需要重构核心逻辑:
- 如果需要支持「排除特定方法」的配置,仅需把
KEY_METHOD_CONFIG的配置项改为{ include: [], exclude: [] }结构,生成方法列表时过滤掉exclude中的方法即可 - 如果需要方法按顺序执行,仅需把
Promise.all替换为for...of循环依次调用即可 - 如果需要统一加错误捕获、超时、日志逻辑,仅需在方法执行的map回调中统一添加即可,不需要修改每个业务方法
内容的提问来源于stack exchange,提问作者Raul
相关产品推荐
相关产品推荐

