一、復(fù)雜度綜述
1、什么是復(fù)雜度
軟件設(shè)計的核心在于降低復(fù)雜性。
--《軟件設(shè)計的哲學(xué)》
業(yè)界對于復(fù)雜度并沒有統(tǒng)一的定義,斯坦福教授John Ousterhout從認知負擔和工作量方面給出了一個復(fù)雜度量公式
?
??
子模塊的復(fù)雜度cp乘以該模塊對應(yīng)的開發(fā)時間權(quán)重值tp,累加后得到系統(tǒng)的整體復(fù)雜度C
這里的子模塊復(fù)雜度cp是一個經(jīng)驗值
需要注意:如果一個子系統(tǒng)特別復(fù)雜,但是很少使用及修改,也不會對整體復(fù)雜度造成太大影響。例:spring框架內(nèi)部代碼較為復(fù)雜,但由于幾乎不需要我們?nèi)プ儎?,所以對系統(tǒng)的整體復(fù)雜度影響并不大
2、復(fù)雜度分類
?
?
??
本文主要面向業(yè)務(wù)復(fù)雜度的治理
3、業(yè)務(wù)復(fù)雜度高的影響
(1)研發(fā)成本高。需要花費更多的時間去理解、維護代碼;同樣的需求,可能需要要修改更多的工程和類
(2)穩(wěn)定性差。過高的業(yè)務(wù)復(fù)雜度,會導(dǎo)致系統(tǒng)難以理解甚至理解出現(xiàn)錯漏,改動代碼后極易出現(xiàn)“按下葫蘆起了瓢”的問題
二、業(yè)務(wù)系統(tǒng)復(fù)雜度高的常見原因
1、業(yè)務(wù)系統(tǒng)模塊多,關(guān)系復(fù)雜,互相依賴
比如一個電商業(yè)務(wù),會包含商品、訂單、采購、庫存、財務(wù)等多個系統(tǒng),系統(tǒng)之間有各種各樣的依賴關(guān)系關(guān)系,如訂單系統(tǒng)依賴庫存充足才可以正常下單,采購依賴商品必須創(chuàng)建才可以發(fā)起采購;而系統(tǒng)內(nèi)部又可以劃分為多個子系統(tǒng),如訂單系統(tǒng)可以包含接單、營銷、會員等各個子系統(tǒng)
2、代碼晦澀,從代碼中很難找到關(guān)鍵信息
如下邊的業(yè)務(wù)處理代碼,不點進具體的方法,根本不知道做了什么:
/**
* 處理業(yè)務(wù)邏輯
*/
public void handleBiz(){
step1();
step2();
step3();
}
再比如下邊的業(yè)務(wù)處理代碼,做了方法定義外的操作,開發(fā)者很容易遺漏重要信息
/**
* 轉(zhuǎn)換對象方法
*/
public Po convert(Dto dto){
Po po=new Po();
po.field=dto.filed;
//更新操作,不應(yīng)該放到轉(zhuǎn)換方法里
mapper.update(po);
//調(diào)用rpc服務(wù),不應(yīng)該放到轉(zhuǎn)換方法里
XXGateway.update(dto);
return po;
}
3、業(yè)務(wù)規(guī)則、流程變化多,變化頻繁
大量的變化造成花在系統(tǒng)上的時間增多,提升了開發(fā)時間權(quán)重;
繁雜的業(yè)務(wù)規(guī)則寫在代碼中,難以理解、梳理
?
?
三、降低業(yè)務(wù)復(fù)雜度的方法
1、抽象分治,分解復(fù)雜度
(1)領(lǐng)域拆分
將與核心概念有關(guān)的內(nèi)容抽取/合并,形成獨立的領(lǐng)域
a、實體類的系統(tǒng)。映射到物理實體,比如商品中心、用戶中心、地址服務(wù)等
b、流程類系統(tǒng)。映射到多個角色的串聯(lián)協(xié)調(diào)工作,比如供應(yīng)鏈上單,審核類系統(tǒng)等
c、計算任務(wù)類系統(tǒng)。映射到虛擬計算機類及數(shù)據(jù)處理,比如搜索排序、推薦計算等
供應(yīng)鏈領(lǐng)域化拆分例子:
?
?
??
?
(2)領(lǐng)域之內(nèi)拆分
01、變與不變拆分。將不易變化的系統(tǒng)能力拆分出來,上層適配各種業(yè)務(wù)邏輯,底層提供穩(wěn)定的能力單元。如營銷領(lǐng)域,將優(yōu)惠卡券這個不易變的內(nèi)容作為一個子系統(tǒng)來設(shè)計
?
02、場景隔離。如B/C隔離,B端業(yè)務(wù)復(fù)雜度較高,流量較小,更注重數(shù)據(jù)建模、可配置、可擴展;C端業(yè)務(wù)復(fù)雜度低,但是流量較大,更注重高性能、高可用
?
?
例:營銷領(lǐng)域進行【變與不變拆分】+【場景BC隔離】例子:
背景:營銷域即有面向商家的營銷資質(zhì)、營銷活動管理,又有面向C端用戶根據(jù)活動領(lǐng)紅包、優(yōu)惠券的高頻業(yè)務(wù)
其中,B端業(yè)務(wù)邏輯較復(fù)雜但請求量小,C端業(yè)務(wù)邏輯較簡單但請求量大
治理方案:【變與不變拆分】+【場景BC隔離】
建設(shè)營銷B端服務(wù)。提供面向商家的營銷活動管理,重心放在建設(shè)復(fù)雜的營銷活動模型、處理復(fù)雜的業(yè)務(wù)流程;
建設(shè)C端用戶系統(tǒng),提供面向C端客戶的領(lǐng)營銷資產(chǎn)、消費影響資產(chǎn)服務(wù),重心放在高并發(fā)、高可用建設(shè);
建設(shè)底層營銷資產(chǎn)管理服務(wù)。如卡券中心提供生成卡券、消費卡券、查詢卡券等基礎(chǔ)穩(wěn)定的服務(wù)。相對不易變化,不需要經(jīng)常迭代;
上層的服務(wù)根據(jù)不同的業(yè)務(wù)場景來決定把卡券發(fā)給誰、什么時候發(fā)、發(fā)多少,業(yè)務(wù)場景變化或者有新的場景時,經(jīng)常需要迭代
拆分架構(gòu)如下圖:
?
??
?
例:庫存中心【變與不變拆分】例子
背景:
01、業(yè)務(wù)上。庫存業(yè)務(wù)面向的業(yè)務(wù)場景較多,如采購加庫存、銷售扣庫存,退貨加庫存等等;庫存扣減邏輯也較復(fù)雜,如一個sku下多個渠道按照優(yōu)先級扣減、一個sku有多個批次按照過期時間先后扣減等等;
02、技術(shù)上。為了提升性能,高頻操作場景會先操作redis緩存,再異步同步DB數(shù)據(jù)
治理方案:抽象業(yè)務(wù)+變與不變拆分
庫存操作業(yè)務(wù)可以抽象為:根據(jù)用戶條件、庫存劃分規(guī)則定位到需要操作的庫存記錄,按照庫存記錄對庫存進行操作
變與不變拆分: 01、DB庫存操作、Redis庫存操作建設(shè)為底層支撐能力,僅提供基礎(chǔ)的庫存加減能力,盡量保障其不變性,避免大幅度的改動影響所有業(yè)務(wù) 02、根據(jù)業(yè)務(wù)邏輯去定位庫存,這部分相對來說變化比較頻繁,由上游業(yè)務(wù)系統(tǒng)進行處理
庫存架構(gòu)如下圖:
?
??
?
(3)子領(lǐng)域/系統(tǒng) 內(nèi)部拆分
01、代碼分層。確定每一層的分工;確定調(diào)用關(guān)系,不能跨層調(diào)用;如果下層能解決的復(fù)雜性問題,不要放到上層,如:外部接口調(diào)用失敗重試,不能放到服務(wù)層
常用的貧血模型分層例子:
?
??
核心思路:關(guān)注點分離、能力復(fù)用。各層職責:
?接入層:負責服務(wù)接入,包含日志打印、異常處理、參數(shù)檢查、權(quán)限檢查等接入層能力。該層不包含業(yè)務(wù)邏輯
@Override
public InsertDeptResponse insertDept(InsertDeptRequest request) {
//入?yún)⒂涗? log.error("insertDept request:{}",request);
InsertDeptResponse response=null;
String umpKey = XXX;
Profiler.registerInfo(umpKey, true, true);
try{
//校驗入?yún)ⅲ绻r灢煌ㄟ^,checkParam方法會拋出參數(shù)校驗不通過異常
checkParam(request);
//權(quán)限校驗,如果校驗不通過,checkAuth方法會拋出權(quán)限校驗不通過異常
checkAuth();
//業(yè)務(wù)邏輯處理,內(nèi)部可能會拋出影響可用性的異常和不影響可用性的異常
response=XXFacade.insertDept(request);
}catch(BizRuntimeException ce){
//不影響可用性的異常
log.error("XXX");
//組裝返回值
response=XXX;
}catch (Exception e) {
//影響可用性的異常
log.error("XXX");
//組裝返回值
response=XXX;
Profiler.functionError(info);
}
log.error("insertDept response:{}",response);
Profiler.registerInfoEnd(info);
return response;
}
?業(yè)務(wù)邏輯層:整體負責接口中的業(yè)務(wù)邏輯處理。主要進行各領(lǐng)域間的邏輯串聯(lián)、數(shù)據(jù)處理
?領(lǐng)域服務(wù)層:以某個核心概念為核心組件領(lǐng)域服務(wù),如權(quán)限服務(wù),將該領(lǐng)域相關(guān)能力進行收口,提供給上層可復(fù)用的能力
?數(shù)據(jù)層:負責與數(shù)據(jù)庫或者外部服務(wù)進行數(shù)據(jù)交互
02、自上而下的結(jié)構(gòu)化分解。使用金字塔原理,將復(fù)雜邏輯進行結(jié)構(gòu)化自上而下分解
例:供應(yīng)鏈業(yè)務(wù)二維碼牌安裝服務(wù)治理
背景:供應(yīng)鏈業(yè)務(wù)中,二維碼牌安裝服務(wù)流程特別復(fù)雜,理解與修改都很困難;流程中用到很多數(shù)據(jù),在不同方法中被重復(fù)獲取
治理方案:結(jié)構(gòu)化抽象、分解業(yè)務(wù)流程
?
?
??
結(jié)構(gòu)化分解后的流程:
?
?
??
拆分后主方法偽代碼:
public void setUp(){
//參數(shù)檢查階段
paramCheck();
//初始化階段
initData();
//業(yè)務(wù)校驗階段
businessCheck();
//業(yè)務(wù)執(zhí)行階段
businessExecute();
}
(4)方法邏輯拆分。職責單一、命名準確
方法隨意命名,尤其是做了與命名無關(guān)的事情,會極大的增加復(fù)雜度,影響閱讀者對代碼邏輯的理解
(5)系統(tǒng)合并
如果多個系統(tǒng)邏輯上耦合嚴重,改了一個模塊,另一些基本都要變動,考慮將這些系統(tǒng)合并
2、添加注釋,使代碼易懂
(1)代碼思路要通過注釋/自注釋標識出來
例:redis模式的庫存操作,在處理邏輯主方法中,較為清晰的標注了每一步核心邏輯
?
??
(2)注釋應(yīng)當能提供代碼之外額外的信息
重視What和Why,而不只是代碼是如何實現(xiàn)的(How)
01、一些不那么直觀的代碼,可以附上原因
?
??
?
02、設(shè)計思路,特別復(fù)雜的,可以考慮貼上設(shè)計方案的地址
?
??
3、配置化
(1)業(yè)務(wù)對象可配置
業(yè)務(wù)中用到的同類型對象特別多,使用硬編碼方式維護困難時,可以考慮抽象出可配置化的對象配置中心
如:商品中心,將商品抽象為sku,并提供名稱、價格、重量等可配置的屬性
(2)業(yè)務(wù)規(guī)則可配置
業(yè)務(wù)中規(guī)則部分特別復(fù)雜,可以考慮抽象出可配置化的規(guī)則配置中心
如:售賣策略配置,某sku在某業(yè)務(wù)線+某業(yè)務(wù)場景中,必須搭配某種前置sku,以XX價格進行售賣
業(yè)界有多種開源規(guī)則引擎,如Aviator、Drools、QLExpress等,不同的規(guī)則引擎在功能、性能、學(xué)習(xí)維護成本上有一定差異,需要根據(jù)自己的業(yè)務(wù)場景來進行選型
業(yè)務(wù)規(guī)則配置例子:
背景:物流能力中心項目中,要計算不同的倉類型、不同的資源類型(人員、場地、物資、車輛等)、不同履約時效的履約能力,每種場景接入的參數(shù)都不一樣,計算工時也不一樣,算法還經(jīng)常需要調(diào)整,如果使用硬編碼的方式,要對幾十種業(yè)務(wù)組合編寫業(yè)務(wù)邏輯,工作量大,維護困難
方案:引入規(guī)則引擎,實現(xiàn)業(yè)務(wù)規(guī)則可配置,提升開發(fā)、維護人效
?
?
??
?
(3)業(yè)務(wù)流程可配置
如果不同的業(yè)務(wù)場景,需要不同的執(zhí)行流程,可以考慮引入流程配置框架,如:一些業(yè)務(wù)場景,流程執(zhí)行順序為A-->B-->C,另外一些業(yè)務(wù)場景執(zhí)行順序為C-->B-->A,還有一些業(yè)務(wù)場景執(zhí)行順序為B-->A-->C。
注:流程配置框架相對比較復(fù)雜,更適合平臺/中臺建設(shè),其他場景建議謹慎評估后再引入
流程配置框架的核心:流程編排+能力復(fù)用(插件化),前提是流程抽象?流程標準化
業(yè)界流程編排框架:阿里TMF、美團BPF、京東物流batrix
TMF2.0 配置流程:
?
??
4、使用規(guī)范降低復(fù)雜度
本質(zhì)上,是做好約定,簡化思考。如:一個類命名為OrderDao,不需要看代碼,就可以很清晰的知道這是一個訂單數(shù)據(jù)處理的類
(1)代碼規(guī)范
如接口、類、方法等的命名
(2)架構(gòu)層級規(guī)范
系統(tǒng)分層級。上層調(diào)用底層,避免底層直接調(diào)用上層,同層級盡量避免互相調(diào)用
例:物流百川三層架構(gòu),定義了系統(tǒng)層級,使系統(tǒng)交互有序
?
??
?
四、業(yè)務(wù)復(fù)雜度優(yōu)化重構(gòu)原則
1、小步快跑。每個迭代要能獨立交付,保障每次迭代充分驗證,更快看到重構(gòu)效果
2、先寫后讀。通過雙寫,驗證新模型的可行性;通過數(shù)據(jù)一致性校驗后,再逐步遷移讀接口
3、先輕后重。先做簡單邏輯再做復(fù)雜邏輯。先遷移輕業(yè)務(wù),有了經(jīng)驗后,再去遷移更復(fù)雜的重業(yè)務(wù)
審核編輯 黃宇
-
軟件設(shè)計
+關(guān)注
關(guān)注
3文章
58瀏覽量
17770 -
代碼
+關(guān)注
關(guān)注
30文章
4779瀏覽量
68521
發(fā)布評論請先 登錄
相關(guān)推薦
評論