title: AHA author: Gamehu tags: - AHA categories: - 编程 date: 2020-12-01 17:52:00 --- {% asset_img a.jpg [Collin Armstrong](https://unsplash.com/@brazofuerte) %} ### 背景 干研发的有个高频词语:**抽象**,这个词语可应用于各种场景,我今天聊的是代码抽象,在此篇就比较low逼的理解成代码复用吧,不然感觉有点虚。 为啥记录这个呢还是源于近段时间遇到的一些矛盾,重复代码该不该都抽出来,在这之前我会毫不犹豫的说应该,包括现在团队里也几乎是这样的声音,但是是不是就一定对呢?现在我觉得这个观点是不对的,因为我发现有些代码抽出来之后反倒变得越来越不可掌控。 所以我在思考**克制抽象**是不是也应该提出来。为了验证这个思考,遂搜了搜,别说还真有那么些大佬早就提出了这个观点。 #### AHA `AHA` (读作"[Aha](https://kentcdodds.com/blog/aha-programming)!" ):**Avoid Hasty Abstractions**(避免草率的抽象) 读了几篇文章特别感动,尤其是Sandi Metz的 [The Wrong Abstraction](https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction),特别有共鸣。 **核心观点就是** > 宁愿复制而不是错误的抽象 具体的支撑**克制抽象**内容,这几篇文章说的很清楚了,我就不再来一遍了。 ##### 我就给个现实的例子 ```javascript /** * 获取某个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; }; ``` ```javascript /** * 过滤可见属性 * @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](https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction)里写的一样,各个使用方或找我或自己对该方法进行了扩展,这方法那是叫惨不忍睹啊,就这还是我重构之后的样子,没重构之前更丑。 > > 那有人就问了,为什么就扩展了呢? > > 1. 个人风格问题,该方法之前满足我得需求现在不满足了,所以我要改它,这样最简单,我可不管其它模块需不需要这个逻辑。 > 2. 我也知道可能在上面加不太合适,因为加的扩展逻辑不是所有模块都需要的,但是也不是我一个人需要的,比如A、B、C、D...,A、B都需要,那为了不重复写代码,在原有方法上扩展我觉得也还行。 > 3. ... > > 后来当我发现的时候,我就在群里发出了一个共识。 > > 1. 这类公共的api原则上不加*个性化的扩展*但是可加通用性(不影响整体数据结构且没有业务逻辑,比如:对原始数据进行数据筛选(eg:可见、不可见))的扩展,且加的时候需要与该api的最初作者对齐。 > 2. 如果要扩展个性化,请自行copy一份代码再修改。 > > 我的理由是如果再这么搞那我就不维护了爱咋咋滴....................当然前面是意淫的咱们是一个team,和为贵。 > > **真正的理由是维护成本会越来越高且与当初抽象的意义渐行渐远。** 可能我给的例子不够有足够力量的说服力,但是我还是觉得,抽象不一定就一定时好的必须的,有些时候我们得反过来想想,任何事情都有两面性。虽然咱没有能力提出牛逼得理论和观点,但是我们可以基于大佬们提出得理论和观点,做些反思、验证...。 有句话不是说吗:**站在巨人的肩膀上。这句话我理解不是说巨人的肩膀才稳,而是说能看得更远。** ##### You Know Duplication is far cheaper than the wrong abstraction `本文引用的内容,如有侵权请联系我删除,给您带来的不便我很抱歉。`