初步认识
在开发中我们可能经常会遇到诸如:配置化、DSL、规则平台、Schema化协议这样的字眼。
你是否隐隐约约感觉他们有一些相似的地方 ?
- 他们是比代码更加轻量化的描述方式
- 他们通过 “配置”的方式描述一个业务方案
- 当我们谈及“配置化”,说明这部分内容放在代码中不够灵活/泛用
举例
小明是一个程序员,他发现代码中很多地方需要用到一个“文章类型”元数据 ,而当前的类型分布在各个死代码处,他想,能不能把它做成配置统一管理。于是他写出了下面这样的配置。
[
{
type: '',
}
]
这是常见的配置化方案,这样子做好像没有什么问题,而且非常方便,只需要维护一个配置,各处业务逻辑就能同步修改。
让我们升级一下需求,进一步研究配置化的哲学
- 每一个类型具有不同的展示方式
- 在 51 期间,生活文章类型的进行 20 % 的推流 , AGC 类文章进行 10 % 的推流
- 五一专题文章只在五一期间存在,过期后回到普通文章类型
小明加班写出了以下两份配置
[
{
type: '生活文章',
extra:{
condition: "days.between("2025-5-1","2025-5-7")",
action: "push",
threshold: "0.2"
}
},
{
type: 'AGC 文章',
extra:{
condition: "days.between("2025-5-1","2025-5-7")",
action: "push",
threshold: "0.1"
}
},
{
type: '五一专题文章',
extra:{
condition: "!days.between("2025-5-1","2025-5-7")",
action: "hidden",
}
},
{
type: '普通文章',
extra:{
condition: "!days.between("2025-5-1","2025-5-7") and type === '五一生活文章'",
action: "show",
}
},
]
[ { type: '生活文章', extra:{ hidden:false, push:{ threshold: "20%", period: ["2025-5-1","2025-5-7"]
}
}
},
{
type: 'AGC类文章',
extra:{
hidden:false,
push:{
threshold: "10%",
period: ["2025-5-1","2025-5-7"]
}
}
},
{
type: '五一专题文章',
extra:{
hidden:{
period: ["2025-5-1","2025-5-7"],
fallback: "普通文章"
},
}
},
]
你觉得哪一份好 ?
公布答案,第二份更好。 为什么呢? 有的同学可能认为第一份更佳,因为他更工整
其实不然,第一份配置其实耦合了一些“逻辑”,而不是“纯数据”。不信?让我们把上面的逻辑部分用代码写出来看看:
switch(type){
case : "生活文章"
if(days.between("2025-5-1","2025-5-7")){
push(type,0.2)
}
return
case : "AGC文章"
if(days.between("2025-5-1","2025-5-7")){
push(type,0.1)
}
return
case : "五一专题文章"
if(!days.between("2025-5-1","2025-5-7")){
type = "普通文章"
}
return
}
这样看,你是不是觉得代码会比第一份更好。如果还不明显,你可以尝试给小明多加几个需求,由于配置一不是纯元数据的,包含了操作逻辑,所以他会随着业务增长而变得臃肿。同时由于他只是一个配置,没有代码与生俱来的优势:图灵完备性 + 可维护性 + 可拓展性。所以这时候我们不如直接用代码写。
但是你或许注意到,代码中有很多写死的数据,如果要更改这部分,我们还得改代码,效率很低。所以这一部分才是我们需要提出到配置的。总的来说,我们将一个业务逻辑分工 :
- 配置,负责保存参数。
- 代码,负责使用参数执行业务逻辑。
换句话说, 配置用来说 what,而代码表达用 “what”去干什么,也就是how。至于 why,通常只有产品同学知道😃。
对比思考
让我们来对比一下配置和代码
我们已经得出结论:在一个业务中,代码负责逻辑行为描述,配置负责参数变量的描述,相互合作构成一个合理的架构。
优点
- 没有复杂的代码逻辑,非技术人员也能很快看懂并操作
- 聚合分散的数据,一处配置多处同步,提高业务迭代的效率
缺点
配置的缺点,其实相对的就是代码的优势
- 可维护 : 由于没有工具链的支持,在配置中写变量名或者相互依赖的内容,是不可维护的
- 可拓展性 : 每一个配置应当是原子化的,因为他们没办法通过其他配置项继承/组合,但是代码天然可以
- 图灵完备性 :能够描述复杂的逻辑 : 循环,递归等
最佳实践
我们何时使用配置化?
根据配置和代码的区别,我们可以总结出一个判断标准
- 配置化有利于快速更新 --- 这部分内容需要频繁修改 ? --- 是则使用配置
- 配置化有利于聚合分散元数据 --- 这部分数据在多处使用 ? --- 是则配置化
- 配置化没有复杂逻辑,简单易懂 --- 配置修改是否需要技术背景 ? --- 否则配置化
- 配置化只擅长于描述元数据 --- 配置的内容是否不包含逻辑 ? --- 是则配置化
我们如何设计配置?
设计配置化的目的就是扬长避短,发挥聚合,便捷的优势。避免使用配置描述逻辑
- 配置内容需要原子化,配置之间不应该有耦合的关系
- 配置的内容应该尽量语义化,降低阅读门槛
- 配置的内容需要保证兼容性,越简单,就越容易被各种各样的业务情况使用
在下面的代码中,黄色部分就是建议提出到配置化,你可以看到,他们就是完全的数据,不包含任何逻辑。
switch(type){
case : "生活文章"
if(days.between("2025-5-1","2025-5-7")){
push(type,0.2)
}
return
case : "AGC文章"
if(days.between("2025-5-1","2025-5-7")){
push(type,0.1)
}
return
case : "五一专题文章"
if(!days.between("2025-5-1","2025-5-7")){
type = "普通文章"
}
return
}
经验启发和总结自诸多互联网大牛和前辈,小弟才疏学浅,难免疏漏或者误解,欢迎不吝赐教。