背景
干研发的有个高频词语:抽象,这个词语可应用于各种场景,我今天聊的是代码抽象,在此篇就比较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
|
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; };
export const getCi = async (code, userVisibleFilter = true) => { const meta = await request.post('/api/v1/model/ci/getCi', { code }); 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; };
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); };
export const getCi = async (code, userVisibleFilter = true, dict = true) => { const meta = await formatVisibleAttributes(code, userVisibleFilter); if (!dict) { return bindDict(meta); } return meta; };
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
|
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(); 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,和为贵。
真正的理由是维护成本会越来越高且与当初抽象的意义渐行渐远。
可能我给的例子不够有足够力量的说服力,但是我还是觉得,抽象不一定就一定时好的必须的,有些时候我们得反过来想想,任何事情都有两面性。虽然咱没有能力提出牛逼得理论和观点,但是我们可以基于大佬们提出得理论和观点,做些反思、验证…。
有句话不是说吗:站在巨人的肩膀上。这句话我理解不是说巨人的肩膀才稳,而是说能看得更远。
You Know
Duplication is far cheaper than the wrong abstraction
本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。