前言
互联网开发多以团队协同的方式为主,在实际的开发过程中,我们经常会面对在同事的代码的基础上重新开发的需求。然而由于人员的迭代、需求的变更、文档的缺失等原因,我们贸然修改同事的代码往往需要承担一些额外的开发风险,比如修改一个BUG,导致更多的BUG,层层顺延,最后从删库到跑路......
这是一个大家都不愿提及又不得不面对的问题,修改同事代码很容易引发不可控的开发风险,从而降低开发效率,导致大家无法按时完成额定的开发任务,之后的一系列影响——开发延期、测试时间压缩、线上报BUG、交付质量不合格,则又是这个问题之外的恶性影响了。
对于程序员而言,我们的职业定位是对于现实问题给出技术解决方案,从而给企业的工作效率带来一定程度的提升,面对这样的行业痛点我们必须要找到一种合适的方式去解决这样的问题,而不是回避、退缩,问题是绕不过去的,而能解决别人解决不了的问题就是我们与众不同的价值体现。
需求分析
我总结了一下修改同事的代码的需求,主要有以下三类:
1、非侵入式修改同事的代码。这类需求主要是需要对同事代码进行逻辑的增强,不需要改动原有逻辑。
2、侵入式修改同事的代码。这类需求主要是需要对同事的原有逻辑进行整体或部分的改动,这是需求本身的改动,这种改动一定程度上可以归结为设计问题,当然如果程序开发需要对设计妥协,我们也往往不得不侵入式地改动同事的代码。
3、 并行修改同事的代码。两种版本都是项目的需要,可能同时提供,也可能通过某种机制进行回退或切换。
修改同事的代码无非就是这三类需求,在同事代码的基础上继续开发,需要注意的三是,我们要尽量少地修改原有代码逻辑,把修改原有逻辑转为新增扩展逻辑。
简单案例
假设有这样的一个需求:Boss高启强对手下唐小虎说,“告诉老默,我想吃鱼了...”
唐小虎要如何设计需求呢?
老板手下有N个杀手,不可能每一次都指定老默,可能今天叫老默,可能明天叫老冯。所以杀手的名字要作为参数传递过去,而不能写死。
我唐小虎也是上过商学院的,我知道设计遵循依赖倒置原则,高层代码不能依赖底层代码,而是应该依赖其抽象,于是唐小虎是这样的设计代码的:
抽象接口:
public interface EatService {// 吃的方法void eat(String killer);
}
具体实现:
public class EatServiceImpl implements EatService {@Overridepublic void eat(String killer) {return "告诉" + killer + ",我想吃鱼了...";}
}
相信大家也都是这样做的,Boss只要调用EatService的eat方法,传入杀手名字,就可以指定杀手去“吃鱼”。
然而需求是动态的,当大嫂陈书婷知道这件事后,大嫂陈书婷提出了新的要求,“吃完饭要刷碗...”
作为杀手,你要把事后的问题处理干净。
那怎么在不修改原代码的基础上改设计呢?这就用到了代理模式。
public class EatProxyServiceImpl implements EatService {private EatService eatService = new EatServiceImpl();@Overridepublic void eat(String killer) {eatService.eat(killer);postEat();}private void postEat() {System.out.println("陈书婷说,吃完饭要洗碗...");}
}
我们定义了一个EatProxyServiceImpl,和EatServiceImpl一样,我们需要实现EatService接口,并重写其中的eat()方法,并且把原对象EatServiceImpl注入到该类中,在原对象的前后进行增强逻辑。
如果Boss高启强修改需求了,说我们不吃鱼了,我们去吃海鲜吧,这又要如何在不修改原代码的基础上改动呢?
public class EatProxyServiceImpl implements EatService {private EatService eatService = new EatServiceImpl();@Overridepublic void eat(String killer) {System.out.println("老默,我们去吃海鲜吧...");postEat();}private void postEat() {System.out.println("陈书婷说,吃完饭要洗碗...");}
}
其实我们就不需要写eatService.eat(killer)了,我们只需要在代理实现类中加入新的逻辑即可。
如果Boss说,老客户喜欢吃鱼,新客户喜欢吃海鲜,我们要允许二者同时调用怎么办?
public class EatProxyServiceImpl implements EatService {private EatService eatService = new EatServiceImpl();@Overridepublic void eat(String killer) {eatService.eat(killer);postEat();}private void postEat() {System.out.println("陈书婷说,吃完饭要洗碗...");}public EatService getEatService(){return eatService;}
}
我们只需要在代理实现中,自定义一个方法,获取旧有实现即可,这样既可以调用老代码,也可以调用新代码。
我们可以测试一下:
public class GaoQQTest {public static void main(String[] args) {System.out.println("高启强开始调用......");EatService eatService = new EatServiceImpl();eatService.eat("老默");System.out.println("陈书婷进行增强......");EatProxyServiceImpl eatProxyService = new EatProxyServiceImpl();eatProxyService.eat("老默");// 陈泰比较念旧,喜欢调用老的方法System.out.println("泰叔开始调用......");eatProxyService.getInstance().eat("老默");}
}
测试结果如下:
总结
代理模式可以很好的解决修改同事代码的问题,我们通过一个简单代理,即可实现侵入式、非侵入式修改以及双版本代码切换,甚至于我们只需要在代理的eat中,根据配置文件的value指定二者之一的任何一个实现,还可以实现线上代码动态切换。
这个案例是很简单的,但是其背后的设计思路是指的思考的,需求是活的,思路不能是一成不变的,总有一种方式可以优雅地解决当下的需求。