# 讓開發自動化: 斷言架構可靠性
_通過主動構建過程掌控架構_
您的軟件架構和您所期望的一樣嗎?當架構落實到代碼時,它并不總是 我們曾經互相討論并預想的那個。在本期的 [_讓開發自動化_](http://www.ibm.com/developerworks/cn/java/j-ap/)中,Paul Duvall 將演示如何通過使用 JUnit、JDepend 和 Ant 編寫 有關測試來發現架構偏差,從而做到在發生問題之前主動發現問題。
我在曾經從事的很多軟件開發項目中觀察到,軟件開發中一直存在這樣一種現象:_您實際擁有的架構往往與想象中的不同。_
通過分析代碼的度量報告,比如由 JDepend (參閱 [Resources](#resources))工具生成的報告, 您可以有效地判定代碼是否實現了確定的架構。有些團隊對代碼做反向設計,得到對應的 UML 圖表,也能夠達到上述效果, 還有一些團隊甚至在編程時使用 IDE 生成相同的工件 —— 即實時反向設計。可是, 所有的這些方法都還是 _反應式(reactive)_的。 您必須手工審視并分析報告或圖表,確定架構是否存在偏離,而有時這種 偏離可能很久之后才被發現。
設想每當某部分代碼與 _期望的_架構有所違背時,您就得到一個提示 —— 比如一個 Ant 構建腳本失敗 —— 如清單 1 所示:
##### 清單 1\. 違背架構導致構建失敗
```
...
BUILD FAILED
...
build.xml:35 Test ArchitecturalRulesTest failed
Total time: 20 seconds
```
## 關于本系列
作為開發人員,我們的工作就是為終端用戶實現過程自動化; 然而,很多開發人員卻忽略了將自己的開發過程自動化的機會。 為此,我編寫了 [_讓開發自動化_](http://www.ibm.com/developerworks/cn/java/j-ap/)這個系列的文章,專門探討軟件開發過程自動化的實際應用,并教您 _何時_以及 _如何_成功地應用自動化。
本文所提及的技術能夠使您通過實現構建自動化主動分析軟件架構。 除此之外,本文的示例還演示了如何基于規則觸發構建過程失敗,您可以使用 JDepend 的 API 和 JUnit 定義這些規則。
當然,最重要的是,通過構建自動化,您和您的團隊能夠在開發周期的 _前期_發現 源代碼與確定架構之間的偏離。 這就是我所說的掌控架構!
## 反應式地設置開發階段
圖 1 說明了在構建 Web 應用時一種常見的架構模式。 `presentation`層(代表一組相關的包)依賴于 `controller`層,`controller`層依賴于 `domain`和 `business`層,最后,`business`層依賴于 `data`和 `domain`層。
##### 圖 1\. 典型 web 應用的一個架構分層圖

至此,一切都很好,是嗎?但是,我很確定您以前遇到類似的情形,那些常見的最佳實踐規則會在日常的軟件開發中被遺忘。事實上,這一點很容易(也很快)就會發生。
舉例而言,圖 2 闡明了對該示例架構的一個微小違背;在這個例子里, `data`層至少調用一次 `business`層:
##### 圖 2\. Data 層正在調用 Business 層的一個對象,由此產生了架構違背

架構的微小變化產生的意外影響使代碼的修改變得更加困難。實際上,現在對一個代碼區域的修改 會要求其他很多區域的變動。舉例而言, 如果您清除或改變 business 層中類的一些方法, 則可能需要從 data 層中清除某些引用。當更多的違背發生時, 修改代碼就會更加困難。
若使用傳統的監控技術,比如查看 JDepend 或 Macker 報告(參閱 [參考資料](#resources)), 您能夠多快地發現對期望設計的偏離呢? 如果您和我一樣,想快速地生產出能夠工作的軟件, 那么越快發現影響交付速度的問題越好, 難道您不這么認為嗎?
* * *
## 使用簡單的 JDepend
JDepend 是最方便的幫助評定架構違背的工具之一。經過幾年的發展, 該開源工具能夠很好地與 Ant 和 Maven 集成; 此外,它對大量的 Java?API 提供支持,具有更細化的交互性。但是, 如同我已經指出的,它生成的報告本質上是被動的。根據您實際運行(并查看)它們的頻率, 架構違背可能直到很難矯正的時候才會被發覺。
## 傳入耦合(Afferent coupling)與傳出耦合(Efferent coupling)
JDepend 中,傳入耦合表示一些包的數量,這些包依賴某個經過分析的包。比如說,如果您正在使用日志框架或一個象 Struts 一樣的 Web 框架, 您會希望這些包具有高的傳入耦合, 因為整個代碼庫的很多包都依靠這些框架。 某包的傳出耦合與傳入耦合相反,是指某個經過分析的包所依賴的其他包的數量,也就是說,它具有的依賴包的數量。
### 斷言架構
要主動判定架構變動是否恰當,實際上就是研究某特定包的耦合。事實上,通過對軟件架構內關鍵包的傳入和傳出耦合進行監控, 以及觀察預期值的偏離,您能輕松地發現錯誤的修改。
舉例而言,在圖 2 所示的修改中, `data`層的新傳出耦合現在大于 0,因為該層目前要 直接與 business 層通信。當然,這種耦合是通過 `data`層中一個簡單的 `import`(及對引用類的使用)而引入的。幸運的是,很容易通過 JUnit、JDepend 的優秀 API 發現此類問題,還有,構建時需要一些技巧。
事實證明,在構建(如 Ant 或 Maven)上下文中,您能夠運行使用 JDepend API 的 JUnit 測試主動辨別耦合值的變化; 此外,如果這些變化不正確,您就可以使構建失敗。 這就實現了主動性,不是嗎?
第一步是創建一個 JUnit 測試并且對 JDepend 做相應配置,如清單 2 所示:
##### 清單 2\. 在 JUnit 中設置 JDepend
```
import junit.framework.TestCase;
import jdepend.framework.JavaPackage;
import jdepend.framework.JDepend;
public class ArchitecturalRulesTest extends TestCase {
private static final String DIRECTORY_TO_ANALYZE =
"C:/dev/project-sandbox/brewery/classes";
private JDepend jdepend;
private String dataLayer = "com.beer.business.data";
private String businessLayer = "com.beer.business.service";
private Collection dataLayerViolations = new ArrayList<String>();
public ArchitecturalRulesTest(String name) {
super(name);
}
protected void setUp()throws IOException {
jdepend = new JDepend();
jdepend.addDirectory(DIRECTORY_TO_ANALYZE);
// Calling the businessLayer from the dataLayer is a violation
dataLayerViolations.add(businessLayer);
}
```
清單 2 很長,我們總結以下幾個要點:
* 需要兩個 JDepend 類:`jdepend.framework.JavaPackage`和 `jdepend.framework.JDepend`。
* 待分析的源類位置由 `DIRECTORY_TO_ANALYZE`常量定義。JDepend 通過 調用 `JDepend.addDirectory`掃描該目錄,該操作通過一個 fixture 完成(即 `setUp()`方法)。
* 要分析的包由 “Layer” `String`定義。
* `dataLayerViolations``Collection`添加了 `businessLayer``String`(表示一個包)來指明 這是對期望架構的違背。
按照這四個要點,我已經有效地設置了 JDepend,以針對特定代碼庫發揮其魔力。 現在,我要設定一些精確的邏輯以說明耦合值的變化。
清單 3 中的 `testDataLayer()`測試用例是架構斷言的核心。 該方法可判定是否存在對 `dataLayer`的任何違背 —— 如果 `isLayeringValid()`方法(在下面的 [清單 4](#listing4)中定義)返回 `false`,測試用例 就被認為失敗,也意味著必然存在一處架構違背。
##### 清單 3\. 使用 JDepend 測試架構違背
```
public void testDataLayer() {
if (!isLayeringValid(dataLayer, dataLayerViolations)) {
fail("Dependency Constraint failed in Data Layer");
}
}
```
清單 3 中測試用例所調用的方法如清單 4 所示:
##### 清單 4\. 循環查找每個包的傳入耦合
`isLayeringValid()`方法的目的確定 [清單 2](#listing2)中 `DIRECTORY_TO_ANALYZE`目錄內所有包的傳入耦合。 您可以在清單 4 底部看到,該方法遵守 `isEfferentsValid()`方法,如清單 5 所示。
這里,如果 `isEfferentsValid()`方法發現某個包不符合指定的包依賴關系(由于從一個包到另一個包的傳出耦合大于 0),則使用 [清單 2](#listing2)中的 `dataLayerViolations`集合將該包標記為一個架構違背。這將間接導致 `testDataLayer()`測試用例(如 [清單 3](#listing3)所示)失敗。
##### 清單 5\. 判定包依賴關系違背
```
private boolean isLayeringValid(String layer, Collection rules) {
boolean rulesCorrect = true;
Collection packages = jdepend.analyze();
Iterator itor = packages.iterator();
JavaPackage jPackage = null;
String analyzedPackageName = null;
while (itor.hasNext()) {
jPackage = (JavaPackage) itor.next();
analyzedPackageName = jPackage.getName();
Iterator afferentItor = jPackage.getAfferents().iterator();
String afferentPackageName = null;
while (afferentItor.hasNext()) {
JavaPackage afferentPackage = (JavaPackage) afferentItor.next();
afferentPackageName = afferentPackage.getName();
}
rulesCorrect = isEfferentsValid
(layer, rules, rulesCorrect, jPackage, analyzedPackageName);
}
return rulesCorrect;
}
```
正如您所看到的,清單 2 到 5 實際上都是掃描一系列包以確定耦合變化; 如果耦合發生了變化,失敗條件被觸發,因此 JUnit 報告測試失敗。 要讓我說的話,這真是令人印象深刻!
### 別忘了自動運行測試
一旦您結合使用 JUnit 和 JDepend 編寫好基于約束的測試后, 您就能夠用諸如 Ant 或 Maven 這樣的工具把它作為構建過程的一部分運行。 舉例而言,清單 6 闡述了用 Ant 運行一系列此類測試。 `test.dependency.dir`屬性 映射到 root/src/test/java/dependency 目錄,其中包含了一些神奇的架構驗證程序。
##### 清單 6\. 運行依賴性約束測試的 Ant 腳本
```
<target name="run-tests" depends="compile-tests">
<mkdir dir="${logs.junit.dir}" />
<junit fork="yes" haltonfailure="true"dir="${basedir}" printsummary="yes">
<classpath refid="test.class.path" />
<classpath refid="project.class.path"/>
<formatter type="plain" usefile="true" />
<formatter type="xml" usefile="true" />
<batchtest fork="yes" todir="${logs.junit.dir}">
<fileset dir="${test.dependency.dir}">
<patternset refid="test.sources.pattern"/>
</fileset>
</batchtest>
</junit>
</target>
```
要使 JUnit 測試成功執行,JDepend JAR 必須出現在 Ant 的類路徑中。 `haltonfailure` 屬性被設為 true,以便讓構建過程在測試失敗時停止。
* * *
## 閾值驅動的架構
我已經指出,使用被動的方法維持架構需要付出大量的努力, 另外,我希望我已經使您相信,開發過程中很容易發生架構違背。 通過將架構測試作為構建過程的一部分執行, 您能夠使這種檢查自動化并且能夠重復執行。圖 3 顯示了在運行 Ant 后顯示構建失敗,這樣不是很好嗎?我甚至根本不需要再去看 JDepend 報告了。
##### 圖 3\. 架構違背引起的構建失敗

這種主動監控的優勢在于,你可以在發現架構分層問題后馬上解決它。 問題解決得越迅速,越有助于降低風險 —— 更不用提代價了。 本質上,您的團隊不會因此收到干擾,并能夠繼續工作,實現快速發布可用軟件的目標。
* * *
## 針對架構的自動化
## JDepend 還有哪些魔力?
存在多種方法通過 JDepend 添加主動檢查。實際上,JDepend 建議使用其 `DependencyConstraint`類。盡管使用 DependencyConstraint 非常簡單,但我還是不選擇它,因為 它只具有使用 API 執行架構規則這么一種途徑,而且不能可靠地根據我的需求工作。還有其他一些工具支持包依賴關系遵從性; 可參閱 [參考資料](#resources)以了解更多細節。
現在您能夠用自己的構建過程主動發現與期望架構的設計違背了。 此外,我已向您展示了幾個可能的示例之一 —— 您一定能夠獲得創造性的方法并分析類似 _Instability_包這樣的度量,從而便于判定架構的整體健壯性。
我所介紹的這個方法是一種簡單的方式,可以減少 為判定架構遵從性而不斷反向設計代碼并分析圖表的需求。 如果您在使用持續集成系統(Continuous Integration system), 您可將這些測試用作一個安全網絡,確保檢查版本控制系統的代碼傳遞這些架構規則 —— _每當進行了一次改動時_。如果您改變了架構, 只要改變您的 JUnit 測試規則,就可確保 您的團隊遵守了項目標準。這就是我所稱的使用主動方式斷言架構可靠性。
- 讓開發自動化
- 讓開發自動化: 部署自動化模式,第 2 部分
- 讓開發自動化: 部署自動化模式,第 1 部分
- 讓開發自動化: 使用基于向導的安裝程序
- 讓開發自動化: 針對廣大開發人員的并行開發
- 讓開發自動化: 實現自動化數據庫遷移
- 讓開發自動化: 持續重構
- 讓開發自動化: 文檔化一鍵通
- 讓開發自動化: 利用 Ivy 管理依賴項
- 讓開發自動化: 自動負載測試
- 讓開發自動化: 使用自動化加速部署
- 讓開發自動化: 持續集成反模式
- 讓開發自動化: 斷言架構可靠性
- 讓開發自動化: 持續測試
- 讓開發自動化: 用 Eclipse 插件提高代碼質量
- 讓開發自動化: 除掉構建腳本中的氣味
- 讓開發自動化: 選擇持續集成服務器
- 讓開發自動化: 持續檢查