title: AHA author: Gamehu tags:
干研发的有个高频词语:抽象,这个词语可应用于各种场景,我今天聊的是代码抽象,在此篇就比较low逼的理解成代码复用吧,不然感觉有点虚。
为啥记录这个呢还是源于近段时间遇到的一些矛盾,重复代码该不该都抽出来,在这之前我会毫不犹豫的说应该,包括现在团队里也几乎是这样的声音,但是是不是就一定对呢?现在我觉得这个观点是不对的,因为我发现有些代码抽出来之后反倒变得越来越不可掌控。
所以我在思考克制抽象是不是也应该提出来。为了验证这个思考,遂搜了搜,别说还真有那么些大佬早就提出了这个观点。
AHA (读作"Aha!" ):Avoid Hasty Abstractions(避免草率的抽象)
读了几篇文章特别感动,尤其是Sandi Metz的 The Wrong Abstraction,特别有共鸣。
核心观点就是
宁愿复制而不是错误的抽象
具体的支撑克制抽象内容,这几篇文章说的很清楚了,我就不再来一遍了。
/**
* 获取某个CI模型数据 v1.0
* @param {object} code 模型code
* @returns {object} 格式化之后的模型对象
*/
export const getCi = async (code) => {
const meta = await request.post('/api/v1/model/ci/getCi', { code });
// 绑定数据字典
meta.attributes = meta.attributes.map((item) => {
const attr = { ...item };
if (attr.changeValue === 'dict') {
if (!DICT.get(attr.code)) {
rlog.error(`找不到 ${attr.code} 对应的数据字典`);
} else {
attr.dict = DICT.get(attr.code).items;
}
}
return attr;
});
return meta;
};
/**
* 获取某个CI模型数据 v2.0
* @param {object} code 模型code
* @param {boolean} userVisibleFilter 是否按照模型的userVisible过滤,发现页面不过滤
* @returns {object} 格式化之后的模型对象
*/
export const getCi = async (code, userVisibleFilter = true) => {
const meta = await request.post('/api/v1/model/ci/getCi', { code });
// 获取过滤userVisible=true的属性(用户可见)
const { attributes } = meta;
const visibleAttributes = userVisibleFilter
? attributes.filter((item) => item.userVisible)
: attributes;
// 属性绑定数据字典
meta.attributes = visibleAttributes.map((item) => {
const attr = { ...item };
if (attr.changeValue === 'dict') {
if (!DICT.get(attr.code)) {
console.error(`找不到 ${attr.code} 对应的数据字典`);
} else {
attr.dict = DICT.get(attr.code).items;
}
}
return attr;
});
return meta;
};
/**
*
* 获取某个CI模型数据
* @param {object} code 模型code
* @param {boolean} userVisibleFilter 是否按照模型的userVisible过滤,发现页面不过滤
* @param {boolean} dict 是否需要绑定数据字典
* @returns {object} 格式化之后的模型对象
*/
export const getCi = async (code, userVisibleFilter = true, dict = true) => {
const { meta, visibleAttributes } = await formatMeta(code, userVisibleFilter);
if (!dict) {
meta.attributes = visibleAttributes;
return meta;
}
// 属性绑定数据字典
return bindDict(meta, visibleAttributes);
};
/**
*
* 获取某个CI模型数据 v3.0
* @param {object} code 模型code
* @param {boolean} userVisibleFilter 是否按照模型的userVisible过滤,发现页面不过滤
* @param {boolean} dict 是否需要绑定数据字典
* @returns {object} 格式化之后的模型对象
*/
export const getCi = async (code, userVisibleFilter = true, dict = true) => {
const meta = await formatVisibleAttributes(code, userVisibleFilter);
if (!dict) {
// 属性绑定数据字典
return bindDict(meta);
}
return meta;
};
/**
*
* 获取某个CI模型数据 v4.0
* @param {object} code 模型code
* @param {boolean} userVisibleFilter 是否按照模型的userVisible过滤,发现页面不过滤
* @param {boolean} dict 是否需要绑定数据字典
*/
export const getCi = async (code, userVisibleFilter = true, dict = true) => {
const meta = await formatVisibleAttributes(code, userVisibleFilter);
// 属性绑定数据字典
if (dict) {
try {
// 用户自建属性(数据字典)
const userDict = await getUserDicts(code);
return bindDict(meta, userDict);
} catch (error) {
rlog.error(error);
return meta;
}
}
return meta;
};
/**
* 过滤可见属性
* @param {string} code
* @param {boolean} userVisibleFilter
* @returns {object} 只包含可见属性的模型对象
*/
async function formatVisibleAttributes(code, userVisibleFilter) {
const meta = await request.post('/api/v1/model/getCi', { code });
if (meta) {
let { attributes } = meta;
if (!attributes) {
return meta;
}
// 适配后端,使属性正序
attributes = attributes.reverse();
// 获取过滤userVisible=true的属性(用户可见)
if (userVisibleFilter) {
meta.attributes = attributes.filter(
(item) => item.userVisible === 'true'
);
}
return meta;
}
return meta;
}
总共经历了至少4次的改动,逻辑变得越来越复杂,因为需要适配多种场景,本来我一开始抽出来,理由很简单,因为该api是一个获取底层数据的api,大多数前端的功能都需要调用该api,且都是需要有数据字典的,因为要正确的展示数据,所以我抽了一个方法。
这个时候还是很美好的,不过后续就像 The Wrong Abstraction里写的一样,各个使用方或找我或自己对该方法进行了扩展,这方法那是叫惨不忍睹啊,就这还是我重构之后的样子,没重构之前更丑。
那有人就问了,为什么就扩展了呢?
- 个人风格问题,该方法之前满足我得需求现在不满足了,所以我要改它,这样最简单,我可不管其它模块需不需要这个逻辑。
- 我也知道可能在上面加不太合适,因为加的扩展逻辑不是所有模块都需要的,但是也不是我一个人需要的,比如A、B、C、D...,A、B都需要,那为了不重复写代码,在原有方法上扩展我觉得也还行。
- ...
后来当我发现的时候,我就在群里发出了一个共识。
- 这类公共的api原则上不加*个性化的扩展*但是可加通用性(不影响整体数据结构且没有业务逻辑,比如:对原始数据进行数据筛选(eg:可见、不可见))的扩展,且加的时候需要与该api的最初作者对齐。
- 如果要扩展个性化,请自行copy一份代码再修改。
我的理由是如果再这么搞那我就不维护了爱咋咋滴....................当然前面是意淫的咱们是一个team,和为贵。
真正的理由是维护成本会越来越高且与当初抽象的意义渐行渐远。
可能我给的例子不够有足够力量的说服力,但是我还是觉得,抽象不一定就一定时好的必须的,有些时候我们得反过来想想,任何事情都有两面性。虽然咱没有能力提出牛逼得理论和观点,但是我们可以基于大佬们提出得理论和观点,做些反思、验证...。
有句话不是说吗:站在巨人的肩膀上。这句话我理解不是说巨人的肩膀才稳,而是说能看得更远。
Duplication is far cheaper than the wrong abstraction
本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。