AHA

背景

干研发的有个高频词语:抽象,这个词语可应用于各种场景,我今天聊的是代码抽象,在此篇就比较low逼的理解成代码复用吧,不然感觉有点虚。

为啥记录这个呢还是源于近段时间遇到的一些矛盾,重复代码该不该都抽出来,在这之前我会毫不犹豫的说应该,包括现在团队里也几乎是这样的声音,但是是不是就一定对呢?现在我觉得这个观点是不对的,因为我发现有些代码抽出来之后反倒变得越来越不可掌控。

所以我在思考克制抽象是不是也应该提出来。为了验证这个思考,遂搜了搜,别说还真有那么些大佬早就提出了这个观点。

AHA

AHA (读作”Aha!” ):Avoid Hasty Abstractions(避免草率的抽象)

读了几篇文章特别感动,尤其是Sandi Metz的 The Wrong Abstraction,特别有共鸣。

核心观点就是

宁愿复制而不是错误的抽象

具体的支撑克制抽象内容,这几篇文章说的很清楚了,我就不再来一遍了。

我就给个现实的例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* 获取某个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;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 过滤可见属性
* @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里写的一样,各个使用方或找我或自己对该方法进行了扩展,这方法那是叫惨不忍睹啊,就这还是我重构之后的样子,没重构之前更丑。

那有人就问了,为什么就扩展了呢?

  1. 个人风格问题,该方法之前满足我得需求现在不满足了,所以我要改它,这样最简单,我可不管其它模块需不需要这个逻辑。
  2. 我也知道可能在上面加不太合适,因为加的扩展逻辑不是所有模块都需要的,但是也不是我一个人需要的,比如A、B、C、D…,A、B都需要,那为了不重复写代码,在原有方法上扩展我觉得也还行。

后来当我发现的时候,我就在群里发出了一个共识。

  1. 这类公共的api原则上不加个性化的扩展但是可加通用性(不影响整体数据结构且没有业务逻辑,比如:对原始数据进行数据筛选(eg:可见、不可见))的扩展,且加的时候需要与该api的最初作者对齐。
  2. 如果要扩展个性化,请自行copy一份代码再修改。

我的理由是如果再这么搞那我就不维护了爱咋咋滴………………..当然前面是意淫的咱们是一个team,和为贵。

真正的理由是维护成本会越来越高且与当初抽象的意义渐行渐远。

可能我给的例子不够有足够力量的说服力,但是我还是觉得,抽象不一定就一定时好的必须的,有些时候我们得反过来想想,任何事情都有两面性。虽然咱没有能力提出牛逼得理论和观点,但是我们可以基于大佬们提出得理论和观点,做些反思、验证…。

有句话不是说吗:站在巨人的肩膀上。这句话我理解不是说巨人的肩膀才稳,而是说能看得更远。

You Know

Duplication is far cheaper than the wrong abstraction

本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。