我在大厂做 CR——再谈怎么高雅修正代码
书接上回为什么需求依靠注入再做下扩展
上文谈到:“根据笼统接口编程的确是最佳实践:把易于变化的功用点经过界说笼统接口的办法露出出来,不同的完成做到阻隔和扩展,这表现了开闭准则”
public class Foo {
private Bar bar ;
@Inject
public Foo(Bar bar) {
this.bar = bar;
}
public String doSomething(int key) {
//Bar#getResult 体会了代码的复杂性,经过注入不同的 Bar 完成目标,做到功用点的阻隔和扩展
return bar.getResult(key);
}
}
但在实在项目里,往往是多人协作一同开发,一些前史原因导致某些代码片段的完成往往“千奇百怪”,既不能很好的单侧掩盖,一起也充满着违反了开闭准则的“代码坏滋味”;
而此刻的你,作为“被选中的人”,需求对其功用迭代;
或许经过你的评价后,能够去雷厉风行的架构演进,这是点赞的;
但有时也要大局 ROI 去评价雷厉风行重构收益是否足够大,有时分咱们只能退让(trade-off
)。即:如安在严重的交给周期内做到比较好的重构,不让代码持续堕落;
所以这次持续介绍两种修正代码的艺术:办法新增和办法掩盖
战略 1:办法新增
经过新增办法来阻隔旧逻辑,即:在旧办法里横切“缝隙”,注入新的事务逻辑被调用;
拿之前的 Case 举例,一个前史老办法,需求对回来的数据调集过滤掉空目标:
public class Foo {
private Bar bar;
public Foo() {
bar = new Bar();
}
public List<Data> doSomething(int key) {
//依靠三方服务,RPC 调用成果集
List<Data> result = bar.getResult(key);
//过滤掉空目标
return result.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
}
此处逻辑很简略,运用了Java Lambda
表达式做了过滤,但这样的写法无疑落井下石:的确原先办法现已很 Low 了,也无法单侧。本次只是在最终加了一段简略的逻辑。现已轻车熟路了,或许不少人都会这样搞;
但作为好的程序员,眼前现状的确咱们只能退让,但后续的每一行代码,需求做到保质保量,努力做到不影响原有事务逻辑下做到可测验;
“办法新增”:经过新增办法 getDataIfNotNull
来阻隔旧逻辑:
public List<Data> doSomething(int key) {
//依靠三方服务,RPC 调用成果集
List<Data> result = bar.getResult(key);
return getDataIfNotNull(result);
}
如下 getDataIfNotNull
作为新增办法,很简单对其进行独立测验,一起原有的办法 doSomething
也没有持续堕落
public List<Data> getDataIfNotNull(List<Data> result) {
return result.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
能够看到长处很明显:新老代码明晰阻隔;当然为了愈加责任清楚,运用新增类阻隔
会更好;
战略 2:办法掩盖
将待修正的办法重命名,并创立一个新办法和原办法名和签名共同,一起在新办法中调用重命名后的原办法;
假设有新需求:针对 doSomething
办法做一个音讯告诉操作,那么“办法掩盖”即:将原办法 doSomething
重命名为 doSomethingAndFilterData
,再创立一个与原办法同名的新办法 doSomething
,最终在新办法中调用更名后的原办法:
//将原办法 doSomething 重命名为 doSomethingAndFilterData
public List<Data> doSomethingAndFilterData(int key) {
//依靠三方服务,RPC 调用成果集
List<Data> result = bar.getResult(key);
return getDataIfNotNull(result);
}
//创立一个与原办法同名的新办法 doSomething
public List<Data> doSomething(int key) {
//调用旧办法
List<Data> data = this.doSomethingAndFilterData(key);
//调用新办法
doNotifyMsg(data);
return data;
}
//新的扩展办法契合阻隔扩展,不影响旧办法,也支撑单侧掩盖
public void doNotifyMsg(List<Data> data){
//
}
办法掩盖的另一种写法:通常是再界说一个新的办法,然后在新的办法顺次调用新老事务逻辑;
一般在架构演进的时分,用于切流新老逻辑;例如:根据客户端版别,大于 3.10.x
的客户端切流运用新的逻辑——咱们创立一个新的办法调用新旧两个办法。
//老的前史代码,不做改造
public List<Data> doSomething(int key) {
//依靠三方服务,RPC 调用成果集
List<Data> result = bar.getResult(key);
List<Data> data = getDataIfNotNull(result);
return data;
}
//新创立一个办法,聚合调用新老逻辑
public List<Data> doSomethingWithNotifyMsg(int key) {
List<Data> data = this.doSomething(key);
//调用新办法
doNotifyMsg(data);
return data;
}
//新的扩展办法契合阻隔扩展,不影响旧办法,也支撑单侧掩盖
public void doNotifyMsg(List<Data> data){
//
}
这样的优点是明显易见的,不针对旧办法做修正,一起在更高维度的“上层”切流:确保新功用正常迭代演进,老功用保持不变
boolean enableFunc=getClientVersion()>DEFAULT_CLIENT_VERSION;
if (enableFunc){
return doSomethingWithNotifyMsg();
} else {
return doSomething();
}
能够看到“办法掩盖”不管用何总办法完成,它不会在当时旧办法里添加逻辑,而是经过运用新办法作为进口,这样防止新老逻辑耦合在一同;
“办法掩盖”能够再进阶一步,运用独立的类来阻隔,也便是装修者形式。通常情况下原有的类现已非常复杂了,现已不想在它上做功用迭代了,考虑运用装修者来解耦:
class DecoratedFoo extends Foo{
private Foo foo;
public DecoratedFoo(Foo foo){
}
@Override
public List<Data> doSomething(int key) {
List<Data> data = super.doSomething(key);
notifyMsg();
return data;
}
private void notifyMsg(){
}
}