嗨~歡迎閱讀第 55 期 ExplainThis 全端開發雙週報!
在這期雙週報的主題文中,我們會來談許多人都聽過的「高內聚、低耦合 high cohesion, low coupling」程式撰寫原則。然而在絕大多數的網路文章中,談的重點都是低耦合,過去 ExplainThis 也寫過《軟體設計上,低耦合是什麼意思? 為什麼要鬆散耦合?》一文。
然而,高內聚呢? 高內聚是什麼? 為什麼重要? 跟低耦合之間著重的點有什麼不同? 我們會在這期雙週報來談談。除了主題文外,這期雙週報一樣收錄了過去兩週社群中推薦一讀的內容,讀者們不要錯過了。
以上,讓我們進到這期的主題文吧!
「高內聚、低耦合」中的高內聚是什麼?
在實際談程式撰寫時的高內聚之前,想先用一個生活化的例子跟大家談「內聚」是什麼概念。
以多數人都熟知的廚房為例,在一個料理新手的廚房中,可能因為本身對不同的器具與調味料不熟,所以可能會把所有的東西都塞在一起。這種狀況下,假如今天要煮一道菜,光是找要用的工具以及搭配的調味料,可能就需要先花不少時間。
然而,假如有看過餐廳中專業廚師所使用的廚房,多半會發現整體的動線是精心設計過,不同的廚具、食材、調味料的位置擺放,也都是有縝密的計算,讓廚師能夠在最短的時間拿到自己要用的廚具、食材與佐料。
舉例來說,一個常見的分類方式是把肉類跟蔬菜分開來放,在肉類中不同的肉也會有不同的收納,牛肉不會跟魚肉混在一起,這樣要烹調牛肉相關的料理時,可以非常迅速地找出來。
當然,有些廚房可能會有不同的分類。舉例來說,有些鐵板燒店之所以整體運作效率非常高,其中一個原因是它們把熱門餐點所需的材料,全都事先整理好了。所以當點了沙朗牛排,廚師可以直接拿出要煎那道牛排所需的所有東西,從牛肉本身到相關配料。
上面的這個例子,就是高內聚的表現。例子中的沙朗備盤,是單一職責、單一目的,其存在就是為了讓廚師能煎出美味的沙朗牛排。在沙朗備盤中,沒有任何跟要煎沙朗牛排無關的東西,裡面有的材料全都是與製作沙朗牛排相關。
抽象一點來說,高內聚是指「把相關的東西聚在一起,藉此達到單一目的」,所以備盤的單一目的,以及盤中的東西都是高度相關,就是高內聚的展現。
而當這樣準備,就能夠讓廚師運作起來很高效,因為不用為在煎沙朗牛排時,還要在混雜著甜點佐料的區域找調味料;因為不用大範圍的東翻西找,這能讓速度變快,也能避免拿錯東西。
希望透過上面的例子,讀者們對於高內聚是什麼,有比較具體的理解。在這個段落,讓我們把重點拉回軟體工程的領域中。在軟體工程中,所謂的高內聚是指,把相關的東西聚在一個模組 (例如某個類別或函式)當中,讓該模組有單一且定義清楚的職責。
因此在檢視一個類別或函式時,如果該類別或函式有一個清楚的目的,且類別與函式當中的內容,彼此相近,且僅為該目的而存在,這樣就會說是高內聚。反之,如果一個類別或函式,做了很多事,且做的事情彼此沒關聯,就會被認為是低內聚。
在理解完高內聚的定義後,相信有些讀者這時可能會有個疑問,那就是「高內聚、低耦合」這句話似乎有一點彼此相衝突,高內聚在講要把相近的東西聚在一起,這樣跟耦合在談的要把東西拆開、抽出去似乎有點相違背。
關於這個問題,在《Balancing Coupling in Software Design》一書當中,作者 Vlad Khononov 談到一個很精闢的點。他說內聚是指「模組當中,要素之間彼此相近的程度」。換句話說,內聚本身就是一種耦合。只是內聚的耦合是一種「好耦合」 (作者的原文是寫 cohesion as “good coupling”)。
之所以說這個說法精闢,是因為在寫程式時,總是不免有東西要放一起,只是當放在一起時,如果又要擔心耦合的揉雜,可能會讓大家在寫程式時有所顧忌。但如果用「好耦合」的角度來看,假如某些東西彼此足夠相近,放在一起能帶來上述高內聚的好處,那麼即使放在一起有耦合也無妨。
因此,在思考高內聚時,推薦讀者們可以從「好耦合」的角度來看,推薦可以思考「哪些東西放在一起會讓未來要維護時可以更容易」,藉此來決定什麼要放一起、什麼要拆開。
除此之外,在看「高內聚、低耦合」時,也推薦讀者們可以用不同的視角來看。
具體來說
高內聚是在檢視「模組之內」,確保模組內的東西高度關聯,專注在單一目標
低耦合是在檢視「模組之間」,確保模組間不相互糾纏,避免牽一髮動全身的狀況
事實上,這兩種是相輔相成的,當今天如果我們確保一個模組是高內聚的,就能同時確保這個模組有清楚的邊界,這樣跟其他模組之間也比較容易是低耦合的。
閱讀更多
如果讀者們想更了解高內聚這個主題,並有具體的程式碼案例分析,以及高內聚在軟體工程其他面向的應用,我們在 E+ 成長計畫的主題文中,都有更詳細談。感興趣的讀者,可以在以下連結看到 E+ 的詳細介紹 (連結)。
本期推薦
Anthropic 團隊前陣子跟 Rick Rubin 推出了 The Way of Code 專案,透過程式碼實作藝術成果,還能線上編輯,推薦大家去玩玩 (連結)
近期關於 Vibe Coding 的爭論始終沒有停下來過,但最近看到Matt Pocock 寫的 AI-assisted Development,透過兩個指標區分兩者之別,非常精闢 (連結)
DeepIntoDev 一直以來會分享一些「某個技術背後如何運作」的內容,最近讀《How Databases Store Your Tables on Disk》用淺白的方式談資料庫的運作 (連結)
今年是 JavaScript 這個程式語言推出滿第三十年,看到《A Brief History of JavaScript 》一文爬梳了 JavaScript 語言每年的重大事件,雖說這個語言最初是十天被創造出來,但三十年來的持續迭代是更重要的,感謝前人們的付出 (連結)
Remix 團隊最近發了一篇《Wake up, Remix!》 談到了新的發展方向,其中讓人最感震撼的,是接下來將不再繼續依賴 React 的路線 (連結)
最近重溫了《The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets》一文,該文在談開發者都該懂的 unicode ,二十年老文章,現在依然值得推薦 (連結)
最近 ExplainThis 分享了《為什麼寫好 Pull Request 描述很重要?》的貼文並分享寫 PR 描述可以用的模板 (連結)。如果想進一步了解如何用 AI 自動完成 PR 描述撰寫,歡迎加入 E+ 成長計畫,觀看《Cursor 入門到實戰與 MCP 應用》線上課 (連結)
文末彩蛋
最近讀到一句讓人很有感的話
不求事情變得更容易,求自己變得更好。
不求更少問題,求自己有更多技能。
不求挑戰變少,求自己更有智慧。
原文是
Don’t wish it was easier, wish you were better.
Don’t wish for less problems, wish for more skills.
Don’t wish for less challenges, wish for more wisdom.
— Jim Rohn
在軟體工程師的職涯路上,免不了遇到各種讓人感到挫折、沮喪的片刻。在煎熬的當下,可能會希望挑戰少一點、困難小一點。
然而,換個角度看,那些讓自己感到不舒適的難關,都意味著自己還有成長的空間。比起求更輕鬆,求讓自己變更好、更有能力;當用這個角度看,持續提升自己,在未來就能接住更困難也更有影響力的事~