代码改变世界

重构系统的套路-写有组织的代码

2018-06-28 10:30  春哥大魔王  阅读(1107)  评论(0编辑  收藏  举报

如果一个项目经历了快速发展,势必在业务发展背后留下了一个很无序,结构混乱的代码,无序而混乱的代码势必造成很大的bug修复及扩展成本。

说到搭建系统都在谈论高并发,大数据,而易于维护和可扩展性则被大部分人抛之脑后,增加最基础的面向对象思想和设计模式帮助我们组织好易于维护和阅读的代码。

不要好高骛远看一下高并发,高可用的东西,做好以下这些最基础的东西,你的系统在可读性和可维护可扩展方面将会提升一倍的能力,将大家的人效从每次bug产生梳理代码的过程中解放出来,建立标准建立原则才是架构师首先要做的。

  • 进行适当的封装
  • 开放封闭原则
  • 单一只能原则
  • 代码具备随时删掉而不影响上下文的能力

命名与注释

命名需要见名知义,注释则可以帮助我们去了解当时的业务逻辑,不然后期只能通过一行行的日志去定位问题了。

考虑到IDE会帮助我们创建变量名称,如果名称相似则存在误用可能造成很难定位的BUG,入参可以以Req结尾或者Command结尾,返回值以Resp结尾或者Result结尾。

抽离业务骨骼

Web系统的入口层是Controller,基于RPC的服务入口层往往是XxxServiceImpl,入口层应该像一本书的目录和前言部分,说明了这个方法的主要目的,同时梳理了核心的业务流程,流程不要太细或者太粗,刚刚好满足产品的需求骨骼为主,可以简单的理解为是产品PRD的信息抽象。

建立胶水层代码

大型系统或者业务系统具有一定的复杂程度,势必在某些问题场景包含一些必要的大逻辑处理,于是建立一个胶水层代码,胶水类可以用入口层的一个方法名称作为名称定义。
胶水层代码向外暴露的public接口则为入口层的核心骨骼逻辑,将内部复杂逻辑进行封装,达到部分方法随时可以删掉,注释掉,替换掉而不影响核心骨骼逻辑的效果,可以理解为TDD,关注入参和返回值就好。
如果通过RPC或者ResultAPI和其他系统具有一定的依赖,则放到这里。
其实这层有些上DDD里面的Domain,但是DDD用不好的话在分布式微服务场景下会出现很难把控的问题。

下层依赖代码

在入口和业务逻辑之下基本就是Service层代码和Dao层代码了,Dao主要是和存储系统打交道,主要目的是可以随时切换到其他的存储逻辑中去,而不影响上层业务和代码。
Service则是进行一定的数据结构组织,数据结构可能来自于底层Dao,可能来自于消息队列的订阅,可能来自于Redis缓存或者Hbase等,放在这一层可以有效分离依赖系统数据和本系统数据。

示例

输入图片说明

入参

public class WmPoiReq { 
    private long userId; 
    private long wmPoiId; 
    private int channel; 
}

返回结果

public class WmPoiResp { 
} 

实体

public class Activity { 
    int activityId; 
    Long wmPoiId; 
    int channel; 
    long valida; 
}

服务入口

public class CServiceImpl { 
 
    /** 
     * 发送积分
     * @param req 
     * @return 
     */ 
    public WmPoiResp wmPSendC(WmPoiReq req){ 
        // 入参不合法,及时失败 
        if(null == req || req.getUserId() < 1l || req.getPId() < 1l || req.getCh() < 0){ 
            throw new IllegalStateException("参数无效"); 
        } 
 
        PSendCHandler handler = new PSendCHandler(); 
        // 1. 获取可用列表 
        List<Activity> activities = handler.getActivityList(req.getPId(), req.getUserId(), req.getCh()); 
 
        // 2. 满足,进行发操作 
        boolean sendStatus = handler.sendC(req.getPId(), activities); 
 
        // 3. 调用接口服务化发
        // RPC调用服务发送 
 
        WmPoiResp resp = new WmPoiResp(); 
        return resp; 
    } 
}

创建胶水代码,实现流程细节

public class PSendCHandler { 
 
    /** 
     * 获取可用活动列表 
     * @param iId  
     * @param userId 用户id 
     * @param channel 
     * @return 
     */ 
    public List<Activity> getActivityList(long PiId, long userId, int channel){ 
        // 1. 根据PiId, channle获取活动列表 
        List<Activity> activities = ... // 假装从底层数据获取 
        if(activities.size() == 0) return new ArrayList<>(); 
 
        // 2. 判断获取是否已过期 
        boolean expire = activities.size() > 0 ? activities.get(0).getValida() > new Date().getTime() : true; 
        if(expire) return new ArrayList<>(); 
 
        // 3. 判断是否是新用户 
        boolean freshMan = ... // 假装从底层数据获取 
 
        if(freshMan){ // 新用户,验证是否有适用于新用户活动 
           Iterator<Activity> iterator = activities.iterator(); 
           while (iterator.hasNext()){ 
               // 检查每个activity是否适用于新用户 
           } 
           // 所有活动不适用于新用户 
            return new ArrayList<>(); 
        } 
 
        // 返回可用活动列表 
        return activities; 
    } 
 
    /** 
     * 发券 
     * @param PiId 门店id 
     * @param activities 活动集合 
     * @return 
     */ 
    public boolean sendC(long PiId, List<Activity> activities){ 
        // 通过线程池异步发 
        // 同时记录缓存 
        return true; 
    } 
}

总结

其实总结起来很简单,增加必要的封装和抽离,通过入参和返回值把控。
用看书的思维组织代码系统的,增加一个业务的可阅读可理解能力,在一个系统发展一定阶段之后,最让RD同学苦恼的不是技术问题,往往是一些业务逻辑或者布丁代码,所以研发同学要有意识的对业务和技术进行抽离,而不是简单的将技术和业务纠缠在一起,做好某块业务逻辑代码随时可以删掉而不影响系统的能力。
建立适当的代码命名规则,避免IDE带来的不必要的误用。
丰富wiki及文档,涉及到测试用例,数据库字段文档,产品PRD等。