Open zhangsanshi opened 5 years ago
随着业务发展,同一份代码被部署到越来越多的环境去了,同时每个环境都有一些个性化设置,导致代码越来越无法维护。
产品说,在 A 环境下希望对按钮 A 隐藏,简单 if (env !== 'A') { // xxx }。随着业务的发展,类似的需求都过来了。在 A、C 环境下隐藏按钮 A,在 (B + a)(a 表示另一个影响因素) 环境下隐藏 C 选项。此时代码里充斥着各种 if。突然来了,一个新的环境 D,要求在 D 环境隐藏 A 按钮、C 选项,整个项目有 10+ 模块,每个模块都需要对代码进行调整,那么工作量是一方面,测试又是另一方面。
if (env !== 'A') { // xxx }
(B + a)
if
代码里充斥着是下面的代码
// a.js if (env === 'A' || env === 'C') { // hide A button } // b.js if (env === 'A' || env === 'C') { // hide A button } // 可能有人会采取优化点的写法 if (['A', 'C'].includes(env)) { // hide A button }
可以想象,代码膨胀下去的后果(在多因素的影响下,代码会变得不可维护)
if (['A', 'C', 'E', ...].includes(env)) { // hide A button }
此时有人对这些代码有了一些想法,会将代码优化为下面的样式,在不同的地方进行引用。但是此时没有形成全局性的,可能命名写法以及判断逻辑都不一致。
// config.js const hideA = ['A', 'C'].includes(env); export default { hideA, }; // a.js import config from './config.js'; const hideA = config.hideA; if (hideA) { // hide A button } // b.js import config from './config.js'; const hideA = config.hideA; if (hideA) { // hide A button }
有人开始将此类信息往全局进行抽取,保证此类信息由全局维护,同时由于信息在全局,也不怕重复实现的问题。这个版本看起来相对可以用了。
// global.config.js const hideA = ['A', 'C'].includes(env.AZ); const hideOptions = ['a'].includes(env.az) && ['B'].includes(env.AZ); // 多因素影响 export default { hideA, }; // a.js import config from 'global.config.js'; const hideA = config.hideA; if (hideA) { // hide A button }
注意 1.1 版本那个多因素影响,实际项目中,有 5 个因素的影响,以后还有扩展的可能性。那么就意味着 y = f(a, b, c, d, e ...)。不同的因素会影响取值,但是实际中,影响取值的不会太多(5 个中有2,3个)。
1.1
5
y = f(a, b, c, d, e ...)
现在有一个问题来了,对于一个给定的环境,它的影响因子是限定的,那能很快的说出在这个环境内,哪些东西是隐藏的吗?又或者,希望在项目里的 i、j 模块内,即使在 A 环境下,也展示按钮 A?基于上面的问题,此时 1.1 版本就稍显不足了。
i、j
新的设计方案:
首先规定出每个环境(可以是物理,也可以是逻辑,一般来说逻辑会比物理的宽泛一些)自身的五元组成,这部分设计是独立的,可以随时修改,只要环境名以及环境覆盖逻辑不变即可。
// env.name.js const nameList = { base: '*/*/*/*/*', '环境A': 'A/a/*/*/*', '环境B': 'B/a/*/*/*', '环境C': 'B/b/*/*/*', }; // 这里既可以映射物理环境还可以映射逻辑上的环境 export default function (path) { return nameList.filter(() => { // path }).sort(() => { // 星少的放在前面,星越多,代表信息越全,在后续合并配置的时候,越详细的信息对应的配置会覆盖宽泛的信息对应的配置。 }); }; // path 即任意环境的五元组成
接着设计一个全局的模块,这时候如果回想需求,那么此时的表达形式和需求是一致的,在环境 A、C 隐藏按钮A
// global/config/base.js export default { hideA: false, hideOptions: false, ...others, }; // global/config/环境A.js // 真正环境A的全局配置:mergeDeep({}, base, 环境A) export default { hideA: true, }; // global/config/环境C.js // 真正环境C的全局配置:mergeDeep({}, base, 环境C) export default { hideA: true, };
还有另一个需求,模块的配置有一定的可能覆盖全局的,即希望在项目里的 i、j 模块内,即使在 A 环境下,也展示按钮 A。
// i/config/base.js export default { // 如果默认的状态下和全局一致,可以不存在此文件 }; // i/config/环境A.js // 真正环境A的 i 模块配置:mergeDeep({}, base, 环境A全局配置, i模块的base, 环境A的i模块配置) export default { hideA: false, };
在模块内使用的时候,直接引用模块配置即可
import config from './config.js'; const hideA = config.hideA; if (hideA) { // hide A button }
回头看,版本 2.0 和 版本 1.1 的差距,如果按照 2.0 的想法,不是以变量为主,而是以环境为主。那么改造 1.1 的写法,会发现也是可行的,但是多项目部署代码是隔离的,所以代码会有一定程度的增加,需要通过其他手段调整。
如果产品有新的需求或者又要部署新的环境,只需要找到相应的文件,修改掉配置,或者新增一份配置即可,对于老需求来说,不需要改动代码。
维护性变的更好一点,同时在某种程度上会改变编程方式,因为需要考虑到配置产生的影响,这种影响是跨文件的,那么写出来的代码,一定是可以达到配置变而不影响逻辑的改动,从而在后续的开发中提升一定的效率。对于新需求来说,只要有配置的需要,那么改动一次代码后,以后对于此类需求都可以轻松应对。
前端模拟线上的环境会变的更加方便,利于开发测试。
配置信息集中
配置项需要有全局性的说明和模块级别的说明,否则容易重复说明
配置的逻辑环境声明过多,也会导致混乱,主要有可能会产生互斥的问题
配置项过于细致或者过于粗犷
基于配置项进行二次编程,而不是扩展配置项,可能因为扩展模块级别的配置项,会比较麻烦(后续收集需求,是否可以通过命令行的形式解决)
后续的实施步骤:
产品有一份粗略的配置信息是需要抽取出来的,同时前端结合业务的需求,沉淀了一些配置项可以进行抽取的,后续在业务发展或者产品规划中,也可以明确一下,哪些东西是需要进行配置的(配置不配置对工作量的影响不是过于的大),可以提早做好准备,应对未来的需求。在整个稳定后,就可以将此类信息迁移到后端服务中,由接口进行返回(部分代码需要拿到多个环境的配置,这个会影响后续的实施),后续就可以动态调整配置了。
2.0 版本在陆续改造中,由于之前没有此类概念,所以目前开展的工作还不多,只能简单谈一下遇到的问题。
2.0
产品提了一个需求,在 X 环境下,显示 A、F 模块的创建按钮,显示 A、B、C、D、F 模块的付费信息。
按照需求来说,只需要定义一个变量去控制 `A、F 模块的创建按钮` 和 `A、B、C、D、F 模块的付费信息`。 但是 `A、F 模块的创建按钮` 和 `A、B、C、D、F 模块的付费信息` 实际上是多个功能,只用一个变量控制, 根本无法表达两个含义,假设又有需求需要对 `A、F 模块的创建按钮` 做出改变,那么那个变量就不够用了。 所以这里可以约定一下,对于配置,应该按类划分。 `A 模块的创建按钮` 是一个变量, `F 模块的创建按钮` 是一个变量, `A、B、C、D、F 模块的付费信息` 是一个变量。
配置管理
随着业务发展,同一份代码被部署到越来越多的环境去了,同时每个环境都有一些个性化设置,导致代码越来越无法维护。
1. 引言
产品说,在 A 环境下希望对按钮 A 隐藏,简单
if (env !== 'A') { // xxx }
。随着业务的发展,类似的需求都过来了。在 A、C 环境下隐藏按钮 A,在(B + a)
(a 表示另一个影响因素) 环境下隐藏 C 选项。此时代码里充斥着各种if
。突然来了,一个新的环境 D,要求在 D 环境隐藏 A 按钮、C 选项,整个项目有 10+ 模块,每个模块都需要对代码进行调整,那么工作量是一方面,测试又是另一方面。2. 解决问题
2.1. 版本0.1
代码里充斥着是下面的代码
可以想象,代码膨胀下去的后果(在多因素的影响下,代码会变得不可维护)
2.2. 版本1.0
此时有人对这些代码有了一些想法,会将代码优化为下面的样式,在不同的地方进行引用。但是此时没有形成全局性的,可能命名写法以及判断逻辑都不一致。
2.3. 版本1.1
有人开始将此类信息往全局进行抽取,保证此类信息由全局维护,同时由于信息在全局,也不怕重复实现的问题。这个版本看起来相对可以用了。
2.4. 版本2.0
注意
1.1
版本那个多因素影响,实际项目中,有5
个因素的影响,以后还有扩展的可能性。那么就意味着y = f(a, b, c, d, e ...)
。不同的因素会影响取值,但是实际中,影响取值的不会太多(5 个中有2,3个)。现在有一个问题来了,对于一个给定的环境,它的影响因子是限定的,那能很快的说出在这个环境内,哪些东西是隐藏的吗?又或者,希望在项目里的
i、j
模块内,即使在 A 环境下,也展示按钮 A?基于上面的问题,此时1.1
版本就稍显不足了。新的设计方案:
首先规定出每个环境(可以是物理,也可以是逻辑,一般来说逻辑会比物理的宽泛一些)自身的五元组成,这部分设计是独立的,可以随时修改,只要环境名以及环境覆盖逻辑不变即可。
接着设计一个全局的模块,这时候如果回想需求,那么此时的表达形式和需求是一致的,在环境 A、C 隐藏按钮A
还有另一个需求,模块的配置有一定的可能覆盖全局的,即希望在项目里的
i、j
模块内,即使在 A 环境下,也展示按钮 A。在模块内使用的时候,直接引用模块配置即可
2.5. 引申
回头看,版本 2.0 和 版本 1.1 的差距,如果按照 2.0 的想法,不是以变量为主,而是以环境为主。那么改造 1.1 的写法,会发现也是可行的,但是多项目部署代码是隔离的,所以代码会有一定程度的增加,需要通过其他手段调整。
3. 优势
如果产品有新的需求或者又要部署新的环境,只需要找到相应的文件,修改掉配置,或者新增一份配置即可,对于老需求来说,不需要改动代码。
维护性变的更好一点,同时在某种程度上会改变编程方式,因为需要考虑到配置产生的影响,这种影响是跨文件的,那么写出来的代码,一定是可以达到配置变而不影响逻辑的改动,从而在后续的开发中提升一定的效率。对于新需求来说,只要有配置的需要,那么改动一次代码后,以后对于此类需求都可以轻松应对。
前端模拟线上的环境会变的更加方便,利于开发测试。
配置信息集中
4. 劣势
配置项需要有全局性的说明和模块级别的说明,否则容易重复说明
配置的逻辑环境声明过多,也会导致混乱,主要有可能会产生互斥的问题
配置项过于细致或者过于粗犷
基于配置项进行二次编程,而不是扩展配置项,可能因为扩展模块级别的配置项,会比较麻烦(后续收集需求,是否可以通过命令行的形式解决)
5. 后续
后续的实施步骤:
产品有一份粗略的配置信息是需要抽取出来的,同时前端结合业务的需求,沉淀了一些配置项可以进行抽取的,后续在业务发展或者产品规划中,也可以明确一下,哪些东西是需要进行配置的(配置不配置对工作量的影响不是过于的大),可以提早做好准备,应对未来的需求。在整个稳定后,就可以将此类信息迁移到后端服务中,由接口进行返回(部分代码需要拿到多个环境的配置,这个会影响后续的实施),后续就可以动态调整配置了。
2.0
版本在陆续改造中,由于之前没有此类概念,所以目前开展的工作还不多,只能简单谈一下遇到的问题。产品提了一个需求,在 X 环境下,显示 A、F 模块的创建按钮,显示 A、B、C、D、F 模块的付费信息。