如果這是第二次看到我的文章,歡迎訂閱z哥的公号(跨界架構師)哦~
本文長度為2871字,建議閱讀8分鐘。
堅持原創,每一篇都是用心之作~
下面的這個場景你可能會覺得很熟悉(Z哥我又要出演了):
Z哥:@All 兄弟姐妹們,這次我這邊有個需求需要給「商品上架」增加一道審核,會影響到大家和我交互的接口。大家抽空配合改一下,明天一起更新個版本。小Y:哥,我這幾天很忙啊,昨天剛配合老王改過促銷!小X:行~當一切已成習慣。作為被通知人,如果在你的現實工作中也發生了類似事件,我相信哪怕嘴上不說,心裡也會有不少想法和抱怨:“md,改的是你,我也要發布,好冤啊!”。
這個問題的根本原因就是多個項目之間的耦合度過于嚴重。
越大型的項目越容易陷入到這個昭潭中,難以自拔。
而解決問題的方式就是進行更合理的分層,并且持續保證分層的合理性。
一提到分層,必然離不開6個字「高内聚」和「低耦合」。
什麼是高内聚低耦合在z哥之前的文章中有多次提到,分布式系統的本質就是「分治」和「冗餘」。
其中,分治就是“分解 -> 治理 -> 歸并”的三部曲。「高内聚」、「低耦合」的概念就來源于此。
需要注意的是,當你在做「分解」這個操作的時候,務必要關注每一次的「分解」是否滿足一個最重要的條件:不同分支上的子問題,不能相互依賴,需要各自獨立。
因為一旦包含了依賴關系,子問題和父問題之間就失去了可以被「歸并」的意義。
比如,一個「問題Z」被分解成了兩個子問題,「子問題A」和「子問題B」。但是,解問題A依賴于問題B的答案,解問題B又依賴于問題A的答案。這不就等于沒有分解嗎?
題外話:這裡的“如何更合理的分解問題”這個思路也可以用到你的生活和工作中的任何問題上。所以,當你在做「分解」的時候,需要有一些很好的着力點去切入。
這個着力點就是前面提到的「耦合度」和「内聚度」,兩者是一個此消彼長的關系。
越符合高内聚低耦合這個标準,程序的維護成本就越低。為什麼呢?因為依賴越小,各自的變更對其他關聯方的影響就越小。
所以,「高内聚」和「低耦合」是我們應當持續不斷追求的目标。
題外話:耦合度,指的是軟件模塊之間相互依賴的程度。比如,每次調用方法 A 之後都需要同步調用方法 B,那麼此時方法 A 和 B 間的耦合度是高的。内聚度,指的是模塊内的元素具有的共同點的相似程度。比如,一個類中的多個方法有很多的共同之處,都是做支付相關的處理,那麼這個類的内聚度是高的。二、怎麼做好高内聚低耦合做好高内聚低耦合,思路也很簡單:定職責、做歸類、劃邊界。
首先,定職責就是定義每一個子系統、每一個模塊、甚至每一個class和每一個function的職責。
比如,在子系統或者模塊層面可以這樣。
又比如,在class或者function層面可以這樣。
我想這點大家平時都會有意識的去做。
做好了職責定義後,内聚性就會有很大的提升,同時也提高了代碼/程序的複用程度。
至此,我們才談得上「單一職責(SRP)」這種設計原則的運用。
其次,做歸類。梳理不同模塊之間的依賴關系。
像上面提到的案例1可以歸類為3層:
- 基礎層:商品基礎服務、會員基礎服務、促銷基礎服務
- 聚合層:購物車服務、商品詳情服務、登陸服務
- 接入層:快閃店API、綜合商城API
案例2也可以歸類為3層:
- 數據訪問層:訪問會員表數據、訪問會員積分表數據、訪問會員等級表數據
- 業務邏輯層:會員登陸邏輯、會員使用積分邏輯、會員升級邏輯
- 應用層:接收用戶輸入的賬戶密碼、接收用戶輸入的使用積分數、接收用戶的付款信息
最後就是劃邊界。好不容易梳理清楚,為了避免輕易被再次破壞,所以需要設立好合理清晰的邊界。
否則你想的是這樣整齊。
實際會慢慢變成這樣混亂。
那麼應該怎麼劃邊界呢?
class和function級别。這個層面可以通過codereview或者靜态代碼檢測工具來進行,可以關注的點比如:
- 調用某些class必須通過interface而不是implement
- 訪問會員表數據的class中不能存在訪問商品數據的function
模塊級别。可以選擇以下方案:
- 給每一種類型的class分配不同project,打包到各自的dll(jar)中
- 每次代碼push上來的時候檢測其中的依賴是否有超出規定的依賴。例如,不能逆向依賴(檢測dal是否包含bll);不能在基礎層做聚合業務(檢測商品基礎服務是否包含其他基礎服務的dll(jar))。
系統級别。及時識别子系統之間的調用是否符合預期,可以通過接入一個調用鍊跟蹤系統(如,zipkin)來分析請求鍊路是否合法。
讓邊界更清晰、穩定的最佳實踐很多時候不同的模塊或者子系統會被分配到不同的小組中負責,所以z哥再分享幾個最佳實踐給你。它可以讓系統之間的溝通更穩定。
首先是:模塊對外暴露的接口部分,數據類型的選擇上盡量做到寬進嚴出。比如,使用long代替byte之類的數據類型;使用弱類型代替強類型等等。
舉個「寬進嚴出」的例子:
//使用long代替byte之類的數據類型。void Add(long param1, long param2){ if(param1 <1000&& param2 < 1000){ //先接收進來,到裡面再做邏輯校驗。 //do something... } else{ //do something... }}其次是:寫操作接口,接收參數盡可能少;讀操作接口,返回參數盡可能多。
為什麼呢?因為很多時候,寫操作的背後會存在一個潛在預期,是「準确」。
準确度和可信度有着很大的聯系,隻有更多的邏輯處理在自己掌控範圍内進行才能越具備「可信度」(當然是職責範圍内的邏輯,而不是讓商品服務去計算促銷的邏輯)。反之,上遊系統一個bug就會牽連到你的系統中。
而讀操作背後的潛在預期是:「滿足」。你得提供給我滿足我當前需要的數據,否則我的工作無法開展。
但是呢,在不同時期,客戶端所需要的數據可能會發生變化,你無法預測。所以呢,不要吝啬,返回參數盡可能多,用哪些,用不用是客戶端的事。
還可以做的更好的一些,就是,在可以滿足的基礎上支持按需獲取。客戶端需要返回哪些字段自己通過參數傳過來,如此一來還能避免浪費資源做無用的數據傳輸。
題外話:對外露出的接口設計,可以使用http json 這種跨平台 弱類型的技術組合,可具備更好的靈活性。實際上,一個程序大多數情況下,在某些時刻是客戶端,又在某些時刻是服務端。站在一個完整程序的角度來提煉參數設計的思路就是:“吃”的要少,“産出”的要多。
題外話:有一些設計原則可以擴展閱讀一下。單一職責原則SRP(Single Responsibility Principle)開放封閉原則OCP(Open-Close Principle)裡式替換原則LSP(the Liskov Substitution Principle LSP)依賴倒置原則DIP(the Dependency Inversion Principle DIP)接口分離原則ISP(the Interface Segregation Principle ISP)總結本文z哥帶你梳理了一下「高内聚低耦合」的本質(來自于哪,意義是什麼),并且分享了一些該怎麼做的思路。
可以看到「高内聚」、「低耦合」其實沒有這個名字那麼高端。哪怕你現在正在工作的項目是一個單體應用,也可以在class和function的設計中體會到「高内聚」、「低耦合」的奧妙。
來來來,接下去馬上開始在項目中「刻意練習」起來吧~
「易伸縮」篇的相關文章:
- 分布式系統伸縮性大招之——「無狀态」
如果你喜歡這篇文章,可以點一下右下角的「贊」。
這樣可以給我一點反饋。: )
謝謝你的舉手之勞。
▶ 關于作者:張帆(Zachary)。堅持原創,每一篇都是用心之作~
既然都看到這了,順手分享給更多志同道合的小夥伴吧~,歡迎訂閱z哥的公号(跨界架構師)。
定期發表原創内容:架構設計丨分布式系統丨産品丨運營丨一些深度思考。
作者:跨界架構師(Zachary)。
更多原創精品,歡迎加入小圈子,請戳【了解更多】
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!