模組是系統的小單元,管理API的單位,我們可以獨立創建、重複使用、進行擴充,無論語言技術在模組化方面是否明確支援,模組化的設計考量,應該在設計系統的過程中,始終當成一級公民來看待。

模組化是或不是什麼?

對於不同語言來說,「模組」這個名詞可能代表不同技術。例如,Python/JavaScript將一個原始碼檔案視為模組,可控制原始碼檔案中的API是否公開;Go將其套件的概念擴充為模組,模組可以包含數個套件,可描述模組間的相依性與版本;而Java 9將模組視為套件的集合,可控制套件是否公開、模組間相依性與版本等更多特性。

然而,模組化的思考與設計,與各語言技術上的「模組」定義沒有直接關聯。

因為,分門別類地管理實體原始碼檔案,為API設計名稱空間,或者使用語言中直接稱呼為「模組」的技術,並不等於從事模組化設計,充其量只是模組化的過程可能採用的實作方式,而技術層面上剛好支援罷了。

Java 8以前的rt.jar就是絕佳的例子,但它模組化了嗎?沒有!因為JAR並不是模組的邊界,充其量只是一堆套件的組合,在類別路徑下被攤開為一片平坦的API清單,這些API彼此間互相取用,開發者難以釐清關係,從中只擷取想要的子模組,從而令過去的Java執行環境難以瘦身。

在面對一組API的設計與組織時,開發者若開始思考著,在改變此API時,特別是改變API公開介面時,會影響系統中多少區塊?只有這個原始碼、套件(資料夾)?還是會擴及其他區域?在設計這個API時,應該將它放到哪個原始碼或套件之中?其他原始碼、套件中的API變更時,會對這塊API造成多大影響?事實上,面對這類問題時,就是在思考模組化了。

從另一個角度來看,在查看或試著理解系統時,思考著該從哪一塊開始看起;在思考著某個區塊的設計,可否獨立出來在其他系統中使用;在試著為API分門別類時,想著這是必要的基礎API,或是額外的效用(Utility)API等問題時,也是試著在進行模組化的思考了。

便於建立與設計、易於重用與擴充、便於理解的分類、妥善隱藏的實作細節,以及良好定義的公開性、清楚的相依關係與版本控制等,當我們思考著如何令手邊的一組API在上述考量下取得平衡,這才是模組化設計。

模組的邊界

乍看之下,這不是跟設計函式、類別等很像嗎?某些程度上是有點像,不過粒度要來得更大,因為,模組相依並非以函式或類別的等級在看待,模組化設計會是以整體關係來思考,就算A模組只取用了B模組中的一個函式,A模組就算是整個依賴在B模組了。

正因為粒度被放大了,慣於以函式、類別粒度來思考的開發者,很容易忽略模組邊界這件事,就算單看函式、類別間的關係在設計上還算良好,卻看不清楚模組的邊界在哪裡。

近年常見微服務的設計與探討,當中就可以發現許多例子。

在很大程度上,模組化設計與微服務設計之間有很大的同質性,因為單體應用程式是否易於拆解出微服務,關鍵就在於單體應用程式本身設計上,是否有良好的模組邊界,而單體應用程式往往難以拆解為微服務,這件事實也就說明了開發者經常忽略模組邊界。

可否獨立地重複使用,是思考模組邊界的一個方式,通常有兩個部份要思考:公開哪些API,以及如何相依在另一模組。直接依賴另一模組的公開API可能是不利的,若是如此,應該自定一個銜接介面,包裹相依的模組,以利日後抽換模組。

單純只思考獨立性,以及可否重複使用,也可能令模組之間,有著錯綜複雜的關係(想想Java 8以前的標準API),因此,另一種思考模組邊界的方式是:可否清楚地畫分並理解系統的組成。想想看,假以時日回頭或者是其他開發者查看系統時,可否清楚地辨識出各個模組,以及他們組成系統的順序或關係。

以特定功能或任務來組織模組是種方式,例如,我們可以將矩陣運算相關功能組織為模組,或者將團隊任務的邊界對齊模組邊界;在《Java 9模組化》第五章的〈決定模組邊界〉中,也談到類似作法可以參考。

模組化的策略

在〈決定模組邊界〉當中,也談到下列這些事情,那就是,模組化下的種種考量之間「有些張力(tension),這裡沒有一體適用的解決方案」,實際上,將來如果程式庫的規模擴大、版本更迭等情況下,面臨的考量也會不同。

如果能依需求而適時進行重構是最好的,然而,版本控制就相對地重要,因為這會有利於模組的客戶端能做出相對應的措施,各版本號之間要有明確的語意畫分,適當地透過棄用警訊,在幾個小版本之間暫時維持相容性,在大版本間完成模組的重構調整。

別以為使用了相依管理工具,我們此時就算完成了模組化設計,因為,相依管理工具其實只是解決模組的版本問題,並不見得解決了模組穿透之類的問題。

例如,開發者不該輕忽地呼叫模組A依賴的底層模組B,因為未來A模組可能不再依賴模組B,相依管理工具屆時也就不會下載模組B,這時開發者該怎麼辦呢?

因為,既存的(龐大)系統要進行模組化,就不是那麼容易了,這方面的經驗文件也太少,然而,我們可以借鏡將單體應用程式重構為微服務的經驗,這部份可參考先前專欄〈重構與微服務〉提供的一些文件鏈結。

借鏡Java 9模組化之路是個方式,就如《Java 9模組化》的序談到的,Java 9模組化在做的事,就像是把「快速拉開桌布,讓杯子停在原處」這件事反過來執行一樣地困難,需要一些策略,在先前專欄〈Java 9模組化概觀〉曾提過一種自底而上的遷移策略,有興趣可以參考。

Java 9 模組化是在技術層面上,嘗試管理與降低複雜性,可惜它導入的時間太晚,整個Java生態圈在過去並沒有將模組化設計,作為一級公民來考量。如果突然將整個模組化作為整個平臺的設計中心,過去既有的一切勢必構成強大的反對聲浪,另一方面,它帶來的強制性是否一體適用,也是個很大的爭議點。

把模組化當成一級公民

但是,模組化不單只是Java圈子的問題,畢竟在這套程式語言設計之初,誰能預期Java會被應用到現今的規模呢?只要沒將模組化當成是一級公民來思考,任何語言只要被應用到某個規模,都會有這個問題。

因此,真正重要的是,能被當成是一級公民來思考的,並非止於物件、函式等技術層面的元素,若面對的是整個系統,一開始就須將模組作為一級公民來思考與規畫,而不是等到整個API都攤成一個平面,彼此之間關係錯綜複雜、嘗到苦果之後,再來處理。

專欄作者

熱門新聞

Advertisement