使用構建系統將許多項目分為模塊/子項目( Maven , Gradle , SBT …); 編寫模塊化代碼通常是一件好事。 將代碼分為構建模塊主要用于:
- 隔離代碼部分(減少耦合)
- api / impl拆分
- 僅將第三方依賴項添加到代碼的特定部分
- 具有相似功能的代碼分組
- 靜態檢查一個模塊中的代碼僅使用其依賴模塊(模塊間依賴)中的代碼
盡管有些人可能說它對于單獨的編譯也很有用,但我認為這并不重要(考慮一個項目時)。 如今,構建工具非常聰明,可以找出需要重新編譯的內容。
構建模塊問題
我認為這種方法存在一些問題。 首先,很難確定何時某個功能“足夠大”以將其轉換為構建模塊。 幾門課就夠了嗎? 還是您需要更多? 嚴格來說,每個模塊是否應該只有一種功能? 但這會導致模塊爆炸。 等等。 至少在我參與的項目中,這是討論的共同主題,即構建模塊的粒度應如何粗略。
其次,構建模塊非常“繁重”。 我想Maven最糟糕,您需要一個很大的xml來創建一個模塊,其中包含許多樣板(例如重復的組ID,版本號,父級定義); SBT和Gradle更好,但是,這仍然是一項巨大的努力。 需要創建一個單獨的目錄,整個目錄結構( src/main/...
, src/test/...
),更新構建配置等。總的來說,這很麻煩。
然后,當我們經常將漂亮的模塊分開時,事實證明,要使其中兩個模塊相互協作,我們需要一個“通用”部分。 然后,我們要么以一個blo腫的foo-common模塊結束,其中包含不相關的類,要么是多個小型foo-foomodule-common模塊; 第二種解決方案當然可以,只是浪費時間進行設置。
最后,構建模塊是您還必須命名的其他內容。 包名稱和類名稱很可能已經反映了代碼的作用,現在還需要在構建模塊名稱中重復(違反DRY)。
總而言之,我認為創建構建模塊非常困難且耗時。 程序員是懶惰的(這當然是一件好事),這導致設計不像它們可能的那么干凈。 是時候改變它了:)。
(另請參見我先前的模塊博客 。)
配套
Java,Scala和Groovy已經有一個用于對代碼進行分組的系統:程序包。 但是,當前包僅是字符串標識符。 除了一些非常有限的可見性選項(在Java中為package-private,在Scala中為package-scoping)之外,軟件包沒有語義。 因此,我們有幾個級別的分組代碼:
- 項目
- 構建模塊
- 包
- 類
如果我們將2.和3.合并在一起會怎樣? 為什么不應該使用軟件包來創建模塊?
包作為模塊?
讓我們來看看將包擴展為模塊需要做什么。 顯然,我們需要做的第一件事是將一些元數據與每個模塊相關聯。 已經有一些機制(例如,通過package-info.java
上的注釋),或者這可能是Scala中軟件包對象的擴展-可以混入某些特征,或覆蓋val 。
什么樣的元數據? 當然,我們不想將整個構建定義移到軟件包中。 但是讓我們分開關注 –構建定義應該定義如何構建項目,而不是模塊依賴項。 然后,在模塊的元數據中定義的第一件事就是對第三方庫的依賴。 這樣的定義可能只是符號,它將被綁定到構建定義中的具體版本。
例如,我們將指定包“ foo.bar.dao
”取決于“ jpa
”庫。 然后,構建定義將包含從“ jpa
”到Maven工件列表的映射(例如hibernate-core,hibernate-entitymanager等)。 而且,如果這種依賴關系可以傳遞到子包,則可能最有意義。 因此,定義全局庫將意味著增加對根包的依賴。
附帶說明一下,通過擴展Scala的包對象,甚至可以將其設置為類型安全的。 包對象可以實現特征,其中要覆蓋的值之一可以是第三方依賴項符號的列表。 符號本身可以包含在根包中定義的Enumeration
中; 這可以使諸如“根據jpa查找所有模塊”之類的事情在IDE中進行簡單的用法搜索。
第二步也是使用此機制定義模塊間的依賴關系。 在包的元數據中,可以定義其他包的列表,從中可以看到代碼。 這遵循當前使用的構建模塊的方式:每個模塊都包含可訪問的項目模塊的列表。 (另一個Scala旁注:由于包對象將實現特征,因此這意味著定義具有給定類型的對象列表。)
進一步講,我們可以指定api和impl類型包。 默認情況下,可以從其他軟件包訪問Api型的。 另一方面,如果未明確將Impl類型的程序包指定為依賴項,則無法訪問它們。
在實踐中看起來如何? Scala中的一個非常粗糙的草圖:
package foo.user// Even without definition, each package has an implicit package object
// implementing a PackageModule trait ...
package object dao { // ... which is used here. The type of the val below is // List[PackageModule].override val moduleDependsOn = List(foo.security, foo.user.model) override val moduleType = ModuleType.API// FooLibs enum is defined in a top-level package or the build systemoverride val moduleLibraries = List(FooLibs.JPA)
}
重構
重構是日常活動; 但是,重構模塊通常是一項艱巨的任務,偶爾需要處理一次。 應該是這樣嗎? 如果將程序包擴展到模塊,則重構模塊將與移動和重命名程序包相同,另外還需要更新元數據。 這將比現在容易得多,我認為這將導致更好的總體設計。
建立系統
上面的內容顯然意味著構建系統需要做更多的工作–很難確定模塊列表,構建順序,要創建的工件列表等(順便說一句,如果要為一個程序包創建一個單獨的jar,可以也是元數據的一部分)。 此外,還需要進行一些驗證-對于循環依賴關系,或試圖以錯誤的方式限制可見性。
但是后來,人們做了比這更復雜的軟件
拼圖?
您可能會說,這與項目Jigsaw重疊,后者將在Java 9中(或不是)中出現。 但是,我認為Jigsaw的目標范圍不同:項目級別的模塊。 因此,一個拼圖模塊將是您的整個項目,而您將擁有多個(數十個)軟件包模塊。
名稱“模塊”在這里是重載的,也許名稱“迷你模塊”會更好,或者非常謙虛地“正確地打包”。
底線
我認為,當前定義構建模塊的方法太過困難且受限制。 另一方面,將包裝提升到模塊將非常輕便。 定義一個新模塊與創建一個新程序包相同–簡單得多。 第三方庫只能在需要的地方添加。 少一件事。 每個項目只有一個源樹。
同樣,這種方法將可擴展并可根據項目需求進行調整。 無需花費太多精力就可以定義細粒度模塊或粗粒度模塊。 甚至更好的是,為什么不創建兩個模塊呢?模塊可以嵌套并在另一個模塊之上構建。
現在…唯一的問題是實現并添加IDE支持;)
參考: 讓我們將包變成模塊系統! 來自我們的JCG合作伙伴 Adam Warski, 來自Adam Warski博客的Blog。
翻譯自: https://www.javacodegeeks.com/2012/11/lets-turn-packages-into-a-module-system.html