代碼改變世界

重構系統的套路-寫有組織的代碼

2018-06-28 10:30 春哥大魔王 閱讀(...) 評論(...) 編輯 收藏

如果一個項目經歷了快速發展,勢必在業務發展背后留下了一個很無序,結構混亂的代碼,無序而混亂的代碼勢必造成很大的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等。

四川金7乐历史开奖号码查询