高內聚與低耦合

高內聚與低耦合

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1



WHAT

    "高內聚"與"低耦合"是軟件設計和開發中經常出現的一對概念。它們既是做好設計的途徑,也是評價設計好壞的標準。"高內聚"是說,一個業務應當儘量把它所涉及的功能和代碼放到一個模塊中;"低耦合"則是說,一個業務應當儘量減少對其它業務或功能模塊的依賴。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

“高內聚低耦合”也是這麼成雙成對地出現


    這一對概念其實不僅適用於軟件行業。如果你是宅男/宅女,高內聚就是吃喝拉撒睡都不用出門,低耦合就是不用跟人打交道。如果你是上班族,高內聚就是工作都由自己說了算,低耦合就是跨組/跨部門的溝通協作少。如果你常跟政府部門打交道,一定還記得以往去政府辦一個證明要跑八九個部門、蓋十幾個章的麻煩,這其實就是政府服務"低內聚"的惡果——後來政府精簡辦事流程,一個窗口蓋完所有章,這就是一種"高內聚"的服務。如果你是蘋果的重度用戶,一定對"封閉生態環境"體會深刻:它固然能提供很好的服務,但是深陷其中之後——iPhone、iPad、macbook、iCloud等——那麼,即使其中某項服務無以爲繼了、或者滿足不了需求了,也無法輕易地脫身而出、改換門庭。這就是"高耦合"帶來的難題。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

一隻“高內聚低耦合”的喵……


WHY & WHY NOT

    高內聚低耦合的優點和缺點都是顯而易見的。

優點

    如果一個模塊、一個系統能夠做到高內聚、低耦合,那麼,它就具有了非常高的可擴展性。可擴展性高意味着“未來有無限可能”:功能不滿足新需求了,在模塊內部簡單擴展就能滿足;性能上達不到要求了,系統內部挖潛就能達標;技術太過陳舊要更新換代了,自己就能完成新陳代謝……


    這麼說可能有些費解,我們還是舉例子吧。

    

    對宅男/宅女來說,高內聚就是吃喝拉撒睡都不用出門,低耦合就是不用跟人打交道。這樣一來,我想吃披薩就吃披薩,想喝兩碗豆漿就喝兩碗豆漿,完全不用顧慮別人想吃什麼、別人會說什麼。這樣的生活愜意不?   

    對上班族來說,高內聚就是工作都由自己說了算,低耦合就是跨組/跨部門的溝通協作少。這樣一來,我想用數據集A就用數據集A,想用數據集B就用數據集B;想用Excel用Excel,想寫腳本寫腳本;想用PPT彙報就用PPT彙報,想用demo彙報就用demo彙報。這樣的工作快樂不?

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

我反正是……


    技術上……我還是舉個例子吧。我們系統中有一個模塊,用於處理用戶短信驗證操作。簽約驗證成功後,針對具體的業務還需要做一些後續處理。隨着業務的不斷髮展,後續業務處理從最初的一套邏輯增加到現在的五套邏輯。此外我們還做了一些技術優化,如優化庫表結構、增加併發處理等,最後的流程大概是這樣的:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

短信驗證模塊的基本流程


    由於整個短信驗證模塊、以及後續業務處理模塊都做到了高內聚、低耦合(自吹自擂臉),因此,無論是業務擴展還是技術優化,都隻影響到了各自對應的模塊,其它模塊、功能對此毫無感知。尤其是業務擴展、增加一個後續業務處理模塊時,其它後續業務處理模塊絲毫不受影響。沒有影響意味着開發不用改代碼、測試不用迴歸測試、上線時間可以縮短、老代碼裏不會引入新問題……


    可見,高內聚低耦合的設計能帶來多贏的結果。對開發來說,改動的代碼當然是越少越好——高內聚低耦合的設計能減少要修改的代碼量。對測試來說,測試範圍當然是越小越好——高內聚低耦合的模塊只需要測試新增功能而不需要回歸原有功能。對產品來說,需求上線的時間當然是越短越好——高內聚低耦合的設計能減少開發和測試的工作量,上線時間自然也就變短了。對用戶來說,系統問題當然是越少越好——高內聚低耦合的設計至少能保證老代碼正常運行,不會因爲新功能而莫名崩潰。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

你好了不?


缺點

    優點如此顯而易見,爲什麼實際工作中很少有人認真執行“高內聚低耦合”的設計要求呢?


    如果你是一個政府部門,你是願意自己只蓋一個章、讓用戶去跑其它部門蓋完剩下的十幾個章,還是願意讓用戶只來你這一個部門、你去跑其它部門改完所有的章?如果你是一家商店,你是願意讓用戶把錢全都花在你的店裏,還是願意提供一堆服務讓用戶去別的店鋪消費?做軟件設計也要面對“投入產出比”的考慮。如果一個良好設計的成本太高而收益太低、甚至與主要目標背道而馳,那麼人們自然就會用腳投票、不使用這種設計。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

前幾年這種漫畫沒少見吧


    “高內聚低耦合”的設計就常常會陷入這樣的困境中。首先,要嚴格做到高內聚、低耦合,通常多要付出很多精力來做設計,還常常會增加不少的開發工作量。而這些額外成本有時並不一定能帶來期望的結果。以前面說的短信驗證功能模塊爲例,如果五種業務邏輯的差異都只有一兩行代碼,那麼全用if-else的方式放在一個類中會是一種更合適的方法。但是,如果業務邏輯本身比較複雜、或者彼此之間差異比較大,還用if-else處理的話,最後代碼會變成一團亂麻、快速腐化。因此,儘管我們的設計多了六個類(一個業務分發類、五個不同的業務處理類),並導致了前期開發時多花了一天半天的時間,但整個模塊的可維護性、可擴展性都有很大提高,可以說是“過當”了吧。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

這是短信驗證模塊目前的類圖


    其次,“高內聚低耦合”是一種原則性的設計準則。在現實中,原則性的東西一般都會爲靈活性——比如工期啦,歷史遺留問題啦,部門關係啦——讓步。我曾經經常聽到“以前就是這麼做的,這次也這樣處理吧”、“他們的接口就是這樣的,我們也沒辦法”這樣的話,也經常看到因爲這些原因而放棄更好的設計,甚至因此而不再思考有沒有更好的設計。有時真讓人感嘆技術其實什麼都改變不了。另外,原則性的東西往往太務虛而無法落到實處。“這個模塊不夠高內聚”,“這段代碼的耦合度太高了”,具體是怎麼不夠高內聚?要改成怎樣才能降低耦合度?完全叫人丈二金剛摸不着頭腦。最後,法學上有種罪名叫做“籮筐罪”,當你覺得一個東西有問題、但又說不清楚具體是什麼問題時,就可以把它歸入“籮筐罪”中——也就是所謂“xx罪是個框,什麼都能往裏裝”。“高內聚低耦合”可以說是軟件設計界的“籮筐罪”,只要想往裏裝就能往裏裝。例如前面那個短信驗證功能模塊,它真的滿足了“高內聚低耦合”了嗎?雖然經過精心設計,但是如果要挑,也還能挑出一籮筐毛病來。所以,“高內聚低耦合”的度很難把握,過於執着甚至可能走火入魔。這也是爲什麼“高內聚低耦合”很少在實際工作中提及和應用的原因之一。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

原則和實踐……常常就是這麼殘酷


HOW

    有些問題可以靠技術來解決。例如,開發成本高這個問題在團隊開發水平提高到一定層次之後就迎刃而解了;太過務虛的問題也可以通過學習和掌握SOLID、設計模式等具體的設計技能來解決。這些具體的技術會在以後慢慢討論。但是非技術的問題——例如歷史包袱、部門關係等,我就愛莫能助了。


高內聚低耦合與抽象

    雖然高內聚低耦合有上面這些問題,但是,在做業務抽象的設計時,我們還是要認真考慮、並遵守這個原則。因爲高內聚低耦合不僅是做好設計的途徑、也是評價設計好壞的標準:對於面向對象的業務抽象設計來說更是如此。

    一個好的業務抽象必須能表達出自己“是什麼”或者“能做什麼”、而隱藏自己“怎麼做”。高內聚、低耦合的理念正適合用來讓業務抽象隱藏自己的實現細節。如果一個業務抽象不夠內聚或者過度耦合,它一定會過多地把自己的實現細節泄露出去。還記得在《抽象》一文中出現的QueryService嗎?它把queryFromRemote和queryFromLocal暴露到業務抽象外部,導致了使用接口時的種種不便,這就是不夠高內聚的結果。還有ExcelService,這個接口與2003版的Excel文件格式過度耦合了,結果無法順利地升級到20007版Excel上。


qrcode?scene=10000004&size=102&__biz=MzUzNzk0NjI1NQ==&mid=2247484270&idx=1&sn=4ee9a0bcbd1a45014d90b28282f735d1&send_time=

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章