天天看點

《軟體架構設計》一書目錄

第一部分軟體架構概念與思想篇 1

第1章解析軟體架構概念 3

1.1軟體架構概念的分類 3

1.1.1組成派 4

1.1.2決策派 5

1.2軟體架構概念大觀 5

1.2.1Booch、Rumbaugh和Jacobson的定義 5

1.2.2Woods的觀點 6

1.2.3Garlan和Shaw的定義 6

1.2.4Perry和Wolf的定義 6

1.2.5Boehm的定義 6

1.2.6IEEE的定義 6

1.2.7Bass的定義 6

1.3軟體架構關注分割與互動 7

1.4軟體架構是一系列有層次性的決策 8

1.5PMTool案例:領會軟體架構概念 10

1.5.1案例故事 10

1.5.2軟體架構概念的展現 12

1.5.3重要結論 14

1.6總結與強調 14

第2章子系統、架構與架構 15

2.1子系統和架構在架構設計中的地位 16

2.1.1關注點分離之道 16

2.1.2子系統和架構在架構設計中的地位 17

2.2子系統與軟體架構 19

2.2.1不同粒度的軟體單元 20

2.2.2子系統也有架構 21

2.2.3子系統不同,架構不同 21

2.2.4不同實踐者眼中的粒度 23

2.3架構與軟體架構 23

2.3.1架構的概念 23

2.3.2架構和架構的差別 24

2.3.3架構和架構的聯系 25

2.3.4架構也有架構 26

2.4超越概念:立足實踐了解架構 26

2.4.1了解架構 26

2.4.2回到實踐 28

2.5專題:架構技術 29

2.5.1架構vs.類庫 29

2.5.2架構的分類 30

2.5.3架構的開發過程 32

2.5.4如何實作架構中的擴充點 33

2.6總結與強調 36

第3章軟體架構的作用 37

3.1充分發揮軟體架構的作用 37

3.2軟體架構對新産品開發的作用 38

3.3軟體架構對軟體産品線開發的作用 40

3.4軟體架構對軟體維護的作用 42

3.5軟體架構重構 42

3.6總結與強調 43

第二部分軟體架構設計方法與過程篇 45

第4章軟體架構視圖 47

4.1呼喚軟體架構視圖 47

4.1.1辦公室裡的争論 48

4.1.2呼喚軟體架構視圖 48

4.2軟體架構為誰而設計 49

4.2.1為使用者而設計 49

4.2.2為客戶而設計 50

4.2.3為開發人員而設計 50

4.2.4為管理人員而設計 51

4.2.5總結 51

4.3引入軟體架構視圖 52

4.3.1生活中的“視圖”運用 53

4.3.2什麼是軟體架構視圖 54

4.3.3多組涉衆,多個視圖 54

4.4實踐指南:邏輯架構與實體架構 55

4.4.1邏輯架構 56

4.4.2實體架構 57

4.4.3從邏輯架構和實體架構到設計實作 58

4.5裝置調試系統案例:領會邏輯架構和實體架構 59

4.5.1裝置調試系統案例簡介 59

4.5.2邏輯架構設計 59

4.5.3實體架構設計 61

4.6總結與強調 62

第5章架構設計的5視圖法 63

5.1架構設計的5視圖法 64

5.2實踐中的5視圖方法 66

5.3辦公室裡的争論:回顧與落實 67

5.4案例:再談裝置調試系統 67

5.4.1根據需求決定引入哪些架構視圖 68

5.4.2開發架構設計 68

5.4.3運作架構設計 69

5.5總結與強調 71

第6章從概念性架構到實際架構 73

6.1概念性架構 73

6.2實際架構 77

6.3從概念性架構到實際架構 78

6.4網絡管理系統案例:從分層架構開始 78

6.4.1構思:概念性架構設計 78

6.4.2深入:實際架構設計 81

6.5總結與強調 82

第7章如何進行成功的架構設計 83

7.1何謂成功的軟體架構設計 83

7.2探究成功架構設計的關鍵要素 84

7.2.1是否遺漏了至關重要的非功能需求 84

7.2.2能否馴服數量巨大且頻繁變化的需求 86

7.2.3能否從容設計軟體架構的不同方面 86

7.2.4是否及早驗證架構方案并做出了調整 87

7.3制定軟體架構設計政策 87

7.3.1政策一:全面認識需求 88

7.3.2政策二:關鍵需求決定架構 89

7.3.3政策三:多視圖探尋架構 89

7.3.4政策四:盡早驗證架構 90

7.4總結與強調 90

第8章軟體架構要設計到什麼程度 93

8.1軟體架構要設計到什麼程度 94

8.1.1分而治之的兩種方式 94

8.1.2架構設計與詳細設計 96

8.1.3軟體架構是團隊開發的基礎 96

8.1.4架構設計要進行到什麼程度 98

8.2高來高去式架構設計的症狀 98

8.2.1缺失重要架構視圖 99

8.2.2淺嘗辄止、不夠深入 100

8.2.3名不副實的分層架構 101

8.3如何克服高來高去症 101

8.4網絡管理系統案例:如何将架構設計落到實處 102

8.4.1網管産品線的概念性架構 102

8.4.2識别每一層中的功能子產品 102

8.4.3明确各層之間的互動接口 103

8.4.4明确各層之間的互動機制 104

8.4.5案例小結 105

8.5總結與強調 105

第9章軟體架構設計過程 107

9.1打造有效的架構設計過程 107

9.1.1一般的軟體過程 107

9.1.2架構師自己的架構設計過程 109

9.2軟體架構設計過程解析 111

9.2.1架構設計政策應成為一等公民 111

9.2.2架構設計過程中的工作産品 112

9.3總結與強調 114

第10章需求分析 115

10.1軟體需求基礎 116

10.1.1什麼是軟體需求 116

10.1.2需求捕獲vs.需求分析vs.系統分析 116

10.1.3需求捕獲及其工作成果 118

10.1.4需求分析及其工作成果 118

10.1.5系統分析及其工作成果 119

10.2需求分析在軟體過程中所處的位置 120

10.2.1概念化階段所做的工作 120

10.2.2需求分析所處的位置 122

10.3架構師必須掌握的需求知識 123

10.3.1軟體需求的類型 123

10.3.2各類需求對架構設計的不同影響 127

10.3.3超市系統案例:領會需求類型的不同影響 129

10.3.4各類需求的“易變更性”不同 130

10.3.5品質屬性需求與需求折衷 132

10.4PMTool實戰:需求分析 135

10.4.1上遊活動:确定項目願景 135

10.4.2第1步:從業務目标到特性清單 135

10.4.3第2步:從特性清單到用例圖 136

10.4.4第3步:從用例圖到用例規約 138

10.4.5需求啟發與需求驗證 139

10.4.6最終成果:《軟體需求規格說明書》 140

10.5總結與強調 141

第11章專題:用例技術及應用 143

11.1用例圖vs.用例簡述vs.用例規約vs.用例實作 143

11.2儲蓄系統案例:需求變化對用例的影響 148

11.3用例技術應用指南 150

11.4用例與需求捕獲 152

11.5用例與需求分析 153

11.6用例與《軟體需求規格說明書》 154

11.7總結與強調 155

第12章領域模組化 157

12.1領域模型基礎知識 157

12.1.1什麼是領域模型 158

12.1.2領域模型相關的UML圖 158

12.2領域模組化在軟體過程中所處的位置 159

12.2.1領域模組化的必要性:從需求分析的兩個典型困難說起 159

12.2.2領域模組化與需求分析的關系 161

12.2.3領域模組化所處的位置 162

12.3領域模型對軟體架構的重要作用 163

12.3.1配置管理工具案例:探索複雜問題、固化領域知識 163

12.3.2人事管理系統案例:決定功能範圍、影響可擴充性 165

12.3.3線上拍賣系統案例:提供交流基礎、促進有效溝通 168

12.4領域模型vs.文字說明 170

12.5PMTool實戰:建立項目管理的領域模型 171

12.5.1領域模組化實錄(1) 171

12.5.2領域模組化實錄(2) 174

12.6總結與強調 176

第13章确定對軟體架構關鍵的需求 177

13.1虛拟高峰論壇:窮兵黩武還是擇戰而鬥 177

13.1.1需求是任何促成設計決策的因素 178

13.1.2很少有開發者能奢侈地擁有一個穩定的需求集 178

13.1.3關鍵性的第一步是縮小範圍 178

13.1.4要擇戰而鬥 178

13.1.5功能、品質和商業需求的某個集合塑造了構架 179

13.2關鍵需求決定架構 179

13.2.1實踐中的常見問題 179

13.2.2關鍵需求決定架構 181

13.3确定關鍵需求在軟體過程中所處的位置 182

13.3.1對架構關鍵的需求vs.需求優先級 182

13.3.2關鍵需求對後續活動的影響 183

13.4什麼是對軟體架構關鍵的需求 184

13.4.1關鍵的功能需求 184

13.4.2關鍵的品質屬性需求 185

13.4.3關鍵的商業需求 186

13.5如何确定對軟體架構關鍵的需求 187

13.5.1全面整理需求 188

13.5.2分析限制性需求 188

13.5.3确定關鍵功能需求 189

13.5.4确定關鍵品質屬性需求 190

13.6PMTool實戰:确定關鍵需求 190

13.7總結與強調 191

第14章概念性架構設計 193

14.1概念性架構設計的步驟 194

14.2魯棒性分析 195

14.2.1分析和設計之間的鴻溝 195

14.2.2魯棒圖簡介 196

14.2.3從用例到魯棒圖 197

14.3運用架構模式 198

14.3.1架構模式簡介 198

14.3.2架構模式的經典分類 199

14.3.3架構模式的現代分類 200

14.3.4分層 201

14.3.5MVC 201

14.3.6微核心 202

14.3.7基于元模型的架構 203

14.3.8管道—過濾器 204

14.4PMTool實戰:概念性架構設計 204

14.4.1進行魯棒性分析 204

14.4.2引入架構模式 206

14.4.3品質屬性分析 207

14.4.4設計結果 207

14.5總結與強調 208

第15章品質屬性分析 209

15.1品質屬性需求基礎 210

15.2品質屬性分析的位置 211

15.3利用“屬性—場景—決策”表設計架構決策 211

15.3.1概述 211

15.3.2“屬性—場景—決策”表方法 212

15.3.3題外話:《需求文檔》如何定義品質屬性需求 214

15.4PMTool實戰:可擴充性設計 214

15.5總結與強調 215

第16章細化架構設計 217

16.1架構細化在軟體過程中所處的位置 218

16.1.1我們走到哪了 218

16.1.2運用基于5視圖方法進行架構細化 219

16.2設計邏輯架構 220

16.2.1概述 220

16.2.2識别通用機制 220

16.3設計開發架構 223

16.3.1概述 223

16.3.2分層和分區 223

16.4設計資料架構 226

16.4.1概述 226

16.4.2如何将OO模型映射為資料模型 227

16.5設計運作架構 229

16.5.1概述 229

16.5.2運用主動類規劃并發 230

16.5.3應用協定的設計 234

16.6設計實體架構 234

16.6.1概述 234

16.7注意滿足所有限制性軟體需求 235

16.8PMTool實戰:細化架構設計 236

16.9總結與強調 239

第17章實作并驗證軟體架構 241

17.1基礎知識 242

17.1.1原型技術及分類 242

17.1.2驗證架構的兩種方法 245

17.2實作并驗證軟體架構的具體做法 245

17.3總結與強調 247

第三部分程式員成長篇 249

第18章MIME編碼類案例:從面向過程到面向對象 251

18.1設計目标 251

18.2MIME編碼基礎知識 252

18.3MIME編碼類的設計過程 252

18.3.1面向過程的設計方案 252

18.3.2轉向面向對象設計 254

18.3.3面向對象設計方案的确定 257

18.3.4TemplateMethod和Strategy模式的對比 260

第19章突破OOP思維:繼承在OOD中的應用 261

19.1從一則禅師語錄說起 261

19.1.1見繼承是繼承——程式員境界 262

19.1.2見繼承不是繼承——成長境界 262

19.1.3見繼承隻是繼承——設計師境界 262

19.2從OOD層面認識繼承 262

19.3針對接口程式設計——隔離變化 263

19.3.1相關理論 263

19.3.2針對接口程式設計舉例——用于架構設計 263

19.3.3針對接口程式設計舉例——用于類設計 265

19.4混入類——更好的重用性 266

19.4.1相關理論 266

19.4.2混入類舉例 266

19.5基于角色的設計——使用角色組裝協作 267

19.5.1相關理論 267

19.5.2基于角色的設計舉例 268

第20章細微見真章:耦合其實并不空洞 269

20.1順序耦合性簡介 269

20.2案例研究:順序耦合性Bug一例 269

20.2.1項目簡介 270

20.2.2新的需求 270

20.2.3發現順序耦合性Bug 271

20.2.4跟蹤調試 271

20.2.5分析原因 273

20.2.6解決政策 273

20.2.7運用重構的“ExtractMethod”成例 273

20.2.8運用重構的“HideMethod”成例 274

20.2.9運用重構的“IntroduceParameterObject”成例 274

20.2.10其他改進 274

第21章靈活設計:從理論到實踐 277

21.1換個角度考察依賴 278

21.1.1依賴的概念 278

21.1.2從會不會造成“實際危害”的角度考察依賴 278

21.2良性依賴原則 278

21.2.1依賴是不可避免的 278

21.2.2重要的是如何務實地應付變化 279

21.3案例:需求改變引起良性依賴變成惡性依賴 279

21.4案例:隔離第三方SDK可能造成的沖擊 281

21.5案例:對具體類的良性依賴 283

21.6總結:如何處理好依賴關系 285

第22章基于角色的設計:從理論到實踐 287

22.1基于角色的設計理論 288

22.2基于角色的設計與團隊開發 288

22.3基于角色的設計實踐 289

22.4基于角色的設計案例 291

22.4.1項目簡介 291

22.4.2通過基于角色的設計組織子系統之間的協作 291

22.4.3通過基于角色的設計組織同一子系統内不同子產品之間的協作 292

22.5基于角色的設計與面向對象分析 293

第23章超越設計模式:了解和運用更多模式 295

23.1關于模式的兩個問題 295

23.2模式的正交分類法 296

23.2.1正交思維 296

23.2.2正交思維用于模式分類 297

23.3專攻性能:性能模式簡介 299

23.4模型驅動開發的方方面面:MDD模式簡介 301

23.5總結:擁抱模式 302

第24章如此輕松:立足圖論學UML 303

24.1管窺UML中的OO思想 304

24.1.1一道筆試題的故事 304

24.1.2UML背後的思想 305

24.2圖的定義與UML應用 306

24.2.1圖的定義 306

24.2.2圖的定義的UML應用——UML的圖論觀點 307

24.2.3圖的定義的UML應用——關聯類文法的了解 308

24.2.4圖的定義的UML應用——說說序列圖 309

24.3有向邊與UML應用 310

24.3.1有向邊 310

24.3.2有向邊的UML應用——依賴關系 310

24.3.3有向邊的UML應用——泛化、實作和關聯的依賴思想 312

24.3.4有向邊的UML應用——一個例子 312

24.4着色頂點與UML應用 313

24.4.1着色頂點 313

24.4.2着色頂點的UML應用——通過顔色為圖元分類 314

24.4.3着色頂點的UML應用——UML彩色模組化方法介紹 315

24.5着色邊與UML應用 317

24.6圖的同構與UML應用 317

24.6.1圖的同構 317

24.6.2圖的同構的UML應用——UML風格 318

第25章了解軟體過程:解析RUP核心概念 321

25.1架構師必須了解軟體過程 321

25.1.1架構師的工作職責 321

25.1.2架構師必須了解軟體過程 322

25.2RUP實踐中的常見問題 322

25.3RUP核心概念解析 323

25.3.1一圖勝千言 323

25.3.2角色執行活動,活動生産工件 323

25.3.3階段和疊代:提供不同級别的決策時機 324

25.3.4配置和變更管理支援疊代式的基于基線的開發 326

25.3.5釋出是什麼,釋出不是什麼 327

第26章海闊憑魚躍:通盤了解軟體工程 329

26.1什麼是軟體工程概念模型 329

26.2一個精簡的軟體工程概念模型 329

26.3一個細化的軟體工程概念模型 330

26.3.1模型概述 331

26.3.2方法論 331

26.3.3過程 331

26.3.4目标 332

26.3.5項目 332

26.3.6其他 333

26.4軟體工程概念模型的具體應用 333

26.4.1搞清楚Agile是過程還是方法論 333

26.4.2為CMM定位 334

26.4.3了解RUP定制 335

26.5總結:軟體工程概念模型的啟示 335

26.5.1軟體工程,一門實踐的科學 335

26.5.2軟體過程,合适的才是最好的 336

26.5.3對個人的啟示 336

26.5.4呼喚高層次人才 336

參考文獻 337

如何設計架構?

Part 1 層

  層(layer)這個概念在計算機領域是非常了不得的一個概念。計算機本身就展現了一種層的概念:系統調用層、裝置驅動層、作業系統層、CPU指令集。每個層都負責自己的職責。網絡同樣也是層的概念,最著名的OSI的七層協定。

  層到了軟體領域也一樣好用。為什麼呢?我們看看使用層技術有什麼好處:

  ● 你使用層,但是不需要去了解層的實作細節。

  ● 可以使用另一種技術來改變基礎的層,而不會影響上面的層的應用。

  ● 可以減少不同層之間的依賴。

  ● 容易制定出層标準。

  ● 底下的層可以用來建立頂上的層的多項服務。 當然,層也有弱點:

  ● 層不可能封裝所有的功能,一旦有功能變動,勢必要波及所有的層。

  ● 效率降低。

  當然,層最難的一個問題還是各個層都有些什麼,以及要承擔何種責任。

典型的三層結構

  三層結構估計大家都很熟悉了。就是表示(presentation)層, 領域(domain)層, 以及基礎架構(infrastructure)層。

  表示層邏輯主要處理使用者和軟體的互動。現在最流行的莫過于視窗圖形界面(wimp)和基于html的界面了。表示層的主要職責就是為使用者提供資訊,以及把使用者的指令翻譯。傳送給業務層和基礎架構層。 基礎架構層邏輯包括處理和其他系統的通信,代表系統執行任務。例如資料庫系統互動,和其他應用系統的互動等。大多數的資訊系統,這個層的最大的邏輯就是存儲持久資料。

  還有一個就是領域層邏輯,有時也被叫做業務邏輯。它包括輸入和存儲資料的計算。驗證表示層來的資料,根據表示層的指令指派一個基礎架構層邏輯。

  領域邏輯中,人們總是搞不清楚什麼事領域邏輯,什麼是其它邏輯。例如,一個銷售系統中有這樣一個邏輯:如果本月銷售量比上個月增長10%,就要用紅色标記。要實作這個功能,你可能會把邏輯放在表示層中,比較兩個月的數字,如果超出10%,就标記為紅色。

  這樣做,你就把領域邏輯放到了表示層中了。要分離這兩個層,你應該現在領域層中提供一個方法,用來比較銷售數字的增長。這個方法比較兩個月的數字,并傳回boolean類型。表示層則簡單的調用該方法,如果傳回true,則标記為紅色。

例子

  層技術不存在說永恒的技巧。如何使用都要看具體的情況才能夠決定,下面我就列出了三個例子:

  例子1:一個電子商務系統。要求能夠同時處理大量使用者的請求,使用者的範圍遍及全球,而且數字還在不斷增長。但是領域邏輯很簡單,無非是訂單的處理,以及和庫存系統的連接配接部分。這就要求我們1、表示層要友好,能夠适應最廣泛的使用者,是以采用html技術;2、支援分布式的處理,以勝任同時幾千的通路;3、考慮未來的更新。

  例子2:一個租借系統。系統的使用者少的多,但是領域邏輯很複雜。這就要求我們制作一個領域邏輯非常複雜的系統,另外,還要給他們的使用者提供一個友善的輸入界面。這樣,wimp是一個不錯的選擇。

  例子3:簡單的系統。非常簡單,使用者少、邏輯少。但是也不是沒有問題,簡單意味着要快速傳遞,并且還要充分考慮日後的更新。因為需求在不斷的增加之中。

何時分層

  這樣的三個例子,就要求我們不能夠一概而論的解決問題,而是應該針對問題的具體情況制定具體的解決方法。這三個例子比較典型。

  第二個例子中,可能需要嚴格的分成三個層次,而且可能還要加上另外的中介(mediating)層。例3則不需要,如果你要做的僅是檢視資料,那僅需要幾個server頁面來放置所有的邏輯就可以了。

  我一般會把表示層和領域層/基礎架構層分開。除非領域層/基礎架構層非常的簡單,而我又可以使用工具來輕易的綁定這些層。這種兩層架構的最好的例子就是在VB、PB的環境中,很容易就可以建構出一個基于SQL資料庫的windows界面的系統。這樣的表示層和基礎架構層非常的一緻,但是一旦驗證和計算變得複雜起來,這種方式就存在先天缺陷了。

  很多時候,領域層和基礎架構層看起來非常類似,這時候,其實是可以把它們放在一起的。可是,當領域層的業務邏輯和基礎架構層的組織方式開始不同的時候,你就需要分開二者。

更多的層模式

  三層的架構是最為通用的,尤其是對IS系統。其它的架構也有,但是并不适用于任何情況。

  第一種是Brown model [Brown et al]。它有五個層:表示層(Presentation),控制/中介層(Controller/Mediator),領域層(Domain), 資料映射層(Data Mapping), 和資料源層(Data Source)。它其實就是在三層架構種增加了兩個中間層。控制/中介層位于表示層和領域層之間,資料映射層位于領域層和基礎架構層之間。

  表示層和領域層的中介層,我們通常稱之為表示-領域中介層,是一個常用的分層方法,通常針對一些非可視的控件。例如為特定的表示層組織資訊格式,在不同的視窗間導航,處理交易邊界,提供Server的facade接口(具體實作原理見設計模式)。最大的危險就是,一些領域邏輯被放到這個層裡,影響到其它的表示層。

  我常常發現把行為配置設定給表示層是有好處的。這可以簡化問題。但表示層模型會比較複雜,是以,把這些行為放到非可視化的對象中,并提取出一個表示-領域中介層還是值得的。

  Brown ISA

  表示層 表示層

  控制/中介層 表示-領域中介層

  領域層 領域層

  資料映射層 資料庫互動模式中的Database Mapper

  資料源層 基礎架構層

  領域層和基礎架構層之間的中介層屬于本書中提到的Database Mapper模式,是三種領域層到資料連接配接的辦法之一。和表示-領域中介層一眼,有時候有用,但不是所有時候都有用。

  還有一個好的分層架構是J2EE的架構,這方面的讨論可以見『J2EE核心模式』一書。他的分層是客戶層(Client),表示層(Presentation),業務層(Business ),整合層(Integration),資源層(Resource)。差别如下圖:

  J2EE核心 ISA

  客戶層 運作在客戶機上的表示層

  表示層 運作在伺服器上的表示層

  業務層 領域層

  整合層 基礎架構層

  資源層 基礎架構層通信的外部資料

  微軟的DNA架構定義了三個層:表示層(presentation),業務層(business),和資料存儲層(data access),這和我的架構相似,但是在資料的傳遞方式上還有很大的不同。在微軟的DNA中,各層的操作都基于資料存儲層傳出的SQL查詢結果集。這樣的話,實際上是增加了表示層和業務層同資料存儲層之間的耦合度。 DNA的記錄集在層之間的動作類似于Data Transfer Object。

Part 2 組織領域邏輯

  要組織基于層的系統,首要的是如何組織領域邏輯。領域邏輯的組織有好幾種模式。但其中最重要的莫過于兩種方法:Transation Script和Domain Model。標明了其中的一種,其它的都容易決定。不過,這兩者之間并沒有一條明顯的分界線。是以如何選取也是門大學問。一般來說,我們認為領域邏輯比較複雜的系統可以采用Domain Model。

  Transation Script就是對表示層使用者輸入的處理程式。包括驗證和計算,存儲,調用其它系統的操作,把資料回傳給表示層。使用者的一個動作表示一個程式,這個程式可以是script,也可以是transation,也可以是幾個子程式。在例子1中,檢驗,在購物車中增加一本書,顯示遞送狀态,都可以是一個Transation Script。

  Domain Model是要建立對應領域名詞的模型,例如例1中的書、購物車等。檢驗、計算等處理都放到領域模型中。

  Transation Script屬于結構性思維,Domain Model屬于OO思維。Domain Model比較難使用,一旦習慣,你能夠組織更複雜的邏輯,你的思想會更OO。到時候,即使是小的系統,你也會自然的使用Domain Model了。

  但如何抉擇呢?如果邏輯複雜,那肯定用Domain Model:如果隻需要存取資料庫,那Transation Script會好一些。但是需求是在不斷進化的,你很難保證以後的需求還會如此簡單。如果你的團隊不善于使用Domain Model,那你需要權衡一下投入産出比。另外,即使是Transation Script,也可以做到把邏輯和基礎架構分開,你可以使用Gateway。

  對例2,毫無疑問要使用Domain Model。對例1就需要權衡了。而對于例3,你很難說它将來會不會像例2那樣,你現在可以使用Transation Script,但未來你可能要使用Domain Model。是以說,架構的決策是至關緊要的。

  除了這兩種模式,還有其它中庸的模式。Use Case Controller就是處于兩者之間。隻有和單個的用例相關的業務邏輯才放到對象中。是以大緻上他們還是在使用Transation Script,而Domain Model隻是Database Gateway的一組集合而已。我不太用這種模式。

  Table Module是另一個中庸模式。很多的GUI環境依托于SQL查詢的傳回結果。你可以建立記憶體中的對象,來把GUI和資料庫分開來。為每個表寫一個子產品,是以每一行都需要關鍵字變量來識别每一個執行個體。

  Table Module适用于很多的元件建構于一個通用關系型資料庫之上,而且領域邏輯不太複雜的情況。Microsoft COM 環境,以及它的帶ADO.NET的.NET環境都适合使用這種模式。而對于Java,就不太适用了。

  領域邏輯的一個問題是領域對象非常的臃腫。因為對象的行為太多了,類也就太大了。它必須是一個超集。這就要考慮哪些行為是通用的,哪些不是,可以由其它的類來處理,可能是Use Case Controller,也可能是表示層。

  還有一個問題,複制。他會導緻複雜和不一緻。這比臃腫的危害更大。是以,甯可臃腫,也不要複制。等到臃腫為害時再處理它吧。

選擇一個地方運作領域邏輯

  我們的精力集中在邏輯層上。領域邏輯要麼運作在Client上,要麼運作在Server上。

  比較簡單的做法是全部集中在Server上。這樣你需要使用html的前端以及web server。這樣做的好處是更新和維護都非常的簡單,你也不用考慮桌面平台和Server的同步問題,也不用考慮桌面平台的其它軟體的相容問題。

  運作在Client适合于要求快速反應和沒有聯網的情況。在Server端的邏輯,使用者的一個再小的請求,也需要資訊從Client到Server繞一圈。反應的速度必然慢。再說,網絡的覆寫程度也不是說達到了100%。

  對于各個層來說,又是怎麼樣的呢?

  基礎架構層:一般都是在Server啦,不過有時候也會把資料複制到合适的高性能桌面機,但這是就要考慮同步的問題了。

  表示層在何處運作取決于使用者界面的設計。一個Windows界面隻能在Client運作。而一個Web界面就是在Server運作。也有特别的例子,在桌面機上運作web server的,例如X Server。但這種情況少的多。

  在例1中,沒有更多的選擇了,隻能選在Server端。是以你的每一個bit都會繞一個大圈子。為了提高效率,盡量使用一些純html腳本。

  人們選用Windows界面的原因主要就是需要執行一些非常複雜的任務,需要一個合适的應用程式,而web GUI則無法勝任。這就是例2的做法。不過,人們應該會漸漸适應web GUI,而web GUI的功能也會越來越強大。

  剩下的是領域邏輯。你可以全部放在Server,也可以全部放在Client,或是兩邊都放。

  如果是在Client端,你可以考慮全部邏輯都放在Client端,這樣至少保證所有的邏輯都在一個地方。而把web server移至Client,是可以解決沒有聯網的問題,但對反應時間不會有多大的幫助。你還是可以把邏輯和表示層分離開來。當然,你需要額外的更新和維護的工作。

  在Client和Server端都具有邏輯并不是一個好的處理辦法。但是對于那些僅有一些領域邏輯的情況是适用的。有一個小竅門,把那些和系統的其它部分沒有聯系的邏輯封裝起來。 領域邏輯的接口

  你的Server上有一些領域邏輯,要和Client通信,你應該有什麼樣的接口呢?要麼是一個http接口,要麼是一個OO接口。

  http接口适用于web browser,就是說你要選擇一個html的表示層。最近的新技術就是web service,通過基于http、特别是XML進行通信。XML有幾個好處:通信量大,結構好,僅需一次的回路。這樣遠端調用的的開銷就小了。同時,XML還是一個标準,支援平台異構。XML又是基于文本的,能夠通過防火牆。

  雖然XML有那麼多的好處,不過一個OO的接口還是有它的價值的。hhtp的接口不明顯,不容易看清楚資料是如何處理的。而OO的接口的方法帶有變量和名字,容易看出處理的過程。當然,它無法通過防火牆,但可以提供安全和事務之類的控制。

  最好的還是取二者所長。OO接口在下,http接口在上。但這樣做就會使得實作機制非常的複雜。

Part 3 組織web Server

  很多使用html方式的人,并不能真正了解這種方式的優點。我們有各種各樣好用的工具,但是卻搞到讓程式難以維護。

  在web server上組織程式的方式大緻可以分為兩種:腳本和server page。

  腳本方式就是一個程式,用函數和方法來處理http調用。例如CGI腳本和java servlet。它和普通的程式并沒有什麼兩樣。它從web頁面上獲得html string形态的資料,有時候還要做一些表達式比對,這正是perl能夠成為CGI腳本的常用語言的原因。而java servelet則是把這種分析留給程式員,但它允許程式員通過關鍵字接口來通路資訊,這樣就會少一些表達式的判斷。這種格式的web server輸出是另一種html string,稱為response,可以通過流資料來操作。

  糟糕的是流資料是非常麻煩的,是以就導緻了server page的産生,例如PHP,ASP,JSP。

  server page的方式适合回應(response)的處理比較簡單的情況。例如“顯示歌曲的明細”,但是你的決策取決于輸入的時候,就會比較雜亂。例如“通俗和搖滾的顯示格式不同”。

  腳步擅長于處理使用者互動,server page擅長于處理格式化回應資訊。是以很自然的就會采用腳本處理請求的互動,使用server page處理回應的格式化。這其實就是著名的MVC(Model View Controller)模式中的view/controller的處理。

web server端的MVC工作流程示意圖

  應用Model View Controller模式首要的一點就是模型要和web服務完全分離開來。使用Transaction Script或Domain Model模式來封裝處理流程。

  接下來,我們就把剩餘的模式歸入兩類模式中:屬于Controller的模式,以及屬于View的模式。

View模式

  View這邊有三種模式:Transform View,Template View和Two Step View。Transform View和Template View的處理隻有一步,将領域資料轉換為html。Two Step View要經過兩步的處理,第一步把領域資料轉換為邏輯表示形式,第二步把邏輯表示轉換為html。

  兩步處理的好處是可以将邏輯集中于一處,如果隻有一步,變化發生時,你就需要修改每一個螢幕。但這需要你有一個很好的邏輯螢幕結構。如果一個web應用有很多的前端使用者時,兩步處理就特别的好用。例如航空訂票系統。使用不同的第二步處理,就可以獲得不同的邏輯螢幕。

  使用單步方法有兩個可選的模式:Template View,Transform View。Template View其時就是把代碼嵌入到html頁面中,就像現在的server page技術,如ASP,PHP,JSP。這種模式靈活,強大,但顯得雜亂無章。如果你能夠把邏輯程式邏輯在頁面結構之外進行很好的組織,這種模式還是有它的優點的。

  Transform View使用翻譯方式。例如XSLT。如果你的領域資料是用XML處理的,那這種模式就特别的好用。

Controller模式

  Controller有兩種模式。一般我們會根據動作來決定一項控制。動作可能是一個按鈕或連結。所這種模式就是Action Controller模式。

  Front Controller更進一步,它把http請求的處理和處理邏輯分離開來。一般是隻有一個web handle來處理所有的請求。你的所有的http請求的處理都由一個對象來負責。你改變動作結構的影響就會降到最小。

axing(轉載自 www.Linuxaid.com.cn)  2003年05月04日

Part 1 層

  層(layer)這個概念在計算機領域是非常了不得的一個概念。計算機本身就展現了一種層的概念:系統調用層、裝置驅動層、作業系統層、CPU指令集。每個層都負責自己的職責。網絡同樣也是層的概念,最著名的OSI的七層協定。

  層到了軟體領域也一樣好用。為什麼呢?我們看看使用層技術有什麼好處:

  ● 你使用層,但是不需要去了解層的實作細節。

  ● 可以使用另一種技術來改變基礎的層,而不會影響上面的層的應用。

  ● 可以減少不同層之間的依賴。

  ● 容易制定出層标準。

  ● 底下的層可以用來建立頂上的層的多項服務。 當然,層也有弱點:

  ● 層不可能封裝所有的功能,一旦有功能變動,勢必要波及所有的層。

  ● 效率降低。

  當然,層最難的一個問題還是各個層都有些什麼,以及要承擔何種責任。

典型的三層結構

  三層結構估計大家都很熟悉了。就是表示(presentation)層, 領域(domain)層, 以及基礎架構(infrastructure)層。

  表示層邏輯主要處理使用者和軟體的互動。現在最流行的莫過于視窗圖形界面(wimp)和基于html的界面了。表示層的主要職責就是為使用者提供資訊,以及把使用者的指令翻譯。傳送給業務層和基礎架構層。 基礎架構層邏輯包括處理和其他系統的通信,代表系統執行任務。例如資料庫系統互動,和其他應用系統的互動等。大多數的資訊系統,這個層的最大的邏輯就是存儲持久資料。

  還有一個就是領域層邏輯,有時也被叫做業務邏輯。它包括輸入和存儲資料的計算。驗證表示層來的資料,根據表示層的指令指派一個基礎架構層邏輯。

  領域邏輯中,人們總是搞不清楚什麼事領域邏輯,什麼是其它邏輯。例如,一個銷售系統中有這樣一個邏輯:如果本月銷售量比上個月增長10%,就要用紅色标記。要實作這個功能,你可能會把邏輯放在表示層中,比較兩個月的數字,如果超出10%,就标記為紅色。

  這樣做,你就把領域邏輯放到了表示層中了。要分離這兩個層,你應該現在領域層中提供一個方法,用來比較銷售數字的增長。這個方法比較兩個月的數字,并傳回boolean類型。表示層則簡單的調用該方法,如果傳回true,則标記為紅色。

例子

  層技術不存在說永恒的技巧。如何使用都要看具體的情況才能夠決定,下面我就列出了三個例子:

  例子1:一個電子商務系統。要求能夠同時處理大量使用者的請求,使用者的範圍遍及全球,而且數字還在不斷增長。但是領域邏輯很簡單,無非是訂單的處理,以及和庫存系統的連接配接部分。這就要求我們1、表示層要友好,能夠适應最廣泛的使用者,是以采用html技術;2、支援分布式的處理,以勝任同時幾千的通路;3、考慮未來的更新。

  例子2:一個租借系統。系統的使用者少的多,但是領域邏輯很複雜。這就要求我們制作一個領域邏輯非常複雜的系統,另外,還要給他們的使用者提供一個友善的輸入界面。這樣,wimp是一個不錯的選擇。

  例子3:簡單的系統。非常簡單,使用者少、邏輯少。但是也不是沒有問題,簡單意味着要快速傳遞,并且還要充分考慮日後的更新。因為需求在不斷的增加之中。

何時分層

  這樣的三個例子,就要求我們不能夠一概而論的解決問題,而是應該針對問題的具體情況制定具體的解決方法。這三個例子比較典型。

  第二個例子中,可能需要嚴格的分成三個層次,而且可能還要加上另外的中介(mediating)層。例3則不需要,如果你要做的僅是檢視資料,那僅需要幾個server頁面來放置所有的邏輯就可以了。

  我一般會把表示層和領域層/基礎架構層分開。除非領域層/基礎架構層非常的簡單,而我又可以使用工具來輕易的綁定這些層。這種兩層架構的最好的例子就是在VB、PB的環境中,很容易就可以建構出一個基于SQL資料庫的windows界面的系統。這樣的表示層和基礎架構層非常的一緻,但是一旦驗證和計算變得複雜起來,這種方式就存在先天缺陷了。

  很多時候,領域層和基礎架構層看起來非常類似,這時候,其實是可以把它們放在一起的。可是,當領域層的業務邏輯和基礎架構層的組織方式開始不同的時候,你就需要分開二者。

更多的層模式

  三層的架構是最為通用的,尤其是對IS系統。其它的架構也有,但是并不适用于任何情況。

  第一種是Brown model [Brown et al]。它有五個層:表示層(Presentation),控制/中介層(Controller/Mediator),領域層(Domain), 資料映射層(Data Mapping), 和資料源層(Data Source)。它其實就是在三層架構種增加了兩個中間層。控制/中介層位于表示層和領域層之間,資料映射層位于領域層和基礎架構層之間。

  表示層和領域層的中介層,我們通常稱之為表示-領域中介層,是一個常用的分層方法,通常針對一些非可視的控件。例如為特定的表示層組織資訊格式,在不同的視窗間導航,處理交易邊界,提供Server的facade接口(具體實作原理見設計模式)。最大的危險就是,一些領域邏輯被放到這個層裡,影響到其它的表示層。

  我常常發現把行為配置設定給表示層是有好處的。這可以簡化問題。但表示層模型會比較複雜,是以,把這些行為放到非可視化的對象中,并提取出一個表示-領域中介層還是值得的。

  Brown ISA

  表示層 表示層

  控制/中介層 表示-領域中介層

  領域層 領域層

  資料映射層 資料庫互動模式中的Database Mapper

  資料源層 基礎架構層

  領域層和基礎架構層之間的中介層屬于本書中提到的Database Mapper模式,是三種領域層到資料連接配接的辦法之一。和表示-領域中介層一眼,有時候有用,但不是所有時候都有用。

  還有一個好的分層架構是J2EE的架構,這方面的讨論可以見『J2EE核心模式』一書。他的分層是客戶層(Client),表示層(Presentation),業務層(Business ),整合層(Integration),資源層(Resource)。差别如下圖:

  J2EE核心 ISA

  客戶層 運作在客戶機上的表示層

  表示層 運作在伺服器上的表示層

  業務層 領域層

  整合層 基礎架構層

  資源層 基礎架構層通信的外部資料

  微軟的DNA架構定義了三個層:表示層(presentation),業務層(business),和資料存儲層(data access),這和我的架構相似,但是在資料的傳遞方式上還有很大的不同。在微軟的DNA中,各層的操作都基于資料存儲層傳出的SQL查詢結果集。這樣的話,實際上是增加了表示層和業務層同資料存儲層之間的耦合度。 DNA的記錄集在層之間的動作類似于Data Transfer Object。

Part 2 組織領域邏輯

  要組織基于層的系統,首要的是如何組織領域邏輯。領域邏輯的組織有好幾種模式。但其中最重要的莫過于兩種方法:Transation Script和Domain Model。標明了其中的一種,其它的都容易決定。不過,這兩者之間并沒有一條明顯的分界線。是以如何選取也是門大學問。一般來說,我們認為領域邏輯比較複雜的系統可以采用Domain Model。

  Transation Script就是對表示層使用者輸入的處理程式。包括驗證和計算,存儲,調用其它系統的操作,把資料回傳給表示層。使用者的一個動作表示一個程式,這個程式可以是script,也可以是transation,也可以是幾個子程式。在例子1中,檢驗,在購物車中增加一本書,顯示遞送狀态,都可以是一個Transation Script。

  Domain Model是要建立對應領域名詞的模型,例如例1中的書、購物車等。檢驗、計算等處理都放到領域模型中。

  Transation Script屬于結構性思維,Domain Model屬于OO思維。Domain Model比較難使用,一旦習慣,你能夠組織更複雜的邏輯,你的思想會更OO。到時候,即使是小的系統,你也會自然的使用Domain Model了。

  但如何抉擇呢?如果邏輯複雜,那肯定用Domain Model:如果隻需要存取資料庫,那Transation Script會好一些。但是需求是在不斷進化的,你很難保證以後的需求還會如此簡單。如果你的團隊不善于使用Domain Model,那你需要權衡一下投入産出比。另外,即使是Transation Script,也可以做到把邏輯和基礎架構分開,你可以使用Gateway。

  對例2,毫無疑問要使用Domain Model。對例1就需要權衡了。而對于例3,你很難說它将來會不會像例2那樣,你現在可以使用Transation Script,但未來你可能要使用Domain Model。是以說,架構的決策是至關緊要的。

  除了這兩種模式,還有其它中庸的模式。Use Case Controller就是處于兩者之間。隻有和單個的用例相關的業務邏輯才放到對象中。是以大緻上他們還是在使用Transation Script,而Domain Model隻是Database Gateway的一組集合而已。我不太用這種模式。

  Table Module是另一個中庸模式。很多的GUI環境依托于SQL查詢的傳回結果。你可以建立記憶體中的對象,來把GUI和資料庫分開來。為每個表寫一個子產品,是以每一行都需要關鍵字變量來識别每一個執行個體。

  Table Module适用于很多的元件建構于一個通用關系型資料庫之上,而且領域邏輯不太複雜的情況。Microsoft COM 環境,以及它的帶ADO.NET的.NET環境都适合使用這種模式。而對于Java,就不太适用了。

  領域邏輯的一個問題是領域對象非常的臃腫。因為對象的行為太多了,類也就太大了。它必須是一個超集。這就要考慮哪些行為是通用的,哪些不是,可以由其它的類來處理,可能是Use Case Controller,也可能是表示層。

  還有一個問題,複制。他會導緻複雜和不一緻。這比臃腫的危害更大。是以,甯可臃腫,也不要複制。等到臃腫為害時再處理它吧。

選擇一個地方運作領域邏輯

  我們的精力集中在邏輯層上。領域邏輯要麼運作在Client上,要麼運作在Server上。

  比較簡單的做法是全部集中在Server上。這樣你需要使用html的前端以及web server。這樣做的好處是更新和維護都非常的簡單,你也不用考慮桌面平台和Server的同步問題,也不用考慮桌面平台的其它軟體的相容問題。

  運作在Client适合于要求快速反應和沒有聯網的情況。在Server端的邏輯,使用者的一個再小的請求,也需要資訊從Client到Server繞一圈。反應的速度必然慢。再說,網絡的覆寫程度也不是說達到了100%。

  對于各個層來說,又是怎麼樣的呢?

  基礎架構層:一般都是在Server啦,不過有時候也會把資料複制到合适的高性能桌面機,但這是就要考慮同步的問題了。

  表示層在何處運作取決于使用者界面的設計。一個Windows界面隻能在Client運作。而一個Web界面就是在Server運作。也有特别的例子,在桌面機上運作web server的,例如X Server。但這種情況少的多。

  在例1中,沒有更多的選擇了,隻能選在Server端。是以你的每一個bit都會繞一個大圈子。為了提高效率,盡量使用一些純html腳本。

  人們選用Windows界面的原因主要就是需要執行一些非常複雜的任務,需要一個合适的應用程式,而web GUI則無法勝任。這就是例2的做法。不過,人們應該會漸漸适應web GUI,而web GUI的功能也會越來越強大。

  剩下的是領域邏輯。你可以全部放在Server,也可以全部放在Client,或是兩邊都放。

  如果是在Client端,你可以考慮全部邏輯都放在Client端,這樣至少保證所有的邏輯都在一個地方。而把web server移至Client,是可以解決沒有聯網的問題,但對反應時間不會有多大的幫助。你還是可以把邏輯和表示層分離開來。當然,你需要額外的更新和維護的工作。

  在Client和Server端都具有邏輯并不是一個好的處理辦法。但是對于那些僅有一些領域邏輯的情況是适用的。有一個小竅門,把那些和系統的其它部分沒有聯系的邏輯封裝起來。 領域邏輯的接口

  你的Server上有一些領域邏輯,要和Client通信,你應該有什麼樣的接口呢?要麼是一個http接口,要麼是一個OO接口。

  http接口适用于web browser,就是說你要選擇一個html的表示層。最近的新技術就是web service,通過基于http、特别是XML進行通信。XML有幾個好處:通信量大,結構好,僅需一次的回路。這樣遠端調用的的開銷就小了。同時,XML還是一個标準,支援平台異構。XML又是基于文本的,能夠通過防火牆。

  雖然XML有那麼多的好處,不過一個OO的接口還是有它的價值的。hhtp的接口不明顯,不容易看清楚資料是如何處理的。而OO的接口的方法帶有變量和名字,容易看出處理的過程。當然,它無法通過防火牆,但可以提供安全和事務之類的控制。

  最好的還是取二者所長。OO接口在下,http接口在上。但這樣做就會使得實作機制非常的複雜。

Part 3 組織web Server

  很多使用html方式的人,并不能真正了解這種方式的優點。我們有各種各樣好用的工具,但是卻搞到讓程式難以維護。

  在web server上組織程式的方式大緻可以分為兩種:腳本和server page。

  腳本方式就是一個程式,用函數和方法來處理http調用。例如CGI腳本和java servlet。它和普通的程式并沒有什麼兩樣。它從web頁面上獲得html string形态的資料,有時候還要做一些表達式比對,這正是perl能夠成為CGI腳本的常用語言的原因。而java servelet則是把這種分析留給程式員,但它允許程式員通過關鍵字接口來通路資訊,這樣就會少一些表達式的判斷。這種格式的web server輸出是另一種html string,稱為response,可以通過流資料來操作。

  糟糕的是流資料是非常麻煩的,是以就導緻了server page的産生,例如PHP,ASP,JSP。

  server page的方式适合回應(response)的處理比較簡單的情況。例如“顯示歌曲的明細”,但是你的決策取決于輸入的時候,就會比較雜亂。例如“通俗和搖滾的顯示格式不同”。

  腳步擅長于處理使用者互動,server page擅長于處理格式化回應資訊。是以很自然的就會采用腳本處理請求的互動,使用server page處理回應的格式化。這其實就是著名的MVC(Model View Controller)模式中的view/controller的處理。

web server端的MVC工作流程示意圖

  應用Model View Controller模式首要的一點就是模型要和web服務完全分離開來。使用Transaction Script或Domain Model模式來封裝處理流程。

  接下來,我們就把剩餘的模式歸入兩類模式中:屬于Controller的模式,以及屬于View的模式。

View模式

  View這邊有三種模式:Transform View,Template View和Two Step View。Transform View和Template View的處理隻有一步,将領域資料轉換為html。Two Step View要經過兩步的處理,第一步把領域資料轉換為邏輯表示形式,第二步把邏輯表示轉換為html。

  兩步處理的好處是可以将邏輯集中于一處,如果隻有一步,變化發生時,你就需要修改每一個螢幕。但這需要你有一個很好的邏輯螢幕結構。如果一個web應用有很多的前端使用者時,兩步處理就特别的好用。例如航空訂票系統。使用不同的第二步處理,就可以獲得不同的邏輯螢幕。

  使用單步方法有兩個可選的模式:Template View,Transform View。Template View其時就是把代碼嵌入到html頁面中,就像現在的server page技術,如ASP,PHP,JSP。這種模式靈活,強大,但顯得雜亂無章。如果你能夠把邏輯程式邏輯在頁面結構之外進行很好的組織,這種模式還是有它的優點的。

  Transform View使用翻譯方式。例如XSLT。如果你的領域資料是用XML處理的,那這種模式就特别的好用。

Controller模式

  Controller有兩種模式。一般我們會根據動作來決定一項控制。動作可能是一個按鈕或連結。所這種模式就是Action Controller模式。

  Front Controller更進一步,它把http請求的處理和處理邏輯分離開來。一般是隻有一個web handle來處理所有的請求。你的所有的http請求的處理都由一個對象來負責。你改變動作結構的影響就會降到最小。

axing(轉載自 www.Linuxaid.com.cn)  2003年05月04日

如何從開發人員走向架構師

很多架構師都是從好的開發人員逐漸過渡而來的,但并非每個好的開發人員都希望成為架構師,而且他們并不是都适合做架構師。無論您是打算進行職業轉型的開發人員,還是尋找能承擔體系結構設計責任的合适人選的經理,都務必對此轉型過程有個清楚的了解。本文将讨論從實作專家到架構師的過渡過程。

  在尋找優秀的指揮的時候,您首先要找的是一名優秀的音樂演奏家。但并非每個音樂演奏家都能成為優秀的指揮。架構師的專業發展方面也與此類似。越來越多的 IT 組織開始認識到良好軟體體系結構的重要性,架構師職業正迅速發展為 IT 内一個獨立的門類。由于要從相當小的候選範圍内招募架構師,是以這就給管理帶來了一些新挑戰。即使人力資源部門找到了候選者,針對經驗進行的篩選也比其他門類更為嚴格。跨越這些障礙的最快方式是要認識到,大部分好的架構師同時也是好的開發人員,是以尋找架構師人才時可能首先應該從普通開發人員中找起。招聘人員在對候選者(内部或外部)進行詳細審查時,應該考慮這個觀點。不過,對此資源進行挑選可能比較麻煩,因為隻有極少的優秀開發人員具有成為架構師的特征或願望。

  本文列出了開發人員成為架構師要進行的工作。我将從可能考慮進行此轉型的開發人員和評估進行此轉型的開發人員的經理這兩個方面來探讨這一問題。我還将提供一系列在做出這些決策時要考慮的因素。

  個人特征

  軟體開發團隊和管理層之間的聯系始終是 IT 中的一個關鍵所在。二者都傾向于以完全不同的方式考慮給定的問題。大部分相關技術都是讨論項目經理應如何跟蹤和解釋開發人員的進度和問題。但溝通不足的情況仍然非常普遍,而且這是項目失敗的首要原因。好的架構師是解決這個問題的最有效辦法。架構師的主要責任是提供開發人員和項目經理之間的共用溝通媒體。他們負責讓業務規則及需求與工程實踐及限制相适應,以確定成功。以下是成功架構師的一些主要特征。

  願意并有能力進行溝通:在開發人員中發現架構師的最有價值标準是有效的溝通。您需要技術娴熟、經驗豐富的開發人員,這樣的人員需要有就項目中的業務相關問題進行溝通的經曆。架構師經常必須對了解方面的差距進行預計,然後才能有所貢獻。他們必須願意克服困難來確定技術和業務觀點的融合。他們并不必對意見交換工作進行計劃和協調;這仍然主要是項目經理的工作。他們的任務是确定表述系統設計時的最佳工具和構件,以促進有效的意見交換。他們必須能夠判斷目前方法顯得不足而需要采用新方法的情況。寫作技能也非常重要,還需要具有制作草圖的技能或使用制圖軟體的能力。

  具有處理談判細節方面的經驗:架構師經常需要負責讨論系統開發的技術折衷方案。優先級的沖突可能會帶來實踐限制、風險規避或可能導緻在各個不同業務組之間需求不同。優秀的架構師能夠有效地評估技術可能性,并能在不損失項目的主要價值的前提下制訂開發計劃來處理各種利害關系和限制。這與前面讨論的溝通技能緊密相關,但同時也要展現架構師的技術能力。好的架構師候選者應該是經常幫助對有争議的讨論進行引導的人,能夠使讨論得出新的想法,而不會使其在一個位置停滞不前。

  自覺主動;積極解決設計問題:架構師的日常工作目标經常并不明确。很多開發人員直接參考功能規範來列出任務清單。架構師通常則是向這些開發人員提供所需結構的人員,以便盡可能提高工作效率。好的候選者不僅進行溝通方面的工作,而且也會預計各種設計問題并加以解決——通常在沒有任何具體訓示的情況下自覺進行。無論所配置設定的職責如何,積極參與項目的開發人員都有機會從一起工作的人員中脫穎而出。

  抽象思維和分析:架構師必須能夠了解表述模糊的概念并将其變成相關各方能夠了解的項目構件。他們必須能夠了解抽象概念,并以具體的語言對其進行溝通。開發人員中好的候選者經常要求或自己主動解釋開發生命周期中容易混淆的問題。他們能迅速評估各種想法并将其納入後續工作的操作建議中。

  開發人員經常具有很強的數學能力,而好的架構師則傾向于表現出更強的口頭表達能力。管理人員經常說開發人員具有“工程意識”,而這是一個用于評估架構師的非常有意義的方面。架構師應該具有很強的解決技術問題的能力,但還必須能夠準确獲知更為全面的人員如何與技術互動的資訊。這要求具有某種形式的抽象思維(而不再是代碼的細節),這種思維能力可能較難形成。

  有些人認為,某種級别的正式教育是成為優秀開發人員的必備條件之一,我并不同意這種精英論。我遇到了很多高中就辍學的優秀開發人員。不過,對于體系結構設計工作,我的個人經驗以及我對所需能力的認識都讓我相信,好的架構師通常至少獲得了一個有挑戰性的學士學位。

  跟蹤生命周期

  好的架構師通常有在具備定義良好的軟體開發生命周期(Software Development Life Cycle,SDLC)的組織工作的經驗。架構師必須了解在其所屬專業内最重要的操作過程。這并不意味着需要有其他前提,例如,并不需要高能力成熟度模型(Capability Maturity Model,CMM)級别的工作經驗。好的架構師可能來自使用 SDLC 的多個小型疊代的極限程式設計(Extreme Programming,XP)方法的組織。務必注意各種傳統軟體開發操作,如 Michael A. Jackson 的方法:Jackson 結構程式設計(Jackson Structured Programming,JSP)和 Jackson 系統開發(Jackson System Development,JSD)。Jackson 的研究對架構師職業發展的意義就像 Donald Knuth 的研究對程式員一樣重要。架構師可以偏愛任何經典的、經過時間考驗的軟體系統開發方法。

  SDLC 也可以成為評估架構師合适人選的有用機制。每個 SDLC 階段都具有能提供相關線索的特征。SDLC 包含很多小的變體,但在此部分,我将使用幾乎所有方法的公共基礎部分。下面的清單詳細說明了 SDLC 的各個階段,并列出了好的架構師候選者在每個階段表現出來的特征。

  •   分析:在分析期間,好的架構師會考慮非技術影響,以便了解需求和将在其中進行開發的環境。架構師可為風險評估任務帶來廣泛的軟體經驗供參考。尋找具有豐富經驗的開發人員,以幫助業務部門了解技術人員正确解釋需求所需的資訊。尋找在開發的早期階段能夠預計可能遇到的問題的開發人員。
  •   設計:在進階設計期間,好的架構師會收集問題空間的各個抽象元素,并就其進行溝通,以便開發團隊草拟将要開發的系統的相關圖表。架構師負責将需求謹慎地映射到所得到的系統體系結構的功能。在詳細設計期間,他們所扮演的角色并不是核心角色,但為了根據整個系統的規則對特定子產品的元素進行審查,仍然需要他們。尋找善于讓團隊能夠預計設計決策對最終系統的影響的開發人員。尋找善于确定一些最佳構件來促進與技術和非技術閱聽人溝通設計問題的開發人員。
  •   實作:在實作期間,架構師對項目進行引導,以確定其符合系統體系結構。他們在一線評估技術更改請求,并确定如何對設計進行調整,以最好地處理此類請求。架構師還要密切了解開發人員的進度,特别要跟蹤系統中子產品間的內建點的狀态。尋找經常對讨論進行引導來連接配接多個子系統的開發人員。尋找項目經理可以依賴其快速地進行與更改和出現的問題相關的風險評估的開發人員。
  •   測試:架構師對系統內建和使用者接受度測試進行指導,并負責評估進度的正确溝通的持續測試結果。尋找了解錯誤模式且善于将測試複查結果轉換為行動計劃的開發人員。
  •   維護:在維護期間,架構師将發起關于系統內建的讨論。無論處理 IT 基礎設施問題,還是確定部門之間的技術合作,架構師都必須完全了解應用程式,必須快速學習姊妹應用程式的體系結構,而且必須就內建點和風險進行有效溝通。尋找具有系統內建經驗且表現出快速掌握全貌的能力的開發人員。系統內建是一項獨特的任務。

  架構師培養建議

  有些組織能比其他組織更有效地進行架構師培養。如果充分考慮到招聘此類新專業人才的困難,努力促成能鼓勵開發人員發展為架構師的環境是非常明智的政策。但務必避免對不願意或不适合走這條路的開發人員進行處罰。組織應該為開發人員制訂多條發展路線,包括那些願意繼續擔任開發人員的人。對架構師而言,資深開發人員不可或缺。他們可以實作系統中最關鍵的子產品。通過對其他開發人員進行代碼檢查和測試支援,他們可幫助確定總體軟體品質,而如果品質不能保證,即使最好的體系結構也毫無用處。

  組織應制訂個人評估程式,以鼓勵開發人員考慮其職業目标,其中要包含體系結構設計的選項。應該鼓勵經理在其下屬中尋找體系結構設計人才。應該實作指導計劃,讓架構師與希望成為架構師的開發人員協作工作。應該鼓勵開發人員通過參加各種協會、撰寫文章和參加會議,進而參與到專業領域中來。通過這樣參與進來,可幫助開發人員從新的角度了解系統,并幫助他們更好地就其認識進行溝通。這樣還能培養可提高效率的重要創新想法。

  結束語

  開發人員一旦邁出了通向體系結構設計專業方向的第一步,就可以利用很多資源來獲得幫助,其中包括很多來自 IBM 的資源。有時候,此過程的最困難的部分就是第一步,而本文提供了一些線索和提示,經理和開發人員可以利用其來評估應該鼓勵哪些人努力成為架構師。

CSDN聲明:此消息系轉載自CSDN合作媒體,其中細節未經CSDN證明,特此聲明

靈活思維- 架構設計中的方法學

靈活思維-架構設計中的方法學

目錄

1.從方法論看架構設計

2.架構設計的靈活視圖

3.源自需求

4.團隊設計

5.簡單設計

6.疊代設計

7.組合使用模式

8.架構願景

9.分層 (上)

10.分層 (下)

11.精化和合并

12.Refactoring

13.穩定化

14.代碼驗證

15.進一步閱讀

1.從方法論看架構設計

方法論對軟體開發而言意味着什麼?我們如何看待軟體開發中的方法論?方法論能夠成為軟體開發的救命稻草嗎?在讀過此文後,這些疑惑就會得到解答。

在第一篇文章中,我們來了解标題中的一些詞的含義。

  • 方法學是什麼?
  • 靈活是什麼?
  • 為什麼讨論架構?

方法論

方法論的英文為Methodology,詞典中的解釋為"A series of related methods or techniques"我們可以把它定義為軟體開發(針對軟體開發)的一整套方法、過程、規則、實踐、技術。關于方法論的出現的問題,我很贊同Alistair Cockburn的一句話,"方法論源于恐懼。"出于對項目的超期、成本失控等等因素的恐懼,項目經理們從以前的經驗出發,制定出了一些控制、監測項目的方法、技巧。這就是方法論産生的原因。

在Agile Software Development一書中,作者提到了方法論的十三個要素,基本能夠函蓋方法論的各個方面:

  • 角色(Roles)
  • 個性(Personality)
  • 技能(Skills)
  • 團隊(Teams)
  • 技術(Techniques)
  • 活動(Activities)
  • 過程(Process)
  • 工件(Work products)
  • 裡程碑(Milestones)
  • 标準(Standards)
  • 品質(Quality)
  • 工具(Tools)
  • 團隊價值(Team Values)

它們之間的關系可以用一幅圖來表示:

圖 1. 方法論的十三個要素

很多的方法論,都涉及了上面列舉的十三要素中的部分要素,是以,我們可以把方法論看作是一個抽象的、無窮的超集,而現實中的方法論都是指超集的一個有限的子集而已。它們之間的關系就好像有理數和1到100之間的整數的關系一樣。不論是XP,還是UI設計經驗之類,都屬于方法論的一個子集,隻是這兩個子集之間有大小的差别而已。我們還應該看到,讨論一個完備的方法論是沒有意義的,是以這種方法論鐵定不存在,就好像你視圖窮舉出所有的有理數一樣荒唐。是以,我們關于一個通用的方法論的說法也是無意義的。好的方法論,比如說XP、水晶系列,它們都有一個适合的範圍,因為它們了解一點,自己并不是一個無所不能的方法論。

在現實中,我們其實不斷的在接觸方法論。比如說,為了控制項目的進度,項目經理要求所有的開發人員每周遞交一份詳細的進度報告,這就是一種方法、一種技巧。如果把開發過程中的這些技巧系統的組織起來,就能夠成為一種方法論。你可能會說,那一種方法論的産生也太容易了吧。不,這樣産生的方法論并沒有太大的實用價值,沒有實用價值的方法論根本就沒有存在的必要。是以,一個成功的方法論是要能夠為多個的項目所接受,并且能夠成功實作軟體的傳遞的方法論。

我和我的同僚在實踐中做了一些試驗,希望能夠把一些好的方法論應用于開發團隊。試驗的結果很無奈,方法論實施的效果并不理想,一開始我們認為是方法本身的原因,到後來,我們發現事情并不是這麼簡單。在試驗的過程中,開發人員一緻認同方法論的優勢所在,但是在實施過程中,鮮有堅持的下來的。在Agile Software Development中,我發現作者遇到了和我們一樣的問題。

Alistair Cockburn在和大量的項目團隊的訪談之後,寫成了Agile Software Development一書。在訪談之前,他笃定自己将會發現高度精确的過程控制是成功的關鍵所在,結果他發現事實并非如此,他把他的發現歸結為7條定律。而我在實際中的發現也包含在這七條定律中,總結起來就隻有兩點:溝通和回報。

隻要能夠保證良好的溝通和即時的回報,那麼開發團隊即使并沒有采用先進的方法論,一樣可以成功。相反,那些"高品質"的團隊卻往往由于缺乏這兩個因素而導緻失敗(我們這裡指的失敗是使用者拒絕使用最終的軟體)。最有效,而成本也最低的溝通方法就是面對面(face to face)的溝通,而随着項目團隊的變大,或是另外一些影響因素的加入(比如地理位置的隔絕),面對面的溝通越來越難實作,這導緻溝通的的成本逐漸加大,品質也慢慢下降。但這并不是說非面對面的溝通不可,重要的是我們需要知道不同的溝通方式的成本和品質并不相同。XP方法尤為強調面對面的溝通,通過現場客戶、站立會議、結對程式設計等方式來保證溝通的有效。在我的經驗中,一個開發團隊其實是需要多種溝通方式的結合的。完全的面對面的溝通對某些團隊來說是很難實作的,那麼問題的關鍵就在于你如何應用溝通的方式來達到你希望的效果。在前不久結束的歐萊雅創業計劃大賽上,有一支團隊特别引人注目,他們彼此間素未謀面,僅僅憑借Internet和電話完成了高效的合作。他們雖然沒有使用面對面的溝通方式,但是仍然達成了既定的目标。軟體開發也是一樣的,面對面的溝通是非常有必要的,但其它的溝通方式也是需要的。

再看回報,不論是控制進度,還是保證客戶的滿意度,這些活動都需要管理成本。軟體開發中的管理成本的一個通性就是伴随有中間産出物(intermediate delivery)。比如說我們的需求規約、分析文檔、設計文檔、測試計劃,這些都屬于中間産出物。中間産出物的增加将會帶來效率下降的問題,因為開發人員的時間都花在了完成中間産出物的工作上,花在給軟體新功能上的時間就減少了。而中間産出物的主要目的是兩個,一個是為了保證軟體如客戶所願,例如需求規約;另一個是為了作為團隊中的其他成員工作的輸入,例如開發計劃、測試計劃等。是以,我們也可以針對這兩點來商讨對策,一種是采用疊代的思想,提高軟體釋出的頻率,以保證客戶的需求被确實的滿足,另一種就是縮小團隊的溝通範圍,保證成員能夠從其他人那裡得到新的思路,而不是撰寫規範的内部文檔(内部文檔指那些僅為内部開發人員之間的溝通所需要的文檔)。

是以,一個軟體項目的成功和你采用的開發方法論并沒有直接的關系。

重量

我們根據把擁有大量artifact(RUP官方翻譯為工件,意思是軟體開發過程中的中間産物,如需求規約、設計模型等)和複雜控制的軟體開發方法稱為重型(Heavy Weight)方法,相對的,我們稱artifact較少的方法為輕型(Light Weight)方法。在傳統的觀念中,我們認為重型方法要比輕型安全許多。因為我們之是以想出重型方法,就是由于在中大型的項目中,項目經理往往遠離代碼,他無法有效的了解目前的工程的進度、品質、成本等因素。為了克服未知的恐懼感,項目經理制定了大量的中間管理方法,希望能夠控制整個項目,最典型的莫過于要求開發人員頻繁的遞交各種表示項目目前狀态的報告。

在Planning XP一書中有一段讨論輕重型方法論的精辟論述,它把重型方法論歸結為一種防禦性的姿态(defensive posture),而把輕型方法論歸結為一種渴望成功(Plan to win)的心态。如果你是采用了防禦性姿态,那麼你的工作就集中在防止和跟蹤錯誤上,大量的工作流程的制定,是為了保證項目不犯錯誤,而不是項目成功。而這種方法也不可謂不好,但前提是如果整個團隊能夠滿足前面所提到的兩個條件的話,項目也肯定會成功,但是重型方法論的一個弊端就在于,大家都在防止錯誤,都在懼怕錯誤,是以人和人之間的關系是很微妙的,要達到充分的溝通也是很難的。最終,連對人的評價也變成是以避免錯誤的多寡作為考評的依據,而不是成就。我們在做試驗的時候,一位項目經理開玩笑說,"方法論源自項目經理的恐懼,這沒錯。但最糟糕的是整個團隊隻有項目經理一個人恐懼,如果能夠做到人人的恐懼,那大家也就沒有什麼好恐懼的了。"這句話提醒了我們,如果一個團隊的精神就是力求成功,那麼這支團隊的心态就和其它的團隊不同了,尤其是對待錯誤的心态上。根本就沒有必要花費大量的精力來預防錯誤,錯誤犯了就犯了,即時改正就可以了。這其實就是渴望成功的心态。

方法論的藝術

管理,被稱為科學和藝術的融合體,而管理的藝術性部分很大程度的展現為人的管理上。我說,方法學,一樣是科學和藝術的融合體。這是有依據的,其實方法論和管理學是近親關系,管理學中有一門分支是項目管理,而在軟體組織中,項目管理是非常重要的,方法學就是一種針對軟體開發的一種特定的項目管理(或是項目管理的一個子集)。

重型方法最大的一個問題就在于他不清楚或忽略了藝術這個層次,忽視了人的因素,把人做為一個計量機關,一種資源,一種線性元素。而人的要素在軟體開發中是非常重要的,軟體開發實際上是一種知識、智力的轉移過程,最終形成的産品是一種知識産品,它的成本取決于開發者的知識價值,是以,人是最重要的因素。而人這個要素是很難衡量的,每個人都有不同的個性、想法、經驗、經曆,這麼多複雜的因素加在一起,就導緻了人的不可預見性。是以,我們強調管人的藝術。

最簡單的例子是,在重型方法中,我們的基本假設是對人的不信任。項目經理要控制項目。但不信任就會産生很多的問題,比如士氣不高,計劃趕不上變化,創新能力低下,跳槽率升高等等。人都是希望被尊重的,技術人員更看重這一點,而很多公司也口口聲聲說自己多麼多麼以人為本,可是采用的卻是以不信任人為前提的開發方法,言行不一。我們說靈活方法的出發點是互相信任,做到這一點是很難的,但是一旦做到了,那這個團隊就是非常具有競争力的。是以,這就産生了一個問題,在沒有做到完全的互相信任之前,我們到底相不相信他人呢,這就是我提到的藝術性的問題,什麼時候你要相信人?什麼時候你不相信人,這些都是需要權衡的問題,也都是表現你藝術性的問題。

靈活

靈活代表着有效和靈活。我們稱那些輕型的、有效的方法為靈活方法。在重型方法中,我們在一些不必要、重複的中間環節上浪費了太多的精力,而靈活則避免了這種浪費。我們的文章将會重點的讨論靈活(Agile)方法論的思想,靈活這個名字的前身就是輕型。目前已經有了一個靈活聯盟,他們制定了靈活宣言:

  • Individuals and interactions over processes and tools.
  • Working software over comprehensive documentation.
  • Customer collaboration over contract negotiation.
  • Responding to change over following a plan.

而我對靈活的了解包括了幾個方面:

  • 較低的管理成本和高品質的産出。軟體開發存在兩個極端:一個是沒有任何的管理成本,所有的工作都是為了軟體的産出,但是這種方式卻往往導緻軟體開發過程的混沌,産品的低品質,團隊士氣的低落。另一個是大量管理活動的加入,評審、變更管理,缺陷跟蹤,雖然管理活動的加入能夠在一定程度上提高開發過程的有序性,但是成本卻是以提高,更糟糕的是,很容易導緻團隊的低效率,降低創新能力。是以,靈活方法視圖尋找一個平衡點,用低成本的管理活動帶來最大的産出,即軟體的高品質。
  • 尊重人性。靈活方法尊重人性,強調效率。軟體開發可以說是一種腦力的投入,如果不能保證開發人員的自願投入,産品就肯定要打折扣。事實多次的證明,一個願意投入的開發人員和一個不願意投入的開發人員效率相差在三倍以上,對組織的貢獻更是在十倍以上。
  • 溝通和回報是一切的基礎。我們已經讨論過溝通的重要程度,而即時的回報是擁抱變化的前提條件。
  • 客戶是上帝。沒有客戶就沒有一切,客戶的重要性可以用一句話來形容,就是以合理的成本建造合适的軟體(build the right system at the right cost)。

靈活其實也有輕重之分,關鍵在于是否能夠做到有效和靈活。是以,靈活方法論提倡的一個思想是"剛好夠(barely sufficient)"。不過這個"剛好夠"可不是那麼容易判斷的。一支8個人的團隊采用XP方法,随着方法的熟練使用,團隊的能力在不斷的增強,能夠處理的問題越越來越複雜,也許他們能夠處理采用重型方法的20個人團隊能夠處理的問題。可是如果團隊的人數突然增加到12人,這支團隊肯定就會出問題,他的表現可能還不如那支20個人的團隊了。人數增加了的時候,原先的方法肯定還做适當的調整,比如說,在原先的靈活方法上增加一些重型方法的技巧。我們不能夠要求一支6個人的團隊和一支20個人的團隊用同樣的方法,前者可能采用輕一些的靈活方法,後者可能采用重一些的靈活方法,關鍵的問題在于,兩支團隊都把重點放在溝通、回報、頻繁傳遞軟體這些關鍵的因素上,也就是做到有效和靈活。

架構設計

架構(Architecture)(也有被稱為體系結構的)是軟體設計中非常重要的一個環節。軟體開發的過程中隻要需求和架構确定之後,這個軟體就基本上可以定型了。這就好比骨骼确定了,這個人的體形就不會有很大的變化。是以我選擇了架構設計來讨論靈活軟體開發(需求我已經寫過了)。我們在前面讨論過超集和子集的概念,是以我們接下去要讨論的架構設計也是一個很小的子集。方法論如果沒有經曆過多個項目的檢驗是不能稱為成功的方法論的,我也并不認為我的架構設計就是一個好的方法論,但引玉還需抛磚,他的主要目的是為了傳播一種思想。是以,我采用了模式語言(PLOP)做為寫作架構設計的形式,主要的原因就是模式是一種很好的組織思想的方法。

是以,在我們接下去的曆程中,我們集中讨論的東西就圍繞着架構、方法學、靈活這三個要素展開。這篇文章并不是讨論如何編碼實作軟體架構的,也不要單純的把它看作架構設計的指南,其實文中的很多思想來自于方法論,是以提到的很多架構設計的思想也适用于其它工作,如果能夠了解這一點,看這篇文章的收獲可能會更多一些。

2.架構設計的靈活視圖

通過上一章的介紹,我們對靈活和方法有了一個大緻的了解,從這一章起,我們開始對軟體開發過程中架構設計的研究。記住一點,我們并不是為了架構設計而研究架構設計,我們的目的在于靈活方法學的應用。

架構設計是一種權衡(trade-off)。一個問題總是有多種的解決方案。而我們要确定唯一的架構設計的解決方案,就意味着我們要在不同的沖突體之間做出一個權衡。我們在設計的過程總是可以看到很多的沖突體:開放和整合,一緻性和特殊化,穩定性和延展性等等。任何一對沖突體都源于我們對軟體的不同期望。可是,要滿足我們希望軟體穩定運作的要求,就必然會影響我們對軟體易于擴充的期望。我們希望軟體簡單明了,卻增加了我們設計的複雜度。沒有一個軟體能夠滿足所有的要求,因為這些要求之間帶有天生的互斥性。而我們評價架構設計的好壞的依據,就隻能是根據不同要求的輕重緩急,在其間做出權衡的合理性。

目标

我們希望一個好的架構能夠:

  • 重用:為了避免重複勞動,為了降低成本,我們希望能夠重用之前的代碼、之前的設計。重用是我們不斷追求的目标之一,但事實上,做到這一點可沒有那麼容易。在現實中,人們已經在架構重用上做了很多的工作,工作的成果稱為架構(Framework),比如說Windows的視窗機制、J2EE平台等。但是在企業商業模組化方面,有效的架構還非常的少。
  • 透明:有些時候,我們為了提高效率,把實作的細節隐藏起來,僅把客戶需求的接口呈現給客戶。這樣,具體的實作對客戶來說就是透明的。一個具體的例子是我們使用JSP的tag技術來代替JSP的嵌入代碼,因為我們的HTML界面人員更熟悉tag的方式。
  • 延展:我們對延展的渴求源于需求的易變。是以我們需要架構具有一定的延展性,以适應未來可能的變化。可是,如上所說,延展性和穩定性,延展性和簡單性都是沖突的。是以我們需要權衡我們的投入/産出比。以設計出具有适當和延展性的架構。
  • 簡明:一個複雜的架構不論是測試還是維護都是困難的。我們希望架構能夠在滿足目的的情況下盡可能的簡單明了。但是簡單明了的含義究竟是什麼好像并沒有一個明确的定義。使用模式能夠使設計變得簡單,但這是建立在我熟悉設計模式的基礎上。對于一個并不懂設計模式的人,他會認為這個架構很複雜。對于這種情況,我隻能對他說,去看看設計模式。
  • 高效:不論是什麼系統,我們都希望架構是高效的。這一點對于一些特定的系統來說尤其重要。例如實時系統、高通路量的網站。這些值的是技術上的高效,有時候我們指的高效是效益上的高效。例如,一個隻有幾十到一百通路量的資訊系統,是不是有必要使用EJB技術,這就需要我們綜合的評估效益了。
  • 安全:安全并不是我們文章讨論的重點,卻是架構的一個很重要的方面。

規則

為了達到上述的目的,我們通常需要對架構設計制定一些簡單的規則:

功能分解

顧名思義,就是把功能分解開來。為什麼呢?我們之是以很難達到重用目标就是因為我們編寫的程式經常處于一種好像是重複的功能,但又有輕微差别的狀态中。我們很多時候就會經不住誘惑,用拷貝粘貼再做少量修改的方式完成一個功能。這種行為在XP中是堅決不被允許的。XP提倡"Once and only once",目的就是為了杜絕這種拷貝修改的現象。為了做到這一點,我們通常要把功能分解到細粒度。很多的設計思想都提倡小類,為的就是這個目的。

是以,我們的程式中的類和方法的數目就會大大增長,而每個類和方法的平均代碼卻會大大的下降。可是,我們怎麼知道這個度應該要如何把握呢,關于這個疑問,并沒有明确的答案,要看個人的功力和具體的要求,但是一般來說,我們可以用一個簡單的動詞短語來命名類或方法的,那就會是比較好的分類方法。

我們使用功能分解的規則,有助于提高重用性,因為我們每個類和方法的精度都提高了。這是符合大自然的原則的,我們研究自然的主要的一個方向就是将物質分解。我們的思路同樣可以應用在軟體開發上。除了重用性,功能分解還能實作透明的目标,因為我們使用了功能分解的規則之後,每個類都有自己的單獨功能,這樣,我們對一個類的研究就可以集中在這個類本身,而不用牽涉到過多的類。

根據實際情況決定不同類間的耦合度

雖然我們總是希望類間的耦合度比較低,但是我們必須客觀的評價耦合度。系統之間不可能總是松耦合的,那樣肯定什麼也做不了。而我們決定耦合的程度的依據何在呢?簡單的說,就是根據需求的穩定性,來決定耦合的程度。對于穩定性高的需求,不容易發生變化的需求,我們完全可以把各類設計成緊耦合的(我們雖然讨論類之間的耦合度,但其實功能塊、子產品、包之間的耦合度也是一樣的),因為這樣可以提高效率,而且我們還可以使用一些更好的技術來提高效率或簡化代碼,例如Java中的内部類技術。可是,如果需求極有可能變化,我們就需要充分的考慮類之間的耦合問題,我們可以想出各種各樣的辦法來降低耦合程度,但是歸納起來,不外乎增加抽象的層次來隔離不同的類,這個抽象層次可以是具體的類,也可以是接口,或是一組的類(例如Beans)。我們可以借用Java中的一句話來概括降低耦合度的思想:"針對接口程式設計,而不是針對實作程式設計。"

設計不同的耦合度有利于實作透明和延展。對于類的客戶(調用者)來說,他不需要知道過多的細節(實作),他隻關心他感興趣的(接口)。這樣,目标類對客戶來說就是一個黑盒子。如果接口是穩定的,那麼,實作再怎麼擴充,對客戶來說也不會有很大的影響。以前那種牽一發而動全身的問題完全可以緩解甚至避免。

其實,我們仔細的觀察GOF的23種設計模式,沒有一種模式的思路不是從增加抽象層次入手來解決問題的。同樣,我們去觀察Java源碼的時候,我們也可以發現,Java源碼中存在着大量的抽象層次,初看之下,它們什麼都不幹,但是它們對系統的設計起着重大的作用。

夠用就好

我們在上一章中就談過靈活方法很看重剛好夠用的問題,現在我們結合架構設計來看:在同樣都能夠滿足需要的情況下,一項複雜的設計和一項簡單的設計,哪一個更好。從靈活的觀點來看,一定是後者。因為目前的需求隻有10項,而你的設計能夠滿足100項的需求,隻能說這是種浪費。你在設計時完全沒有考慮成本問題,不考慮成本問題,你就是對開發組織的不負責,對客戶的不負責。

應用模式

這篇文章的寫作思路很多來源于對模式的研究。是以,文章中到處都可以看到模式思想的影子。模式是一種整理、傳播思想的非常優秀的途徑,我們可以通過模式的方式學習他人的經驗。一個好的模式代表了某個問題研究的成果,是以我們把模式應用在架構設計上,能夠大大增強架構的穩定性。

抽象

架構的本質在于其抽象性。它包括兩個方面的抽象:業務抽象和技術抽象。架構是現實世界的一個模型,是以我們首先需要對現實世界有一個很深的了解,然後我們還要能夠熟練的應用技術來實作現實世界到模型的映射。是以,我們在對業務或技術了解不夠深入的情況下,就很難設計出好的架構。當然,這時候我們發現一個問題:怎樣才能算是了解足夠深入呢。我認為這沒有一個絕對的準則。

一次,一位朋友問我:他現在做的系統有很大的變化,原先設計的工作流架構不能滿足現在的要求。他很希望能夠設計出足夠好的工作流架構,以适應不同的變化。但是他發現這樣做無異于重新開發一個lotus notes。我聽了他的疑問之後覺得有兩點問題:

首先,他的開發團隊中并沒有工作流領域的專家。他的客戶雖然了解自己的工作流程,但是缺乏足夠的理論知識把工作流提到抽象的地步。顯然,他本身雖然有技術方面的才能,但就工作流業務本身,他也沒有足夠的經驗。是以,設計出象notes那樣的系統的前提條件并不存在。

其次,開發一個工作流系統的目的是什麼。原先的工作流系統運作的不好,其原因是有變化發生。是以才有改進工作流系統的動機出現。可是,畢竟notes是為了滿足世界上所有的工作流系統而開發的,他目前的應用肯定達不到這個層次。

是以,雖然做不到最優的業務抽象,但是我們完全可以在特定目的下,特定範圍内做到最優的業務抽象。比如說,我們工作流可能的變化是工組流路徑的變化。我們就完全可以把工作流的路徑做一個抽象,設計一個可以動态改變路徑的工作流架構。

有些時候,我們雖然在技術上和業務上都有所欠缺,沒有辦法設計出好的架構。但是我們完全可以借鑒他人的經驗,看看類似的問題别人是如何解決的。這就是我們前面提到的模式。我們不要把模式看成是一個硬性的解決方法,它隻是一種解決問題的思路。Martin Fowler曾說:"模式和業務元件的差別就在于模式會引發你的思考。"

在《分析模式》一書中,Martin Fowler提到了分析和設計的差別。分析并不僅僅隻是用用例列出所有的需求,分析還應該深入到表面需求的的背後,以得到關于問題本質的Mental Model。然後,他引出了概念模型的概念。概念模型就類似于我們在讨論的抽象。Martin Fowler提到了一個有趣的例子,如果要開發一套軟體來模拟桌球遊戲,那麼,用用例來描述各種的需求,可能會導緻大量的運動軌迹的出現。如果你沒有了解表面現象之後隐藏的運動定律的本質,你可能永遠無法開發出這樣一個系統。

關于架構和抽象的問題,在後面的文章中有一個測量模式的案例可以很形象的說明這個問題。

架構的一些誤解

我們花了一些篇幅來介紹架構的一些知識。現在回到我們的另一個主題上來。對于一個靈活開發過程,架構意味着什麼,我們該如何面對架構。這裡我們首先要澄清一些誤解:

  • 誤解1:架構設計需要很強的技術能力。從某種程度來說,這句話并沒有很大的錯誤。畢竟,你的能力越強,設計出優秀架構的幾率也會上升。但是能力和架構設計之間并沒有一個很強的聯系。即使是普通的程式設計人員,他一樣有能力設計出能實作目标的架構。
  • 誤解2:架構由專門的設計師來設計,設計出的藍圖交由程式員來實作。我們之是以會認為架構是設計師的工作,是因為我們喜歡把軟體開發和建築工程做類比。但是,這兩者實際上是有着很大的差別的。關鍵之處在于,建築設計已經有很長的曆史,已經發展出完善的理論,可以通過某些理論(如力學原理)來驗證設計藍圖。可是,對軟體開發而言,驗證架構設計的正确性,隻能夠通過寫代碼來驗證。是以,很多看似完美的架構,往往在實作時會出現問題。
  • 誤解3:在一開始就要設計出完善的架構。這種方式是最傳統的前期設計方式。這也是為XP所摒棄的一種設計方式。主要的原因是,在一開始設計出完美的架構根本就是在自欺欺人。因為這樣做的基本假設就是需求的不變性。但需求是沒有不變的(關于需求的細節讨論,請參看拙作『需求的實踐』)。這樣做的壞處是,我們一開始就限制了整個的軟體的形狀。而到實作時,我們雖然發現原來的設計有失誤之處,但卻不願意面對現實。這使得軟體畸形的生長。原本一些簡單的問題,卻因為别扭的架構,變得非常的複雜。這種例子我們經常可以看到,例如為相容前個版本而導緻的軟體複雜性。而2000年問題,TCP/IP網絡的安全性問題也從一個側面反映了這個問題的嚴重性。
  • 誤解4:架構藍圖交給程式員之後,架構設計師的任務就完成了。和誤解2一樣,我們借鑒了建築工程的經驗。我們看到建築設計師把設計好的藍圖交給施勞工員,施勞工員就會按照圖紙建造出和圖紙一模一樣的大廈。于是,我們也企圖在軟體開發中使用這種模式。這是非常要命的。軟體開發中缺乏一種通用的語言,能夠充分的消除設計師和程式員的溝通隔閡。有人說,UML不可以嗎?UML的設計理念是好的,可以減輕溝通障礙問題。可是要想完全解決這個問題,UML還做不到。首先,程式員都具有個性化的思維,他會以自己的思維方式去了解設計,因為從設計到實作并不是一項機械的勞動,還是屬于一項知識性的勞動(這和施勞工員的工作是不同的)。此外,對于程式員來說,他還極有可能按照自己的想法對設計圖進行一定的修改,這是非常正常的一項舉動。更糟的是,程式員往往都比較自負,他們會潛意識的排斥那些未經過自己認同的設計。

架構設計的過程模式

通常我們認為模式都是用在軟體開發、架構設計上的。其實,這隻是模式的一個方面。模式的定義告訴我們,模式描述了一個特定環境的解決方法,這個特定環境往往重複出現,制定出一個較好的解決方法有利于我們在未來能有效的解決類似的問題。其實,在管理學上,也存在這種類似的這種思維。稱為結構性問題的程式化解決方法。是以呢,我們完全可以把模式的思想用在其它的方面,而目前最佳的運用就是過程模式群組織模式。在我們的文章中,我們僅限于讨論過程模式。

我們讨論的過程僅限于面向對象的軟體開發過程。我們稱之為OOSP(object-oriented software process )。因為我們的過程需要面向對象特性的支援。當然,我們的很多做法一樣可以用在非OO的開發過程中,但是為了達到最佳的效果,我建議您使用OO技術。

那麼,我們應該如何避開這些誤區呢,或者,換句話說,靈活軟體開發是如何做架構設計的。這裡有幾種過程模式:

圖 2. 靈活架構過程模式概覽(High-Level)

在接下去的篇幅中,我們會逐一對各種過程模式進行介紹。然後再站在全局的角度分析各個模式之間的關系,并将之歸納為架構設計的模式。

靈活型架構設計

我們說我們這裡列出的過程模式是靈活型的,關于這一點我們會在接下去的各個章節中驗證這一點。我們列出的各個過程模式并不是完全照搬靈活型方法,因為在各種靈活型方法中,某些技巧适合架構設計,某些方法則不适合架構設計。是以,我們在采用一種方法和技術前,我們會問自己幾個簡單的問題:

  • 該方法/技巧有什麼價值?
  • 該方法/技巧需要多大的投入?從建立、維護、教育訓練等多方面估計。
  • 比較該方法/技巧的投入和價值,它還值得我們采用嗎?
  • 是否還有其它價值/投入比更高的方法/技巧呢?

在我們的文章中,每一種方法/技巧的讨論都回答了前三個問題,至于第四個問題,希望有同行能夠告訴我。

3.源自需求

我們說,和重型方法偏重于計劃、過程和中間産物不同,靈活方法更加看重人和溝通。人和溝通永遠是第一位的,而計劃、過程和中間産物,那隻是保證溝通、實作目标的手段。這并不是說計劃、過程、中間産物不重要,隻是不能夠本末倒置

注:我們把中間産物定義為為了實作跨邊界的溝通而制定的文檔、模型、代碼。例如設計文檔、資料模型等。參考RUP的Artifact。

評判軟體成功的标準有很多,對于靈活方法論來說,成功的标準首先在于傳遞可用的軟體。為了保證軟體的可用性,最重要的就是做好需求。做好需求的方法有很多(參見拙作需求的實踐),但這并不是我們讨論的主題。對于我們要開始的架構設計的工作來說,從需求出發來設計架構,這就是保證軟體可用性的一個基本的保證。

Context

我們如何開始我們的架構設計工作?

Problem

我們在進行架構設計的時候,往往主要考慮的都是平台、語言、開發環境、資料庫等一些基本問題,可是對于和客戶的具體情況密切相關的一些問題卻很少系統的考慮。甚至還存在一種誤區,認為架構設計無非就是寫一些空話,套話。這樣子做出來架構設計,如何用于指導軟體的實作呢?

IT界的技術層出不窮,面對着如此之多的技術、平台、架構、函數庫,我們如何選擇一組适合軟體的技術?

每一個客戶的軟體都有自身的特點,如何才能夠設計出符合客戶利益的架構?

軟體中往往都充斥着衆多的問題,在一開始就把所有的問題都想清楚往往很難做到,但是如果不解決問題,風險又居高不下。

Solution

針對需求設計架構。

架構設計就是鋪設軟體的主管道(例1)。我們根據什麼來制定主管道的粗細、路徑等因素呢?很明顯,是根據城市的人口、地理位置、水源等因素來決定的。對應到軟體設計也是一樣的。城市的各因素就是軟體中的各種需求:功能需求、非功能需求、變化案例等等。

一般來說,功能需求決定業務架構、非功能需求決定技術架構,變化案例決定架構的範圍。需求方面的知識告訴我們,功能需求定義了軟體能夠做些什麼。我們需要根據業務上的需求來設計業務架構,以使得未來的軟體能夠滿足客戶的需要。非功能需求定義了一些性能、效率上的一些限制、規則。而我們的技術架構要能夠滿足這些限制和規則。變化案例是對未來可能發生的變化的一個估計,結合功能需求和非功能需求,我們就可以确定一個需求的範圍,進而确定一個架構的範圍。

從例2中,我們看到自已字處理軟體的幾種需求的範例。真正的字處理軟體要複雜的多。而我們最主要的就是必須認識到,架構是來自于需求的。有什麼樣的需求就有什麼樣的架構。試想一下,如果我們沒有對速度的要求,我們還需要考慮這方面的設計嗎?我們上面提到了幾種類型的需求對架構的影響,其實還有一個很重要的需求,就是環境的需求。這并不是一個很重要的需求,但是對于部署(deployment)架構設計來說就特别重要。畢竟,我們開發出的軟體是要上"戰場"的,充分的考慮部署問題是非常有必要的。

例1:城市中自來水管的架設是一項非常的複雜的工程。為了需要滿足每家每戶的需要,自來水管組成了一個龐大的網絡。在這樣一個複雜的網絡中,如何完成鋪設的任務呢。一般的做法是,先找出問題的根源,也就是水的源頭。從水源鋪設一條管道通至城市,然後根據城市的區域劃分,設計出主管道,剩下的就是使用的問題了,每家每戶的管道最終都是連到主管道上的。是以,雖然自來水網絡龐大複雜。但是真正的主管道的非常簡單的。

例2:我們打算開發一個字處理軟體,功能需求可以簡單概括為格式化使用者輸入的文字,非功能需求可能是格式化大小為1000K的一段文字的處理速度不能低于10S,變化案例可能是推出多種語言版本。那麼我們在設計業務架構的時候,我們會集中于如何表示文字、圖象、媒體等要素,我們該需要有另外的技術架構來處理速度問題,比如緩沖技術,對于變化案例,我們也要考慮相應的架構,比如把字型獨立于程式包的設計。

從需求到架構。

在需求階段,我們可以得到一些代表需求調研成果的中間産物。比如說,CRC卡片、基本用例模型、使用者素材、界面原型、界面原型流程圖、非功能需求、變化案例等。我們在架構設計階段的主要工作就是要把這些需求階段的中間産物轉換為架構設計階段的中間産物。

圖 3. 需求階段的中間産物

其實,架構設計就是要完成兩項工作,一是分析,二是設計。分析是分析需求,設計則是設計軟體的大緻結構。很多的方法論把分析和設計兩種活動分開來,但其實這兩者是很難區分的,做分析的時候會想到如何設計,而思考如何設計反過來又會影響分析的效果。可以說,他們兩者之間是互相聯系和不斷疊代的。這種形态我們将會在後面的疊代設計模式中詳細的讨論。

在靈活方法論中,需求最好是疊代進行的,也就是說一點一點的作需求。這種做法在那些需求變化快的項目中尤其适用。由于我們采用的流程是一種疊代式的流程,這裡我們将會面臨着如何對待上一次疊代的中間産物的問題。如果我們每一次疊代都需要修改已存在的中間産物,那麼這種維護的成本未免過大。是以,靈活方法論的基本做法是,扔掉那些已經沒有用處的中間産物。還記得在第一章的時候,我們強調說軟體要比文檔重要。我們生成中間産物的目的都是為了生成最終的程式,對于這些已經完成作用的模型,沒有必要付出額外的維護成本。

不要斷章取義的采用抛棄模型的做法。因為,抛棄模型的做法需要一個适合環境的支援。後面會針對這個話題開展大範圍的讨論。這裡我們簡單的做一個了解:

  • 簡單化:簡單的模型和簡單的程式。模型和程式越複雜,就需要更多的精力來處理它們。是以,我們盡可能的簡化它們,為的是更容易的處理它們。
  • 高效的溝通管道:通過增強溝通的效果來減少對中間産物的需要。試想一下,如果我随時能夠從客戶那裡得到需求的細節資料,那前期的需求調研就沒有必要做的太細緻。
  • 角色的交叉輪換:開發人員之間建立起交換角色的機制,這樣,能夠盡量的避免各子系統諸侯割據的局面。
  • 清晰的流程:或者我們可以稱之為明确的過程。過程在方法論中向來都是一個重點,靈活方法論也不例外。開發人員能夠清楚的知道,今天做什麼,明天做什麼。過程不是給别人看的,而是給自己用的。
  • 工具:好用的工具能夠節省大量的時間,這裡的工具并不僅僅指CASE工具,還包括了版本控制工具、自動化測試工具、畫圖工具、文檔制作和管理工具。使用工具要注意成本和效益的問題。
  • 标準和風格:語言不通是溝通的一個很大的障礙。語言從某個角度來看屬于一種标準、一種風格。是以,一個團隊如果采用同樣的編碼标準、文檔标準、注釋風格、制圖風格,那麼這個團隊的溝通效率一定非常的高。

如果上述的環境你都不具備,或是欠缺好幾項,那你的文檔的模型還是留着的好。

僅針對需求設計架構

僅針對需求設計架構的含義就是說不要做未來才有用的事情。有時候,我們會把架構考慮的非常複雜,主要的原因就是我們把很多未來的因素放入到現在來考慮。或者,我們在開發第一個産品的時候就視圖把它做成一個完美的架構。以上的這兩種思路有沒有錯呢?沒有錯,這隻是如何看待投入的問題,有人希望開始的時候多投入一些,這樣後續的投入就會節省下來。但在現實中,由于需求的不确定性,希望通過增加開始階段的投入來将降低未來的投入往往是難以做到的,架構的設計也絕對不是能夠一蹴而就的,此這種做法并不是一個好的做法。是以我們在後頭會着重論述架構設計的簡單性和疊代過程,也就是因為這個理由。

模式

模式将可以幫助我們抓住重點。設計模式在書的一開始(第二章)就讨論了一個設計一個文檔編輯器的問題。為了解決設計文檔編輯器引出的七個問題,一共使用了8種不同的模式。這8種模式的組合其實就是架構,因為它們解決的,都是系統中最高層的問題。

在實踐中,人們發現架構也是存在模式的。比如,對于系統結構設計,我們使用層模式;對于分布式系統,我們使用代理模式;對于互動系統,我們使用MVC(模型-視圖-控制器)模式。模式本來就是針對特定問題的解,是以,針對需求的特點,我們也可以采用相應的模式來設計架構。

在sun網站上提供的寵物商店的範例中,就把MVC模式的思想擴充成為架構的思想,用于提供不同的界面視圖:

MVC架構圖,這裡提供原圖的概覽,檢視其出處請點選這裡。

我們可以了解到在圖的背後隐藏着的需求:系統需要支援多種使用者界面,包括為普通使用者提供的HTML界面,為無線使用者提供的WML界面,為管理者提供的Swing界面,以及為B2B業務設計的WebService界面。這是系統最重要的需求,是以,系統的設計者就需要确定一個穩定的架構,以解決多界面的問題。相對于多界面的問題,後端的業務處理邏輯都是一緻的。比如HTML界面和WML界面的功能并沒有太大的差别。把處理邏輯和界面分離開來還有額外的好處,可以在添加功能的同時,不涉及界面的改動,反之亦然。這就是我們在第二篇中提到的耦合度的問題。

MVC模式正可以适用于解決該問題。系統使用控制器來為業務邏輯選擇不同的界面,這就完成了MVC架構的設計思路。在架構設計的工作中,我們手頭上有模式這樣一張好牌,有什麼理由不去使用它呢?

抓住重點

在架構設計一開始,我們就說架構是一種抽象,那就是說,架構設計摒棄了具體的細節,僅僅抓住軟體最高層的概念,也就是最上層、優先級最高、風險最大的那部分需求。

我們考慮、分析、解決一個問題,一定有一個漸進的過程。架構設計就是解決問題其中比較早期的一個階段,我們不會在架構設計這個階段投入過多的時間(具體的原因在下文會有讨論),是以關鍵點在于我們要能夠在架構設計中把握住需求的重點。比如,我們在模式一節中提到了分布式系統和互動系統,分布和互動就是這兩個系統的重點。那麼,如果說我們面對的是一個分布式的互動系統,那麼,我們就需要把這兩種特性做為重點來考慮,并以此為基礎,設計架構。而我們提到的寵物商店的範例也是類似的,除了MVC的架構,還有很多的設計問題需要解決,例如用于資料庫通路的資料對象,用于視圖管理的前端控制器,等等(具體使用到的架構模式可以通路sun的網站)。但是這些相對于MVC模式來說,屬于局部的,優先級較低的部分,可以在架構确定後再來設計。

架構設計和領域專家

一個架構要設計的好,和對需求的了解是分不開的。是以在現實中,我們發現業務領域專家憑借着他對業務領域的了解,能夠幫助開發人員設計出優秀的架構來。架構是需要抽象的,它是現實社會活動的一個基本模型,而業務領域的模型僅僅憑開發人員是很難設計出來的。在ERP的發展史上,我們看到MRP發展為MRPII,在發展到閉環MRP,直到發展成為現在的ERP,主要的因素是管理思想的演化,也就是說,對業務領域的了解進步了,架構才有可能進步。

是以,靈活型架構設計的過程中,我們也非常強調領域專家的作用。

例3:信貸系統

在一個銀行的信貸帳務處理系統中,我們應該如何把握最初的架構思路呢?從需求上來看,這個信貸帳務處理系統有幾個特點:

它不是一個單獨的系統,它需要和外部的其它系統互動,例如信貸業務系統、網上銀行、資料倉庫系統等。

在所有的需求中,最複雜的就是它的利息計算的需求,它要求能夠支援多種的利息算法。

是以,我們的架構設計首先是從系統的全局環境開始考慮。其它系統和該系統的關系如何,應該如何設計系統間的接口。通過需求的調研,系統的接口分為4類:

和企業外部系統的接口。信貸系統需要連接配接到人民銀行的系統。

和企業内部系統的接口。信貸系統需要能夠為資料倉庫系統和網上銀行系統提供資料。

和平級系統的接口。信貸系統需要從平級的帳戶、資金、财務系統中取資料,并向帳戶、押彙系統發送資料。

具體的實作政策并不在我們的讨論範圍之内,但是可以看到,架構政策的制定是以需求為基礎的。我們可以把這部分的需求歸納為技術架構或平台架構。

然後是利息算法的問題,我們經過統計,目前的利息計算方式有四種,可預見到的還有兩種。在一開始的階段,我們并不需要考慮具體算法的實作,但是我們需要考慮算法的實作架構,是以我們很自然的想到Strategy模式可以勝任這一工作,把不同的利息算法封裝起來。而我們的工作重點也就轉到定義利息算法的接口問題。通過分析、比較多種利息算法,我們定義了一個最初始的算法接口,然後由不同的利息算法來實作算法接口。雖然,這個接口目前還不是很完整,但是它會在接下去的開發過程中慢慢的完善起來。這部分的需求屬于業務架構的一部分。

考慮到系統的結構非常的複雜,是以在系統結構的處理上,我們采用了層模式做為系統的基本結構。此外,在每個層,我們還定義了幾個子子產品來處理特定的問題。這樣,我們就可以将複雜的功能有序的組織起來。

經過上述的分析,我們對系統的架構有了一個簡單的認識,但是還沒有結束,一個架構應該包括系統的各個基本部分,是以,我們還要考慮票據處理、報表、帳務處理等環節,但是一開始就考慮周詳,這要花費大量的時間,是以我們隻是簡單的定義了一個原始的架構,然後在後續的開發過程中把這個架構完善起來。

4.團隊設計

團隊設計是靈活方法論中很重要的一項實踐。我們這裡說的團隊,指的并不是複數的人。一群人就是一群人,并沒有辦法構成團隊。要想成為團隊,有很多的工作要做。

我們之是以考慮以團隊為機關來考慮架構設計,是因為軟體開發本身就不是一件個人的事情,架構設計更是如此。單個人的思維不免有考慮欠妥之處,單個人的學識也不可能覆寫所有的學科。而組織有效的團隊卻能夠彌補這些缺憾。

Context

誰來負責架構的設計?

Problem

在我們的印象中,總認為架構設計是那些所謂架構設計師的專屬工作,他們往往擁有豐富的設計經驗和相關的技能,他們不用編寫代碼,就能夠設計出理論上盡善盡美的架構,配有精美的圖例。

問題1:理論上設計近乎完美的架構缺乏程式的證明,在實際應用中往往會出這樣那樣的問題。

問題2:設計師設計架構帶有很大的主觀性,往往會忽視客戶的需求,導緻架構無法滿足需求。

問題3:實作的程式員對這種架構有抵觸的情緒,或是因為不了解架構而導緻架構實作的失敗。

問題4:架構師設計架構主要是依據自己的大量經驗,設計出的架構不能真實的反映目前的軟體需要。

Solution

團隊設計的理論依據是群體決策。和個人決策相比,群體決策的最大好處就是其結論要更加的完整。而群體決策雖然有其優點,但其缺點也是很明顯的:需要額外付出溝通成本、決策效率低、責任不明确、等等。但是群體決策如果能夠組織得當的話,是能夠在架構設計中發揮很大的優勢的。

避免象牙塔式的架構設計

對軟體來說,架構設計是一項至關重要的工作。這樣的工作交給某個人是非常危險的。即便這個人再怎麼聰明,他也可能會遺漏部分的細節。組織有效的團隊的力量是大大超過個人的力量的,是以團隊的成果較之個人的成果,在穩定性和思考的周密程度上,都要更勝一籌。

Scott W. Ambler在其著作中給出了象牙塔式架構(ivory tower architecture)的概念:

An ivory tower architecture is one that is often developed by an architect or architectural team in relative isolation to the day-to-day development activities of your project team(s).

中國現在的軟體開發行業中也逐漸出現了象牙塔式的架構設計師。這些架構師并不參與實際的程式編寫,他的工作就是為項目制作出精美的架構模型,這種架構模型在理論上是相當完美的。

例1:在XP中,我們基本上看不到架構設計的影子。并不是說采用XP技術的團隊就不需要架構設計。XP不存在專門的設計時期,它提倡使用一些簡單的圖例、比喻的方式來表達軟體的架構,而這種的架構設計是無時無刻不在進行的。其實,XP中的設計采用的就是團隊設計的方式,結隊程式設計(Pair Programming)和代碼的集體所有制(Collective Ownership)是團隊設計的基礎,也就是基于口述的溝通方式。通過采用這樣的方式,XP幾乎不需要文檔來表達架構的設計。

優秀的架構師能夠充分的利用現有架構,減少軟體的投入,增強軟體的穩定性。這些都沒有錯,但是問題在于“過猶不及”。象牙塔式架構師往往會出現文章開始指出的那些問題。架構設計其實并不是非常複雜的工作,但它要求開發人員具備相關的技能、經驗以及對問題域有一定的了解。開發人員往往都具有相關的技術技能(程式設計、資料庫設計、模組化),而對問題域的了解可以從使用者和行業專家那裡獲得幫助。是以,在理論上,我們要實作架構設計的團隊化是完全可能的。

在上面的象牙塔式架構定義中,我們看到架構師和日常的開發工作是隔絕的。這樣的設計出的架構有很大的局限性。在現實中,我們還會發現另外一種角色,他來自于開發團隊外部,為開發人員提供相關的技術或業務的教育訓練。這種角色稱為教練,在軟體開發中是非常重要的角色,不能夠和象牙塔式架構設計師之間畫等号。

選擇你的設計團隊。

軟體的架構在軟體的生命周期的全過程中都很重要,也就是說,軟體開發團隊中的所有人員都需要和架構打交道。是以,最好的團隊組織方式是所有開發人員都參與架構的設計,我們稱這種方式為全員參與。全員參與的方式保證了所有開發人員都能夠對架構設計提出自己的見解,綜合多方面的意見,在全體開發人員中達成一緻。這種方式尤其适合于一些小的團隊。

還是會有很多的團隊由于種種的原因不适合采用全員參與的方式。那麼,組織優秀的開發人員組成設計組也是比較好的方式。一般,我們選擇那些在項目中比較重要的,有較多開發經驗,或是理論紮實的那些人來組成設計組。當然,如果你考慮到為組織培養後續力量,你也可以讓一些新手加入設計組,或是你覺得自己的開發力量不足,邀請外部的咨詢力量介入,這完全取決于具體的情況。

設計組不同于我們之前提到的象牙塔式架構設計師。設計組設計出來的架構隻能稱為原始架構,它是需要不斷的回報和改進的。是以,在架構實作中,設計組的成員将會分布到開發團隊的各個領域,把架構的思想帶給所有開發人員,編寫代碼來檢驗架構,并獲得具體的回報,然後所有的成員再集中到設計組中讨論架構的演進。

團隊設計中存在的問題

在團隊設計的過程,我們會遇到各種各樣的問題,首當其沖的就是溝通成本的問題。架構設計時,需求尚未被充分了解,軟體的設計思路還處于萌發的狀态。這樣的情況下,團隊的每位成員對軟體都有獨特的見解,這些可能有些是相同的,有些是互斥的。就好比盲人摸象一樣,他們的觀點都代表了軟體的一部分或是一方面,但是沒有辦法代表軟體的全部。

在靈活方法論中,我們的每一個流程都是迅速進行、不斷改進的。架構設計也是一樣,我們不可能在一次架構設計上花費更多的時間。而團隊決策總是傾向于較長的讨論和權衡。

例2中的問題在架構設計中時有發生,純技術的讨論很容易上升稱為争吵。這種情況幾乎沒有辦法完全避免。團隊型的決策必然會發生觀念的沖突。控制一定程度内的觀念的沖突對團隊的決策是有益,但是如果超出了這個程度就意味着失控了,需要團隊上司者的調節。而更重要的,我們需要注意溝通的技巧:

團隊溝通

團隊進行架構設計的時候溝通是一個非常需要注意的問題,上述的情境在軟體組織中是經常發生的,因為技術人員很自然認為自己的技術比别人的好,如果自己的技術受到質疑,那怕對方是抱着讨論的态度,也無異于自身的權威受到了挑戰,面子是無論如何都需要捍衛的。而溝通如果帶上了這樣一層主觀色彩,那麼溝通資訊的閱聽人就會潛意識的拒絕接受資訊。相反,他會找出對方話語中的漏洞,準備進行反擊。是以,我們要注意培養一種良好的溝通氛圍。

在實際的觀察中,我發現團隊溝通中存在兩種角色,一種是建議者,他們經常能夠提出建議。一種是質疑者,他們對建議提出否定性的看法。這兩種角色是可能互換的,現在的建議者可能就是剛才的質疑者。質疑者的發言是很能打擊建議者的積極性的,而在一個腦力激蕩的會議中,最好是大家都能夠扮演建議者的角色,這就要求溝通會議的主持者能夠掌握好這一點,對建議給予肯定的評價,并鼓勵大家提出新的建議。

例2:靈活方法非常注重的就是團隊的溝通。溝通是一個很有意思的話題,講起來會花費大量的時間,我們這裡隻是針對架構設計中可能存在的溝通問題做一個簡單的讨論。我們這裡假設一個讨論情境,這個情境來源于真實的生活:

項目主管徐輝、設計師李浩、設計師羅亦明正在讨論一個新的軟體架構。

"李浩你認為這個軟體資料庫連接配接部分應該如何考慮?"徐輝問。

李浩想了想,"我覺得方案A不錯…" "方案A肯定有問題!這個軟體和上一次的又不同。"羅亦明打斷了李浩的發言。

"你懂什麼!你到公司才多久,方案A是經過很長時間的證明的!"發言被打斷,李浩有點惱火,羅亦明進入公司沒有多久,但在一些事情上老是和他唱反調。

"我進公司多久和方案A的錯誤有什麼關系!"

在這樣一種氛圍中,會議的結果可想而知。

良好的溝通有助于架構設計工作的開展。一個成員的能力平平的團隊,可以藉由良好的溝通,設計出優秀的架構,而一個擁有一個優秀成員的團隊,如果缺乏溝通,最後可能連設計都出不來。這種例子現實中可以找到很多。

标準和風格

我們總是在不知不覺之中使用各種各樣的标準和風格。在團隊設計中,我們為了提高決策的效率,可以考慮使用統一的标準和風格。統一的标準和風格并不是一朝一夕形成的。因為每個人都有自己不同的習慣和經曆,強制性的要求開發人員使用統一的标準(風格)容易引起開發人員的不滿。是以在操作上需要注意技巧。對架構設計而言,比較重要的标準(風格)包括以下的這些類别:

  • 界面設計
  • 流程設計
  • 模組化規範
  • 編碼規範
  • 持久層設計
  • 測試資料

在我的經驗中,有一些組織平時并不注意标準(風格)的積累,認為這種積累屬于雕蟲小技,但正是這些小技,能夠非常有效的提高溝通的效率和降低開發人員的學習曲線。試想一下,如果一個團隊中所有人寫出的代碼都是不同标準和風格的,那麼了解起來肯定會困難許多。當然,我們沒有必要自己開發一套标準(風格)出來,現實中有很多可以直接借用的資料。最好的标準是UML語言,我們可以從UML的官方網站下載下傳到最新的規範,常用的編碼标準更是随處可見。不過雖然有了統一的标準,如果風格不統一,同樣會造成溝通的障礙。例如下圖顯示的類圖,雖然它們表示的是同一個類,但是由于版型、可視性、詳細程度的差别,看起來又很大的差别。而在其它的标準中,這種差别也是普遍存在的。是以,我們在使用了統一的标準之後,還應該使用同樣的風格。Scott W. Ambler專門成立了一個網站讨論UML的模組化風格的相關問題,有興趣的讀者可以做額外的閱讀。

圖 4. 兩種風格的類圖

在統一的風格的基礎上更進一步的是使用術語。使用溝通雙方都了解專門的術語,可以代表大量的資訊。最好的術語的範例就是設計模式的模式名。如果溝通的雙方都了解設計模式,那麼一方隻需要說這部分的設計可以使用工廠模式,另一方就能夠了解,而不用再詳細的解釋設計的思路。這種的溝通方式是最高效的,但它所需要的學習曲線也會比較陡。

團隊設計的四明确

為了最大程度的提高團隊設計的高效性,可以從4個方面來考慮:

1、明确目标

泛泛的召開架構讨論會議是沒有什麼意義的,一個沒有鮮明主題的會議也不會有什麼結果。在源自需求的模式中,我們談到說可以有非功能需求的架構,可以有功能需求的架構。是以,在進行團隊設計之前,我們首先也需要确定,此次要解決什麼問題,是讨論業務邏輯的架構,還是技術架構;是全局性的架構,還是各子產品的架構。

2、明确分工

我們之是以重視團隊,很重要的額一個原因就是不同的成員有不同的擅長的區域。有些成員可能擅長于業務邏輯的模組化,有的擅長于原型設計,有的擅長于資料庫設計,有的則擅長于Web程式設計。你能夠想象一個軟體沒有界面嗎?(有些軟體可能是這種情況)你能夠想象一個軟體隻有資料庫,而沒有處理邏輯嗎?是以,架構設計就需要綜合的考慮各個方面,充分利用成員的優勢。這就要求團隊的各個成員都能夠明确自己的分工。

3、明确責權

除了明确自己的分工,每位成員都需要清楚自己的責任。沒有責任,分工就不會有任何的效力。每位成員都需要明确自己要做些什麼。當然,和責任相對的,沒有成員還需要知道自己的權力是什麼。這些清楚了,進行高效的溝通的前提就具備了。每次架構的讨論下來,每個人都清楚,自己要做些什麼,自己需要要求其他人做些什麼,自己該對誰負責。如果這些問題回答不了,那這次的讨論就白費了。

4、明确溝通方式

這裡使用溝通方式可能有一點點不恰當,為了明确的表達意思,大家可以考慮資訊流這個詞。一個完整架構包括幾個方面,分别都由那些人負責,如何産生,産生的整個過程應該是什麼樣的?這樣的一個資訊流程,囊括了上面提到的三個明确。如果團隊的每一個人都能夠為架構的産生而努力,并順利的設計出架構,那麼這樣的流程是完美的。如果你發現其中的一些人不知道做些什麼,那麼,這就是流程出問題的現象了。完美的流程還會有一個額外的副産品,架構産生之後,團隊對于軟體的設計已經是非常的清晰了。因為我們提倡的是盡可能多的開發人員參與架構的設計。

不僅僅是架構

讨論到這裡,其實有很多的内容已經脫離了架構設計了。也就是說,很多的原則和技巧都是可以用于軟體開發的其它活動的。至于哪一些活動能夠利用這些方法呢?大家可以結合自己的實際情況,來思考這個問題。提示一點,關鍵的入手處在于目前效率較低之處。

5.簡單設計

XP非常強調簡單的設計原則:能夠用數組實作的功能決不用連結清單。在其它Agile方法中,簡單的原則也被反複的強調。在這一章,我們就對簡單性做一個全面的了解。

Context

架構應該設計到什麼程度?

Problem

軟體的架構都是非常的複雜的,帶有大量的文檔和圖表。開發人員花在了解架構本身上的時間甚至超出了實作架構的時間。在前面的文章中,我們提到了一些反對象牙塔式架構的一個原因,而其中的一個原因就是象牙塔式架構的設計者往往在設計時參雜進過多的自身經驗,而不是嚴格的按照需求來進行設計。

在軟體開發領域,最為常見的設計就是"Code and Fix"方式的設計,設計随着軟體開發過程而增長。或者,我們可以認為這種方式根本就不能算是設計,它抱着一種船到橋頭自然直的态度,可是在設計不斷改動之後,代碼變得臃腫且難以了解,到處充滿着重複的代碼。這樣的情形下,架構的設計也就無從談起,軟體就像是在風雨中的破屋,瀕臨倒塌。

針對于這種情形,新的設計方式又出現了,Martin Fowler稱這種方式為"Planned Design"。和建築的設計類似,它強調在編碼之前進行嚴格的設計。這也就是我們在團隊設計中談到的架構設計師的典型做法。設計師們通常不會去程式設計,理由是在土木工程中,你不可能看到一位設計師還要砌磚頭。

"Planned Design"較之"Code and Fix"進步了許多,但是還是會存在很多問題。除了在團隊設計中我們談的問題之外,需求變更将會導緻更大的麻煩。是以,我們理所當然的想到進行"彈性設計":彈性的設計能夠滿足需求的變更。而彈性的設計所付出的代價就是複雜的設計。

題外話:

這裡我們談論"Planned Design"引出的一些問題,并沒有任何排斥這種方式的意思。"Planned Design"還是有很多可取之處的,但也有很多需要改進的地方。事實上,本文中我們讨論的架構設計方式,本質上也是屬于"Planned Design"方式。和"Planned Design"相對應的方式是XP所主張的"Evolutionary Design"方式,但是這種方式還有待于實踐的檢驗,并不能簡單的說他就一定要比"Planned Design"先進或落後。但可以肯定的一點是:"Evolutionary Design"方式中有很多的思想和技巧是值得"Planned Design"借鑒的。

Solution

XP中有兩個非常響亮的口号:"Do The Simplest Thing that Could Possibly Work"和"You Aren't Going to Need It"(通常稱之為YAGNI)。他們的核心思想就是不要為了考慮将來,把目前并不需要的功能加到軟體中來。

粗看之下,會有很多開發人員認為這是不切實際的口号。我能了解這種想法,其實,在我熱衷于模式、可重用元件技術的時候,我對XP提倡的簡單的口号嗤之以鼻。但在實際中,我的一些軟體因為複雜設計導緻開發成本上升的時候,我重新思考這個問題,發現簡單的設計是有道理的。

降低開發的成本

不論是模式,可重用元件,或是架構技術,目的都是為了降低開發的成本。但是他們的方式是先進行大量的投入,然後再節省後續的開發成本。是以,架構設計方面的很多思路都是圍繞着這種想法展開的,這可能也是導緻開發人員普遍認為架構設計高不可攀的原因。

XP的方式恰恰相反,在處理第一個問題的時候,不必要也不可能就設計出具有彈性、近乎完美的架構來。這項工作應該是随着開發的演進,慢慢成熟起來的。我不敢說這種方式肯定正确,但是如果我們把生物的結構視同為架構,這種方式不是很類似于自然界中生物的進化方式嗎?

在一開始就制作出完美的架構的設想并沒有錯,關鍵是很難做到這一點。總是會有很多的問題是你在做設計時沒有考慮到的。這樣,當一開始花費大量精力設計出的"完美無缺"的架構必然會遇到意想不到的問題,這時候,複雜的架構反而會影響到設計的改進,導緻開發成本的上升。這就好比如果方向錯了,交通工具再快,反而導緻錯誤的快速擴大。Martin Fowler在他的論文中說,"Working on the wrong solution early is even more wasteful than working on the right solution early"(提前做一件錯事要比提前做一件對的事更浪費時間),相信也是這個道理。

更有意思的是,通常我們更有可能做錯。在我們進行架構設計的時候,我們不可能完全取得詳細的需求。事實上,就算你已經取得了完整的需求,也有可能發生變化。這種情況下做出的架構設計是不可能不出錯的。這樣,浪費大量的時間在初始階段設計不可能達到的"完美架構",倒不如把時間花在後續的改進上。

提升溝通的效率

我們在團隊設計中已經談過了團隊設計的目标之一就是為了降低溝通的成本,以期讓所有人都能夠了解架構。但是如果架構如果過于複雜,将會重新導緻溝通成本的上升,而且,這個成本并不會随着項目進行而降低,反而會因為上面我們提到的遇到新的問題導緻溝通成本的持續上升。

簡單的架構設計可以加快開發團隊了解架構的速度。我們可以通過兩種方式來了解簡單的含義。首先,簡單意味着問題的解不會非常的複雜,架構是解決需求的關鍵,無論需求再怎麼複雜多變,總是可以找出簡單穩定的部分,我們可以把這個簡單穩定的部分做為基礎,再根據需要進行改進擴充,以解決複雜的問題。在示例中,我們提到了measurement pattern,它就是按照這種想法來進行設計的。

其次,簡單性還展現在表示的簡單上。一份5頁的文檔就能夠表達清楚的架構設計為什麼要花費50頁呢?同樣的道理,能夠用一副簡單的圖形就能夠表示的架構設計也沒有必要使用文檔。畢竟,面對面的溝通才是最有效率的溝通,文檔不論如何的複雜,都不能被完全了解,而且,複雜的文檔,維護起來也需要花費大量的時間。隻有在兩種情況下,我們提倡使用複雜的文檔:一是開發團隊沒有辦法做到面對面溝通;二是開發成果要作為團隊的知識積累起來,為下一次開發所用。

考慮未來

我們之是以考慮未來,主要的原因就是需求的不穩定。是以,我們如果考慮未來可能發生的需求變化,就會不知覺的在架構設計中增加複雜的成分。這違背的簡單的精神。但是,如果你不考慮可能出現的情況,那些和目前設計格格不入的改變,将會導緻大量的返工。

還記得YAGNI嗎?原則上,我們仍然堅持不要在現有的系統中為将來可能的情況進行設計。但是,我們必須思考,必須要為将來可能出現的情況做一些準備。其實,軟體中了不起的接口的思想,不就是源于此嗎?是以,思考未來,但等到需要時再實作。

變更案例有助于我們思考未來,變更案例就是你在将來可能要(或可能不要)滿足的,但現在不需要滿足的需求。當我們在做架構設計的時候,變更案例也将會成為設計的考慮因素之一,但它不可能成為進行決策的唯一考慮因素。很多的時候,我們沉迷于設計通用系統給我們帶來的挑戰之中,其實,我們所做的工作對使用者而言是毫無意義的。

架構的穩定

架構簡單化和架構的穩定性有什麼關系嗎?我們說,架構越簡單,其穩定性就越好。理由很簡單,1個擁有4個方法和3個屬性的類,和1個擁有20個方法和30屬性的類相比,哪一個更穩定?當然是前者。而架構最終都是要映射到代碼級别上的,是以架構的簡單将會帶來架構的穩定。盡可能的讓你的類小一些,盡可能的讓你的方法短一些,盡可能的讓類之間的關系少一些。這并不是我的忠告,很多的設計類的文章都是這麼說的。在這個話題上,我們可以進一步的閱讀同類的文章(關于 refactoring 的思考)。

辨正的簡單

是以,對我們來說,簡單的意義就是不要把未來的、或不需要實作的功能加入到目前的軟體中,相應的架構設計也不需要考慮這些額外的需求,隻要剛好能夠滿足目前的需求就好了。這就是簡單的定義。可是在現實之中,總是有這樣或者那樣的原因,使得設計趨向複雜。一般來說,如果一個設計對團隊而言是有價值的,那麼,付出一定的成本來研究、驗證、發展、文檔化這個設計是有意義的。反之,如果一個設計沒有很大的價值或是發展它的成本超過了其能夠提供的價值,那就不需要去考慮這個設計。

價值對不同的團隊來說具有不同的含義。有時候可能是時間,有時候可能是使用者價值,有時候可能是為了團隊的設計積累和代碼重用,有時候是為了獲得經驗,有時候是為了研究出可重用的架構(FrameWork)。這些也可以稱為目的,是以,你在設計架構時,請注意先确定好你的目的,對實作目的有幫助的事情才考慮。

Scott W.Ambler在他的文章中提到一個他親身經曆的故事,在軟體開發的架構設計過程中,花了很多的時間來設計資料庫到業務邏輯的映射架構,雖然這是一件任何開發人員都樂意專研的事情(因為它很酷)。但他不得不承認,對使用者來說,這種設計先進的架構是沒有太大的意義的,因為使用者并不關心具體的技術。當看到這個故事的時候,我的觸動很大。一個開發人員總是熱衷于新奇的技術,但是如果這個新奇技術的成本由使用者來承擔,是不是合理呢?雖然新技術的采用能夠為使用者帶來效益,但是沒有人計算過效益背後的成本。就我開發過的項目而言,這個成本往往是大于效益的。這個問題可能并沒有确定的答案,隻能是見仁見智了。

簡單并不等于實作簡單

說到這裡,如果大家有一個誤解,認為一個簡單的架構也一定是容易設計的,那就錯了。簡單的架構并不等于實作起來也簡單。簡單的架構需要設計者花費大量的心血,也要求設計者對技術有很深的造詣。在我們正在進行的一個項目中,一開始設計的基礎架構在實作中被修改了幾次,但每修改一次,代碼量都減少一分,代碼的可讀性也就增強一分。從心理的角度上來說,對自己的架構進行不斷的修改,确實是需要一定的勇氣的。因為不論是設計還是代碼,都是開發人員的心血。但跨出這一步是值得的。

右側的例子讨論了Java的IO設計,Java類庫的設計應該來說是非常優秀的,但是仍然避免不了重新的修改。實際上,在軟體開發領域,由于原先的設計失誤而導緻後來設計過于複雜的情況比比皆是(例如微軟的OLE)。同樣的,我們在設計軟體的時候,也需要對設計進行不斷的修改。能夠實作複雜功能,同時自身又簡單的設計并不是一件容易的事情。

簡單設計需要什麼樣的設計師

簡單的架構需要全面的設計師。什麼才是全面的設計師,我的定義是既能夠設計,又能夠編碼。我們在團隊設計模式中就已經談過象牙塔式架構和象牙塔式架構設計師。他們最容易犯的一個毛病就是設計和代碼的脫離。從我們自己的經驗來看,即使在設計階段考慮的非常完美的架構,在編碼階段也會出現這樣或那樣的問題。進而導緻架構實作變得複雜。最明顯的特征就是在編碼時出現了有大量方法的類,或是方法很長的類。這表明架構和代碼脫鈎了。在我們的開發過程中,不隻一次出現這種現象,或者說,出現了壞味道(Bad Smell)。Refactoring的技巧也同樣有助于識别壞味道。

一次的架構設計完成後,開發人員可以按照設計,快速的程式設計。可在一段時間之後,新的特色不斷的加入,我們發現代碼開始混亂,代碼量增大,可讀性下降,調試變得困難,代碼不可控制的征兆開始出現。我們就知道,架構的設計需要調整了。這屬于我們在後面所提到的Refactoring模式。而我們在這裡要說的是,如果架構的設計師不參與編碼,它是無法感受到壞味道的,是以也就不會主動的對設計進行改進。要解決這個問題,最好的辦法是讓設計師參與代碼的編寫,尤其是重要架構的現實部分需要設計師的參與。如果設計師沒有辦法參與編碼,那就需要一種機制,能夠把代碼回報給設計師,讓他在适當的時候,重新考慮改進架構。一個可能的辦法是Code Review。讓設計師稽核代碼,以確定編碼者真正了解了架構設計的意圖。

例1.Java的IO系統

從Java的IO系統設計中,我們可以感受到簡單設計的困難。

IO系統設計的困難性向來是公認的。Java的IO設計的一個目的就是使IO的使用簡單化。在Java的1.0中,Java的IO系統主要是把IO系統分為輸入輸出兩個大部分,并分别定義了抽象類InputStream和OutputStream。從這兩個的抽象類出發,實作了一系列不同功能的輸入輸出類,同時,Java的IO系統還在輸入輸出中實作了FilterInputStream和FilterOutputStream的抽象類以及相關的一系列實作,進而把不同的功能的輸入輸出函數連接配接在一起,實作複雜的功能。這個實作其實是Decorator模式(由于沒有看過源碼和相關的資料,這裡僅僅是根據功能和使用技巧推測,如果大家有不同的意見,歡迎來信讨論)。

是以,我們可以把多個對象疊加在一起,提供複雜的功能:

DataInpuStream in =

new DataInputStream(

new BufferedInputStream(

new FileInputStream("test.txt");

上面的代碼使用了兩個FilterInputStream:DataInpuStream和BufferedInputStream,以實作讀資料和緩沖的功能,同時使用了一個InputStream:FileInputStream,從檔案中讀取流資料。雖然使用起來不是很友善,但是應該還是非常清晰的設計。

令設計混亂的是既不屬于InputStream,也不屬于OutputStream的類,例如RandomAccessFile,這正表明,由于功能的複雜化,使得原先基于輸入輸出分類的設計變得混亂,根據我們的經驗,我們說設計需要Refactoring了。是以,在Java1.1中,IO系統被重新設計,采用了Reader和Writer位基礎的設計,并增加了新的特性。但是目前的設計似乎更加混亂了,因為我們需要同時使用1.0和1.1兩種不同的IO設計。

例2. measurement pattern

在分析模式一書中有一個measurement pattern(測量模式),原來它是為了要解決現實中各種各樣紛繁複雜的可測量的屬性。例如,一個醫療系統中,可能會有身高多高,體重多種,血壓多少等上千種可測量的屬性。如果分别表示它們,必然導緻系統複雜性的上升。是以measurement pattern就從這些屬性的可測量的共性出發,研究新的解決方法,提出了measurement pattern的想法:

如圖所示,把可測量的屬性(Measurement)做為Phenomenon Type的執行個體,此外,每一個的Person可以擁有多個的Measurement,同時,Measurement還對應處理的屬性,例如圖中的Quantity,就表示了Measurement的數量和機關。比如,一個人的體重是65公斤,那麼,Phenomenon Type就是體重,Quantity的amount是65,units是公斤。

圖 5.牋 measurement pattern 的類圖

這其實是一個很簡單的設計,但它清楚的表示了屬性之間的關系,簡化了數千種的屬性帶來的複雜性。此外,我們進一步思考,就會發現,這種架構隻是針對目前出現屬性衆多的問題的基本解決方法,它還可以根據具體的需要進行擴充,例如,實作動态添加機關,或實作不同機關的轉化等問題。

是以,我們這裡展示的其實是一種思考的方法,假想一下,當你在面對一個複雜的醫療系統時,大量的屬性和不同的處理方式,你是不是可以從這樣複雜的需求中找出簡單的部分來呢?在我們架構設計的第一篇中,我們談到架構設計的本質在于抽象,這裡例子就是最典型的一個例子,在我們傳統的想法中,我們都會把身高、體重等概念做為屬性或是類,但是為了滿足這裡的需求,我們對這些具體的概念做一個抽象,提出可測量類别的概念,并把它設計為類(Phenomenon Type),而把具體的概念做為執行個體。這種抽象的思想在軟體設計中無處不在,例如元類的概念。

更深入的了解

下一章中我們将會讨論疊代設計,其中還會涉及到簡單設計的相關知識。建議可以将兩章的内容結合起來看。

6.疊代設計

疊代是一種軟體開發的生命周期模型,在設計中應用疊代設計,我們可以得到很多的好處。

Context

在軟體生命周期中,我們如何對待架構設計的發展?

Problem

架構設計往往發生在細節需求尚未完成的時候進行的。是以,随着項目的進行,需求還可能細化,可能變更。原先的架構肯定會有不足或錯誤的地方。那麼,我們應該如何對待原先的設計呢?

我們在簡單設計模式中簡單提到了"Planned Design"和"Evolutionary Design"的差別。XP社團的人們推崇使用"Evolutionary Design"的方式,在外人看來,似乎擁護者們從來不需要架構的設計,他們采用的方式是一開始就進入代碼的編寫,然後用Refactoring來改進代碼的品質,解決未經設計導緻的代碼品質低下的功能。

從一定程度上來說,這個觀點并沒有錯,它強調了代碼對軟體的重要性,并通過一些技巧(如Refactoring)來解決缺乏設計的問題。但我并不認同"Evolutionary Design"的方式,在我看來,一定程度上的"Planned Design"是必須的,至少在中國的軟體行業中,"Planned Design"還沒有成為主要的設計方向。借用一句明言,"凡事預則立,不預則廢",在軟體設計初期,投入精力進行架構的設計是很有必要的,這個架構是你在後續的設計、編碼過程中依賴的基礎。但是,一開始我們提到的設計改進的問題依然存在,我們如何解決它呢?

在簡單設計模式中,我們提到了設計改進的必要性,但是,如果沒有一種方法去控制設計的改進的話,那麼設計改進本身就是一場噩夢。是以,何時改進,怎麼改進,如何控制,這都是我們需要面對的問題。

Solution

為了實作不斷的改進,我們将在開發流程中引入疊代的概念。疊代的概念在我的另一篇文章--《需求的實踐》中已經提到,這裡我們假設讀者已經有了基本的疊代的概念。

軟體編碼之前的工作大緻可以分為這樣一個工作流程:

上圖中的流程隐含着一個資訊的損失的過程。來自于使用者的需求經過整理之後,開發人員就會從中去掉一些資訊,同樣的事情發生在後面的過程中,資訊丢失或變形的情況不斷的發生。這裡發生了什麼問題?應該說,需求資訊的失真是非常普遍的,我們缺少的是一種有效的辦法來抑止失真,換句話說,就是缺少回報。

如果把眼睛蒙上,那我們肯定沒有辦法走出一條很長的直線。我們走路的時候都是針對目标不斷的調整自己的方向的。同樣的,漫長的軟體開發過程如果沒有一種回報機制來調整方向,那最後的軟體真是難以想象。

是以我們引入了疊代周期。

初始設計和疊代設計

在團隊設計中,我們一直在強調,設計組最開始得到的設計一定隻是一個原始架構,然後把這個原始架構傳播到每一位開發者的手中,進而在開發團隊中形成共同的願景。(願景(Vision):源自于管理學,表示未來的願望和景象。這裡借用來表示軟體在開發人員心中的樣子。在後面的文章中我們會有一個章節專門的讨論架構願景。)

疊代(Iterate)設計,或者我們稱之為增量(Incremental)設計的思想和XP提倡的Evolutionary Design有異曲同工之妙。我們可以從XP、Crystal、RUP、ClearRoom等方法學中對比、體會疊代設計的精妙之處:每一次的疊代都是在上一次疊代的基礎上進行的,疊代将緻力于重用、修改、增強目前的架構,以使架構越來越強壯。在軟體生命周期的最後,我們除了得到軟體,還得到了一個非常穩定的架構。對于一個軟體組織來說,這個架構很有可能就是下一個軟體的投入或參考。

我們可以把早期的原始架構當作第一次疊代前的早期投入,也可以把它做為第一次疊代的重點,這些都是無所謂的。關鍵在于,原始架構對于後續的架構設計而言是非常重要的,我們讨論過架構是來源于需求的,但是原始架構應該來源于那些比較穩定的需求。

TIP:現實中疊代設計退化為"Code and Fix"的設計的情況屢見不鮮("Code and Fix"參見簡單設計)。從表面上看,兩者的做法并沒有太大的差别,都是針對原有的設計進行改進。但是,二者效果的差别是明顯的:"Code and Fix"是混沌的,毫無方向感可言,每一次的改進隻是給原先就已搖搖欲墜的積木上再加一塊積木而已。而疊代設計的每一次改進都朝着一個穩定的目标在前進,他給開發人員帶來信心,而不是打擊。在過程上,我們說疊代設計是在控制之下的。

從實踐的經驗中,我們發現,把原該在目前就該解決的問題退後是造成這一問題的主要原因之一。是以,請嚴格的對待每一次的疊代,確定計劃已經完成、確定軟體的品質、確定使用者的需求得到滿足,這樣才是正統的疊代之路。

單次的疊代

我們說,每一次的疊代其實是一個完整的小過程。也就是說,它同樣要經曆文章中讨論的這些過程模式。隻不過,這些模式的工作量都不大,你甚至可以在很短的時間内做完所有的事情。是以,我們好像又回到了文章的開頭,重新讨論架構設計的過程。

單次疊代最令我們興奮的就是我們總是可以得到一個在目前疊代中相當穩定的結果,而不像普通的架構設計那樣,我們深怕架構會出現問題,但又不得不依賴這個架構。從我們的心理上來分析,我們是在持續的建設架構中,我們不需要回避需求的變更,因為我們相信,在需求相對應的疊代中,我們會繼續對架構進行改進。大家不要認為這種心理的改變是無關緊要的,我起初并沒有意識到這個問題,但是我很快發現新的架構設計過程仍然籠罩在原先的懼怕改變的陰影之下的時候,疊代設計很容易就退化為"Code and Fix"的情形。開發人員難以接受新方法的主要原因還是在心理上。是以,我不得不花了很多的時間來和開發人員進行溝通,這就是我現實的經驗。

疊代的交錯

基于我們對運籌學的一點經驗,疊代設計之間肯定不是線性的關系。這樣說的一個原因架構設計和後續的工作間還是時間差的。是以,我們不會傻到把時間浪費在等待其它工作上。一般而言,當下一次疊代的需求開始之後,詳細需求開始之前,我們就已經可以開始下一次疊代的架構設計了。

各次疊代之間的時間距離要視項目的具體情況而定。比如,人員比較緊張的項目中,主要的架構設計人員可能也要擔任編碼人員的角色,下一次疊代的架構設計就可能要等到編碼工作的高峰期過了之後。可是,多次的交錯疊代就可能産生版本的問題。比如,本次的疊代的編碼中發現了架構的一個問題,回報給架構設計組,但是架構設計組已經根據僞修改的本次疊代的架構開始了下一次疊代的架構設計,這時候就會出現不同的設計之間的沖突問題。這種情況當然可以通過加強對設計模型的管理和引入版本控制機制來解決,但肯定會随之帶來管理成本上升的問題,而這是不符合靈活的思想的。這時候,團隊設計就展現了他的威力了,這也是我們在團隊設計中沒有提到的一個原因。團隊設計通過完全的溝通,可以解決架構設計中存在沖突的問題。

疊代頻率

XP提倡疊代周期越短越好(XP建議為一到兩周),這是個不錯的提議。在這麼短的一個疊代周期内,我們花在架構設計上的時間可能就隻有一兩個小時到半天的時間。這時候,會有一個很有意思的現象,你很難去區分架構設計和設計的概念了。因為在這麼短的一個周期之内,完成的需求數量是很少的,可能就隻有一兩個用例或使用者素材。是以,這幾項需求的設計是不是屬于架構設計呢?如果是的話,由于開發過程是由多次的疊代組成的,那麼開發過程中的設計不都屬于架構設計了嗎?我們說,架構是一個相對的概念,是針對範圍而言的,在傳統的瀑布模型中,我們可以很容易的區分出架構設計和普通設計,如果我們把一次疊代看作是一個單獨的生命周期,那麼,普通的設計在這樣一個範圍之内也就是架構設計,他們并沒有什麼兩樣。但是,疊代周期中的架構設計是要遵循一定的原則的,這我們在下面還會提到。

我們希望疊代頻率越快越好,但是這還要根據現實的情況而定。比如資料倉庫項目,在項目的初期階段,我們不得不花費大量的時間來進行資料模組化的工作,這其實也是一項專門針對資料的架構設計,建立中繼資料,制定維,整理資料,這樣子的過程很難分為多次的疊代周期來實作。

如何确定軟體的疊代周期

可以說,如果一支開發團隊沒有相關疊代的概念,那麼這支團隊要立刻實作時隔兩周疊代周期是非常困難的,,同時也是毫無意義的。就像我們在上面讨論的,影響疊代周期的因素很多,以至于我們那無法對疊代周期進行量化的定義。是以我們隻能從定性的角度分析疊代周期的發展。

另一個了解疊代的方法是閱讀XP的相關資料,我認為XP中關于疊代周期的使用是很不錯的一種方法,隻是他強調的如此短的疊代周期對于很多的軟體團隊而言都是難以實作的。

疊代周期的引入一定是一個從粗糙到精确的過程。疊代的本質其實是短周期的計劃,是以這也是疊代周期越短對我們越有好處的一大原因,因為時間縮短了,計劃的可預測性就增強了。我們知道,計劃的制定是依賴于已往的經驗,如果原先我們沒有制定計劃或細節計劃的經驗,那麼我們的計劃就一定是非常粗糙,最後的誤差也一定很大。但是這沒有關系,每一次的計劃都會對下一次的計劃産生正面的影響,等到經驗足夠的時候,計劃将會非常的精确,最後的誤差也會很小。

疊代周期的确定需要依賴于機關工作量。機關工作量指的是一定時間内你可以量化的最小的績效。最簡單的機關工作量是每位程式員一天的編碼行數。可惜顯示往往比較殘酷,團隊中不但有程式員的角色,還有設計師、測試人員、文檔制作人員等角色的存在,單純的編碼行數是不能夠作為唯一的統計依據的。同樣,隻強調編碼行數,也會導緻其它的問題,例如代碼品質。為了保證統計的合理性,比較好的做法是一個團隊實作某個功能所花費的天數作為機關工作量。這裡讨論的内容實際是軟體測量技術,如果有機會的話,再和大家探讨這個問題。

疊代周期和軟體架構的改進

我們應用疊代方法的最大的目的就是為了穩步的改進軟體架構。是以,我們需要了解架構是如何在軟體開發的過程中不斷演進的。在後面的文章中,我們會談到用Refactoring的方法來改進軟體架構,但是Refactoring的定義中強調,Refactoring必須在不修改代碼的外部功能的情況下進行。對于架構來說,我們可以近乎等價的認為就是在外部接口不變的情況下對架構進行改進。而在實際的開發中,除非非常有經驗,否則在軟體開發全過程中保持所有的軟體接口不變是一件非常困難的事情。是以,我們這裡談的架構的改進雖然和Refactoring有類似之處,但還是有差別的。

軟體架構的改進在軟體開發過程會經曆一個振蕩期,這個振蕩期可能橫跨了數個疊代周期,其間架構的設計将會經曆劇烈的變化,但最後一定會取向于平穩。(如果項目後期沒有出現設計平穩化的情況,那麼很不幸,你的項目注定要失敗了,要麼是時間的問題,要麼就是需求的問題)。關鍵的問題在于,我們有沒有勇氣,在架構需要改變的時候就毅然做出變化,而不是眼睜睜的看着問題變得越來越嚴重。最後的例子中,我們讨論三個疊代周期,假設我們在第二個周期的時候拒絕對架構進行改變,那麼第三個周期一定是有如噩夢一般。變化,才有可能成功。

我們知道變化的重要性,但沒有辦法知道變化的确切時間。不過我們可以從開發過程中嗅到架構需要變化的氣味:當程式中重複的代碼逐漸變多的時候,當某些類變得格外的臃腫的時候,當編碼人員的編碼速度開始下降的時候,當需求出現大量的變動的時候。

例子:

從這一周開始,我和我的小組将要負責對軟體項目中的表示層的設計。在這個疊代周期中,我們的任務是要為用戶端提供6到10個的視圖。由于視圖并不很多,表示層的架構設計非常的簡單:

準确的說,這裡談不上設計,隻是簡單讓用戶端通路不同的視圖而已。當然,在設計的示意圖中,我們并沒有必要畫出所有的視圖來,隻要能夠表達用戶端和視圖的關聯性就可以了。

(架構設計需要和具體的實作綁定,但是在這個例子中,為了着重展現設計的演進,是以把不必要的資訊都删掉。在實際的設計中,視圖可能是JSP頁面,也可能是一個視窗。)

第一個疊代周的任務很快的完成了,小組負責的表示層子產品也很順利的和其它小組完成了對接,一個簡陋但能夠運轉的小系統順利的釋出。客戶觀看了這個系統的示範,對系統提出了修改和補充。

第二個疊代周中,子產品要處理的視圖增加到了30個,視圖之間存在相同的部分,并且,負責資料層的小組對我們說,由于客戶需求的改進,同一個視圖中将會出現不同的資料源。由于我們的視圖中直接使用了資料層小組提供給我們的資料源的函數,這意味着我們的設計需要進行較大的調整。

考慮到系統的視圖的量大大的增加,我們有必要對視圖進行集中的管理。前端控制器(Front Control)模式将會是一個不錯的技巧。對于視圖之間的普遍的重複部分,可以将視圖劃分為不同的子視圖,再把子視圖組合為各種各樣的視圖。這樣我們就可以使用組合(Composite)模式:

客戶的請求集中送出給控制器,控制器接受到客戶的請求之後,根據一定的規則,來提供不同的視圖來回報給客戶。控制器是一個具有擴充能力的設計,目前的視圖數量并不多,是以仍然可以使用控制器來直接配置設定視圖。如果視圖的處理規則比較複雜,我們還可以使用建立工廠(Create Factory)模式來專門處理生成視圖的問題。對于視圖來說,使用組合模式,把多個不同資料源的視圖組合為複雜的視圖。例如,一個JSP的頁面中,可能需要分為頭頁面和尾頁面。

項目進入第三個疊代周期之後,表示層的需求進一步複雜化。我們需要處理權限資訊、需要處理資料的合法性判斷、還需要面對更多的視圖帶來的複雜程度上升的問題。

表示層的權限處理比較簡單,我們可以從前端控制器中增權重限控制的子產品。同時,為了解決合法性判斷問題,我們又增加了一個資料過濾鍊子產品,來完成資料的合法性判斷和轉換的工作。為了不使得控制器部分的功能過于複雜,我們把原先屬于控制器的視圖分發功能轉移到新的分發器子產品,而控制器專門負責使用者請求、視圖的控制。

我們來回顧這個例子,從疊代周期1中的需求最為簡單,其實,現實中的項目剛開始的需求雖然未必會像例子中的那麼簡單,但一定不會過于複雜,是以疊代周期1的設計也非常的簡單。到了疊代周期2的時候,需求開始變得複雜,按照原先的架構繼續設計的話,必然會導緻很多的問題,是以對架構進行改進是必要的。我們看到,新的設計能夠滿足新的需求。同樣的,疊代周期3的需求更加的複雜,是以設計也随之演進。這就是我們在文章的開始提到的"Evolutionary Design"的演進的思想。

7.組合使用模式

我們已經讨論了靈活架構設計的4種過程模式,在這一章中,我們對這四種過程模式做一個小結,并讨論4者間的關系以及展現在模式中的靈活方法論特色。通過這一章的描述,大家能夠對前面的内容有更進一步的了解。

四種模式的着重點

我把源自需求、團隊設計、簡單設計、疊代設計這4種過程模式歸類為架構設計的第一層次,這4種模式能夠确定架構設計過程的架構。這裡需要對架構的含義進行澄清:架構設計的架構并不是說你要嚴格的按照文中介紹的内容來進行架構設計,在文章的一開始我們就指出,模式能夠激發思考。是以,這一架構是需要結合實際,進行改造的。實際,我們在這一個部分中介紹的,比較偏向于原則,我們花了很大的時間來讨論原則的來龍去脈,而原則的度,則要大家自己去把握。為什麼我們不讨論原則的度呢?這裡有兩個原因,一個是軟體開發團隊各有特色,很難定義出一個通用的度。第二個原因是我的水準不夠,實踐經驗也不夠豐富。

前面提到的四種模式其實是從四個側面讨論了架構設計中的方法問題。源自需求提供了架構設計的基礎。在軟體過程中,架構設計是承接于需求分析的,如果沒有良好的需求分析活動的支援,再好的架構設計也沒有用。是以我們把這一模式放在首位,做為架構設計的目标。

有了确定的目标,還需有組織的保證,這也就是第二種模式――團隊設計的由來。靈活方法提倡優秀的溝通,是以團隊設計是必要且有效的。而團隊設計的另一個意圖,是保證架構設計的下遊活動得以順利的進行,例如詳細設計、編碼、測試等。由于開發團隊中的人大都加入了架構設計,是以最大程度的減小了不同的活動間的資訊損耗和溝通效率低下的問題。如果說源自需求模式是起承上的作用,那麼團隊設計模式則是扮演了啟下的角色。

在軟體設計的過程中,溝通往往扮演着非常重要的角色。從團隊設計開始的幾種模式所要解決的都是溝通的問題。團隊設計對溝通的貢獻在于它能夠把設計意圖以最小的代價傳播到開發團隊的每個角落。這樣,設計和下遊的活動間由于溝通不暢産生的問題就能夠得到緩解。一般而言,設計到編碼會經曆一個資訊損失的過程,編碼人員無法正确了解設計人員的意圖,設計人員卻往往無法考慮到一些編碼的細節。雖然我們可以通過共通的設計符号來提高溝通的品質,例如UML。但是實踐證明,隻要能夠保證暢通的溝通,即便沒有優秀的開發方法,項目成功的機率依然很高。是以對于單個的項目來說,最關鍵的問題還是在于溝通。隻要組織得當,團隊設計是一個值得應用的模式。當然,配合以UML為代表的模組化語言,更能夠提高溝通的效果。

在設計中,我們發現,當設計資訊轉換為編碼資訊需要一定的時間,這個時間包括設計的組織時間,設計被了解的時間。如果設計比較複雜,或者說設計的文檔比較複雜,編碼人員花在了解上的時間就會大大增加。是以,權衡後的結果是,相對于詳細的設計說明書而言,簡單的設計說明書再配合一定程度的面對面溝通能夠起到更好的效果。"簡單要比複雜有效",這就是簡單設計模式的基本思路。

同樣,簡單的思路還會用在軟體開發的各個方面,例如文檔、設計、流程。堅持簡單的原則,并不斷的加以改進,是降低軟體開發成本的一種很有效的做法。

在有了以上的思路之後,我們還需要面對兩個現實的問題。需求的變化将會導緻設計的不穩定,而需求的複雜性又會導緻簡單架構設計的困難。為了解決這個問題,我們引入了疊代的方法,将問題分割為多個子問題(把一個複雜的問題分解為多個較簡單的子問題是計算機領域最常見的處理方法)。這樣,問題的範圍和難度都大大降低了。而更關鍵的是,由于對使用者需求了解不充分或使用者表達需求有錯導緻的設計風險被降到最低點。疊代和前面幾個模式都有關系。

需求和疊代

源自需求模式是架構設計中的起手式,沒有這一模式的支援,架構設計隻能是空中樓閣。其實,源自需求模式嚴格意義上并不能算是靈活方法論的特色,而應該算是軟體開發的天然特性。不幸的是,就是這麼一個基本的原則,卻沒能夠引起開發者足夠的重視。

靈活方法論中把需求擺在一個非常重要的位置,我們把源自需求模式作為架構設計的第一個模式,主要的目的是承接架構設計的上遊工作――需求。需求決定架構,是以,我們在經典的瀑布模型中可以看到需求到設計的嚴格的分界線,但是在實際的開發中,按照瀑布模型的理論往往會遇到很多的問題,是以,我們嘗試着把需求和(架構)設計之間的界限打破,形成一個重疊的地帶,進而提高軟體開發的速度。是以,我們在源自需求模型中指出,架構設計是緊随着需求開始的。

需求對軟體開發最具影響就是需求的不穩定性。我們都非常的清楚軟體開發的曲線,越到軟體開發的後期,修改軟體的成本越高。是以,在軟體開發上遊的需求的變動将會對軟體開發的下遊産生天翻地覆的影響。為了協調這一沖突,軟工理論提出了螺旋開發模型,這就是我們在疊代開發模式中的讨論的理論基礎。把軟體開發過程分為多個的疊代周期,每一次的疊代周期最後都将生成一個可傳遞的軟體,使用者在每一次的疊代結束後,可以試用軟體,提出下一步的需求或是改變原先的需求。通過這樣的方式,把客戶、開發商的風險降到一個可以接受的水準上。

請注意疊代的前提:需求的易變性。是以,對于那些需求容易發生變化的項目,我們就可以使用疊代式的開發過程,雖然我們會付出一些額外的成本(剛開始這個成本會比較大,但可以用較長的疊代周期來降低這種成本),但是風險減小了。而對于需求比較固定的項目,是不是有必要使用疊代的方法,就要看具體的環境了。是以,我們是根據實際的情況選用開發方法,而不是因為先進或是流行的原因。

實際上,由于現代社會的特性,大部分的項目都是可以采用疊代方法。是以,我們的選擇就變成了了疊代周期應該要多長。疊代周期在理論上應該是越短越好,但是并沒有一個絕對的數值,時間的跨度一般從幾周到幾個月。一般來說,疊代周期會受到幾個因素的影響:

  • 各子產品的關聯程度。在軟體開發中,我們有時候很難把一些子產品分離開來,要開發子產品A,就需要子產品B,而子產品B又需要子產品C。各子產品的關聯程度越高,疊代周期越長。當然,也相應的解決方法,我們可以在各子產品的功能中選取出一些關鍵點,作為裡程碑,以裡程碑作為疊代完成點。
  • 人員技能、經驗的平均程度。團隊中成員的開發能力、開發經驗良莠不齊,這也是造成疊代周期延長的一個原因。能力低、經驗少的開發人員會拖後每一次疊代的時間。針對這種情況,做好統籌規劃就顯得非常的重要,可以通過一兩次的疊代,找出隊伍中的瓶頸人員,安排相應的對策。
  • 工具的缺乏。疊代周期越短,就意味着build、釋出的次數越多,客戶也就有更多的機會來修改需求。這要求有相關的工具來幫助開發人員控制軟體。最重要的工具是回歸測試工具。每一次疊代都需要增加新的功能,或是對原先的功能進行改動,這就有可能引入新的bug,如果沒有回歸測試,開發人員就需要花費時間重新測試原先的功能。
  • 計劃、控制的能力。疊代周期越短,所需要的計劃、控制的能力就越強。因為短時間内的計劃制定和實施需要高度的細分,這就要求開發團隊的管理者對開發能力、工作量、任務配置設定有很強的認識,才能做好這項工作。不過,疊代周期越短,同樣開發時間的疊代次數就越多,而團隊調整、改進計劃控制的機會就越多。是以,後期的疊代一般都能夠做到比較精确的控制。而這樣的做法,要比問題堆積到軟體傳遞日才爆發出來要好的多。沒有突然落後的軟體,隻有每天都在落後的軟體。

簡單和疊代

簡單和疊代關系是雙向的。

在現實設計我們很難界定出簡單設計的程度。怎樣的架構設計才算是簡單?按照我們在簡單設計模式中的讨論,剛好滿足目前的需求的架構設計就算是簡單的設計。但是,從另外一個方面考慮,需求的易變性限制我們做出簡單的設計,因為我們不能夠肯定目前的需求将來會發生什麼樣的變化。是以,為了克服對未知的恐懼,我們花了很大的力氣設計一些靈活的、能夠适應變化的架構。這是源自需求模式對簡單設計模式的影響。

源自需求和疊代設計的關系的讨論建議我們把需求分為多個疊代周期來實作。那麼,相應的架構設計也被分在多個疊代周期中。這樣的方法可以降低架構設計的複雜程度。因為設計人員不需要考慮軟體的全部需求,而隻需要考慮目前疊代周期的需求。複雜性的降低将會有助于架構設計的簡單化,進而達到簡單設計的一系列的好處(參見簡單設計)。

我們從疊代設計中的最後一個例子可以清楚的看到疊代設計是如何把複雜的需求給簡單化的。把握疊代設計有助于我們避免過分設計的毛病。這是個技術人員經常犯的毛病。我所在的團隊很多時候也無法避免。例如,在很多的項目中,我們都會花費大量的時間來設計資料庫到業務實體的映射。諸如此類的技術問題對開發人員的吸引程度是不言而喻的,但是必須看到,這種的設計會導緻開發成本的大幅度上升。更為糟糕的是,除非有豐富的經驗,這種類型的設計給開發工作帶來的價值往往無法超過其成本。

是以,我們需要學會權衡利弊,是否有必要投入大量的資源來開發其實并沒有那麼有用的功能。是以,疊代設計和簡單設計的結合有助于我們擺脫過度設計的困擾,把精力集中在真正重要的功能之上。

此外,簡單的設計并不等同于較少的付出。簡單的設計往往需要對現實世界的抽象,回憶我們在簡單設計中讨論的測量模式的例子,它看似簡單,但實作起來卻需要大量的業務知識、很強的設計能力。是以,做到簡單是程式員不斷追尋的目标之一。

在很多的方法論中,一般并不過分注意代碼重複的問題,要麼是不關注,要麼認為适當的代碼重複是允許的。而XP卻把代碼重複視為良好代碼的大敵。"隻要存在重複代碼,就說明代碼仍有Refactoring的可能。"這種觀點看起來非常的絕對,這可能也正是其名字中Extreme的來曆(英文中的Extreme屬于語氣非常重的一個單詞)。從實踐的角度上來看,追求不重複的代碼雖然很難做到,但是其過程卻可以有效的提高開發團隊代碼的寫作品質,因為它逼迫着你在每次疊代重對代碼進行改進,不能有絲毫的怠惰。而這種疊代的特性,促進了簡單的實作。

團隊和簡單

我們在簡單設計中提過簡單設計需要全面的設計師。除此之外,它還需要團隊的配合。簡單意味着不同活動間傳遞工件的簡單化。也就是說,類似于需求說明書、設計文檔之類的東西都将會比較簡單。正因為如此,我們很難想象一個地理上分布在不同地點的開發團隊或一個超過50人的大團隊能夠利用這種簡單的文檔完成開發任務。

是以,簡單的設計是需要團隊的組織結構來保證的。簡單的設計要求團隊的互相溝通能夠快速的進行。架構設計完成後,架構的設計思路傳達給所有的編碼人員的速度要塊,同樣,編碼中發現問題,回饋給設計者,設計者經過改進之後再傳達給收到影響的編碼人員的速度也要快。象這樣高效率的傳播我們可以稱之為"Hot Channel"。

為了保證"Hot Channel"的高溝通效率,最好的組織機關是開發人員在3到6人之間,并處于同間工作室中。這樣的結構可以保證訊息的互動速度達到最高,不需要付出額外的溝通成本,也不需要過于複雜的版本控制工具或權限配置設定。根據我的經驗,一個共享式的小型版本控制工具、網絡共享、再加上一個簡單的網絡資料庫就能夠解決大部分的問題了。

在理論上,我們說隻要配置設定得當,大型的團隊同樣可以組織為金字塔式的子團隊,以提高大型團隊的工作效率。但是實際中,随着團隊的人數的增加,任務的正确配置設定的難度也随之加大,溝通資訊上傳下達的效率開始下降,子團隊間的隔閡開始出現,各種因素的累加導緻靈活方法并不一定适合于大型的團隊,是以我們讨論的靈活方法都将受到團隊的特性的限制。

模式的源頭

如果你對XP有一定的了解的話,那麼你可能會感覺到我們讨論的模式中應用了XP的實踐。确實如此,XP中有很多優秀的實踐,如果組織得當的話,這些微小的實踐将會組合成為一套了不起的開發方法。不過,目前的軟體開發混亂的現狀阻止了先進的軟體方法的應用,對一個身體虛弱的病人施以補藥隻會适得其反。是以,在前面讨論的模式中,我們應用了一些容易應用、效果明顯的實踐方法。在實踐中适當的應用這些方法,并不需要額外的投入,卻能夠有很好的效果,同時還會為你的團隊打下一個良好的基礎。

8.架構願景

從這一篇開始,我們将會進入另一個不同的主題,和前面所讨論的模式專注于組織、過程、方法不同,以後介紹的模式更偏重于設計。但是過程、方法的影子依然在我們的讨論中隐約可見。

架構願景是一個很簡單的模式,在軟體開發中所占的時間也很短。但是這并不意味着架構願景不重要。相反,它會是設計過程不可或缺的一環。

Context

在單次的疊代開始階段,我們已經收集好了單次疊代的需求。

Problem

架構和分析設計是密不可分的,有時候很難說得清楚架構的定義,但架構應該能夠描述軟體的整體。架構包括了軟體的各個方面,但是每一個設計細節總是需要單獨考慮,這時候就會出現設計細節之間、以及設計細節和架構之間的不一緻。

架構設計的各個部分之間的設計沖突是很容易發生的。發生的機率及頻率和團隊的規模成正比、和溝通的頻度及效果成反比。在很多次的項目開發過程中,我們發現了多處的相同功能的代碼,原因是代碼的作者并不知道别人已經實作了這個功能了。這可能隻是浪費了一點精力,可如果不同子產品間的設計沖突導緻了軟體無法正常運作的時候,我們就需要坐下來好好的審視,究竟發生了什麼。

Solution

我們需要建立一個架構願景。架構願景應該能夠提供軟體全局視圖,包括所有的重要部分,定義了各個部分的責任和之間的關系,而且還定義了軟體設計需要滿足的原則。而這個架構願景的設計,應該是滿足源自需求模式的,也就是說,部分的劃分和部分的設計,都是根據需求而進行的。同時,架構願景應該要能夠滿足架構的其它各種特點,例如簡單、可擴充性、抽象性。簡單來說,我們把架構願景當作是一個mini的架構設計。

由于我們是在單次的疊代中讨論架構願景,是以從整體上考慮,架構願景是也是在不斷的變化的。這是很自然的,因為架構願景代表了架構的設計,架構願景的演進代表了架構設計的演進。

架構的願景是相對于一個範圍來說的,在一個特定的軟體功能範圍之内,談架構願景才有實際的意義,例如針對軟體的全局或某個子子產品。在這個特定的範圍中,訂立了架構願景之後,這個範圍内的所有設計原則将不能違背架構願景。這是非常重要的,是架構願景的最大的用處。有了這樣的保證,我們就可以保證設計的一緻性和有效性。任何一項設計的加入,都能夠融入到原先的架構中,使得軟體更加的完善,而不是更加的危險。

當然,要做到這一點并不是一件容易的事情。特别需要指出的是,架構願景模式僅僅是實作該目的的一條道路,并不是一個充分條件。如果在設計中願景不能夠貫徹其意志,或是願景制定本身就存在問題,那麼要想達到上述的效果,幾乎是不可能的事情。此外,該模式僅僅隻是達到該目的的第一步,我們在接下去的模式中會發現還需要很多方面的配合。

架構願景的層次

我們根據架構适用範圍的不同,把架構願景分為幾個類别讨論:

軟體全局

軟體全局的架構是我們所最關心的,是以也會花費最多的筆墨。

軟體全局中的架構願景一般很難具體化到代碼級别,其實你會發現,就算是具體化到了代碼級别,也會因為實際中存在的問題,導緻代碼沒有太多的價值。是以,為軟體全局設定的架構願景可以以原則、或是模式名的方式展現,并用自然語言或僞代碼描述。例如,可以為一個系統規定三層架構作為其願景,并指出三層的分類原則。注意,我們需要指出分類原則,否則規定三層架構并沒有太大的意義,因為三層架構随着實作平台的不同、開發人員的不同而有很大的差異,如果不能夠規定一個可操作的規範,那麼願景是沒有意義的。在Java環境下,我們可以這樣說:

"用戶端采用前端浏覽器界面,業務邏輯采用servlet,配合JSP編寫,浏覽器到伺服器的資料采用集中處理,具體的方法是在業務邏輯和前端浏覽器之間采用Front Control模式,接受前端浏覽器傳送過來的資料,并指派給相應的業務邏輯處理。資料的合法性檢驗分為兩個部分:和業務邏輯無關的基本合法性驗證在前端使用Java Script處理,和業務邏輯相關的合法性驗證在業務邏輯層處理,可以使用一個集中的servlet專門處理錯誤情況。"

而在Windows環境下,我們知道三層結構的分界和Java中的并不相同(關于這一點,下一篇的文章中将會有詳細的描述),我們可以在業務邏輯層或顯示層直接操縱資料,非常的友善,是以,在Windows中的架構願景的描述又不一樣。另外,在非面向對象的環境中,在分布式的環境中,在Lotus Notes的環境中,其架構的描述都不一樣。

注意到,架構願景的制定是根據不同的應用環境而變化的,這一點我們在文章的一開始就強調過,這裡又出現了相同的問題。我們可以通過一個簡單的例子來加深對這個問題的了解。在Java的環境下,特别是J2EE的環境下面,經典的設計思路是把資料庫的一張表視作一個類,表中的每一行都是這個類的一個具體的執行個體,表的每一個字段對應了類的一個變量,類一般支援Find方法,來擷取資料不同的執行個體。這是一種很自然的設計思路,而且可以通過CMP等技術來簡化設計的繁瑣步驟。可是,在Windows環境下,相信沒有多少人會這麼做。常用的處理方法是使用記錄集(RecordSet)的方式,也就是說針對一組的記錄來進行操作,而不是單個的記錄。這些記錄可以是跨表的(使用SQL的連接配接),也可以是單表的。由于Windows環境下大多數的程式設計語言都能夠支援記錄集方式,是以編寫基于記錄集方式的程式是相當簡單的。當然,我們也可以采用J2EE平台的程式設計方式,但是會導緻程式設計量的激增,同時也難以達到預計的效果。這種平台的差别導緻了架構願景的差别。

此外,我們注意到我們對架構的描述其實還是不夠仔細,這是非常正常的,因為随着開發的深入,我們會不斷的完善架構的描述。例如,我們可以寫出Front Controll的類架構,寫出主要的幾個Servlet,以及他們之間的關系。這個時候,使用UML類圖會是一個不錯的選擇,實際上,我非常推崇在架構設計中大量的使用類圖。不過在實際的運作中,每個軟體團隊大多數都有自己熟悉的架構設計思路,使用何種工具并不是主要的問題。

我們從源自需求模式中,學習到架構的設計是來自于需求的,而應用于軟體全局的架構則來自于最重要的需求。還記得我們在那個模式中提到的網上寵物店的例子嗎?系統采用了MVC模式,sun的官方文檔一開始說明了為什麼采用MVC模式,MVC模式解決了什麼問題,然後開始分析MVC模式的幾個組成部分:Model、View、和Controll。其實,MVC中的每一個部分,在真正的代碼中,大都代表了一個子系統,但是在目前,我們就非常的清楚系統大緻上會是一個什麼樣子,雖然這時候它還十分的朦胧。

不要視圖在全局的架構願景中就制定出非常細緻的規劃,更不要視圖生成大量的實際代碼。因為,你的架構願景還沒有穩定(我們在其後的穩定化的模式中将會讨論穩定的問題),還沒有獲得大家的同意,也沒有經過證明。是以,從整個的開發周期來看,全局架構願景是随着疊代周期的進行不斷發展、修改、完善的。

我們如何确定全局架構願景工作的完成?一般來說,你的架構設計團隊取得了一緻的意見就可以結束了,如果問題域是團隊所熟悉的,一兩個小時就能夠解決問題。接下來設計團隊把架構願景傳播到整個的開發團隊,大家形成一緻的認識,不同的意見将會被回報會來,并在本次的疊代周期(如果時間比較緊迫)或下一次的疊代周期中(如果時間比較寬松)考慮。

子子產品級、或是子問題級的架構願景

這時候的架構願景已經是比較明确的了,因為已經存在明确的問題域。例如界面的設計、領域模型的設計、持久層的設計等。這裡的願景制定本質上和全局的願景制定差不多,具體的例子我們也不再舉了。但是要注意一點,你不能夠和全局願景所違背。在操作上,全局願景是設計團隊共同制定出來的,而子子產品級的架構願景就可以分給設計子團隊來負責,而其稽核則還是要設計團隊的共同參與。這有兩個好處,一是確定各個子子產品(子問題)間不至于互相沖突或出現空白地帶,二是每個子設計團隊可以從别人那裡吸取設計經驗。

在設計時,同樣我們可以參考其它的資料,例如相關的模式、或規範(界面設計指南)。在一個有開發經驗的團隊,一般都會有開發技術的積累,這些也是可供參考的重要資料。

我們在這個層次的願景中主要談一談子子產品(子問題)間的耦合問題。一般來說,各個子子產品間的耦合程度相對較小,例如一個MIS系統中,采購和銷售子產品的耦合度就比較小,而子問題間的耦合程度就比較大,例如權限設計、财務,這些功能将會被每個子產品使用。那麼,我們就需要為子子產品(子問題)制定出合同接口(Contact Interface)。合同的意思就是說這個接口是正式的,不能夠随意的修改,因為這個結構将會被其它的設計團隊使用,如果修改,将會對其它的團隊産生無法預計的影響。合同接口的制定、修改都需要設計團隊的通過。此外,系統中的一些全局性的子問題最好是提到全局願景中考慮,例如在源自需求模式中提到的信貸帳務的例子中,我們就把一個利息計算方式的子問題提到了全局願景中。

代碼級的願景

嚴格的說這一層次的願景已經不是真正的願景,而是具體設計了。但是我們為了保證對架構設計了解的完整性,還是簡單的讨論一下。這一個層次的願景一般可以使用類圖、接口來表示。但在類圖中,你不需要标記出具體的屬性、操作,你隻需要規定出類的職責以及類之間的互相關系就可以了。該層次願景的稽核需要設計子團隊的通過。

而設計細分到這個粒度上,執行願景設計的開發人員可能就隻有一兩個左右。但是比較重要的工作在于問題如何分解和如何歸并。分解主要是從兩個次元來考慮,一個是問題大小維,一個是時間長短維。也就是說,你(設計子團隊負責人)需要把問題按大小和解決時間的長短分解為更細的子問題,交給不同的開發人員。然後再把開發人員提出的解決方法組合起來。

架構願景的形成過程

架構願景的形成的源頭是需求,需要特别指出的是,這裡的需求主要是那些針對系統基本面的需求。比如說,系統的特點是一個互動式系統,還是一個分布式系統。這些需求将會影響到架構願景的設計。在收集影響架構願景的各項需求之後,按照需求的重要性來設計架構願景。

架構願景的設計并不需要很複雜的過程,也不需要花費很多的時間。我們已經提過,架構遠景的主要目的就是為了能夠在開發團隊中傳播設計思路,是以,架構願景包括基本的設計思路和基本的設計原則。

值得注意的是,架構遠景可能會有多種的視角,下文讨論了一種設計模式的視角。但是實際設計中還可能會基于資料庫來設計架構願景。但在企業資訊系統的設計中,我推薦使用領域類的設計,也就是下文中讨論的例子。

架構願景設計好之後,問題的焦點就轉到如何傳播架構願景上來,為了達到在開發團隊中取得統一設計意圖的效果,可以考慮援引團隊設計模式。除此之外,針對性的項目前期教育訓練也會是一種有效的做法。

使用架構模式

架構模式也是一種很好的架構願景設計思路的來源。随着對設計模式的研究的深入,人們發現其中的一些設計模式可以擴充、或變化為軟體設計的基礎。在這個基礎上再實作更多的設計,這些模式就形成了架構模式。當然,不同的軟體,它們的架構模式也是不一樣的。在《Applying Pattern》一文中,有一個很典型的架構願景的例子:

假設我們需要設計分布式的互動式系統。分布式系統和互動式系統都有特定的架構模式,前者為Broker模式,後者為MVC模式。首先我們先要根據系統的特點的重要程度來排列模式的順序。這裡假設需求中分布式特性更重要一些。那麼我們首先選擇Broker模式作為架構的基本模式:

再考慮互動式的特點,根據MVC模式的特點,我們需要從目前的基本架構中識别出Model、Controller、以及View。Model和View都很簡單,分别分布在上圖中的Server和Client中。而Controller則有兩種的選擇,假設這裡的Controller部署在用戶端,上圖則演化為下圖:

這樣,基礎的架構願景就已經出現了。如果我們還有更多的需求,還可以繼續改進。但是,記住一點,架構願景不要過于複雜。正如我們在上一節中所讨論的,這裡我們雖然是基于設計模式來讨論架構願景,但是實際中還有很多從其它的視角來看待架構願景的。至于要如何選擇架構願景的視角,關鍵的還是在于需求的了解。

9.分層 (上)

在定義了架構願景之後,團隊中的所有人員應該對待開發的軟體有一定的了解了。但是,面對一個龐大的軟體系統,接下來要做些什麼呢?分而治之的思想是計算機領域非常重要的思想,是以我們也從這裡開始入手。

要進行應用軟體的設計,分層是非常重要的思想,掌握好分層的思想,設計出的軟體是可以令人賞心悅目的。由于這一章的重要性和特殊性,本章的内容分為上下兩節,并不采取模式描述語言的方式。

分層隻是将系統進行有效組織的方式。

本章特别針對于企業應用進行讨論,但其中大部分的内容都可以應用在其它的系統中,或為其它的系統所參考。

在企業應用中,有兩個非常重要的概念:業務邏輯和持久性。可以說,企業應用是圍繞着業務邏輯進行開展的。例如報帳、下訂單、貨品入庫等都是業務邏輯。從業務邏輯的底層實作來看,業務邏輯其實是對業務實體進行組織的過程。這一點對于面向對象的系統才成立,因為在面向對象的系統中,識别業務實體,并制定業務實體的行為是非常基礎的工作,而不同的業務實體的組合就形成了業務邏輯。

還有另一個重要的概念是持久性。企業應用中大部分的資料都是需要可持久化的。是以,基礎組織支援持久性就顯得非常的重要。目前最為通行的支援持久性的機制是資料庫,尤其是關系性資料庫-RDBMS。

除此之外,在企業應用中還有其它的重要概念,例如人機互動。

為了能夠更有效的對企業中的各種邏輯進行組織,我們使用層技術來實作企業應用。層技術在計算機領域中有着悠久的曆史,計算機的實作中就引用了分層的概念。TCP/IP的七層協定棧也是典型的分層的概念。分層的優勢在于:

上層的邏輯不需要了解所有的底層邏輯,它隻需要了解和它鄰接的那一層的細節。我們知道TCP/IP協定棧就是通過不同的層對資料進行層層封包的,不同層間的耦合度明顯降低。通過嚴格的區分層次,大大降低了層間的耦合度。

某一層次的下級層可以有不同的實作。例如同樣的程式設計語言可以在不同的作業系統和不同的機器中運作。

和第三條類似的,同一個層次可以支援不同的上級層。TCP協定可以支援FTP、HTTP等應用層協定。

綜合上面的考慮,我們把企業應用分為多個層次。企業應用到底應該分為幾種層次,目前還沒有統一的意見。

在前些年的軟體開發中,兩層結構占有很重要的位置。例如在銀行中應用很廣的大型主機/終端方式,以及Client/Server方式。兩層的體系結構一直到現在還廣泛存在,但是兩層結構卻有着很多的缺點,例如用戶端的維護成本高、難以實作分布式處理。随着在兩層結構的終端使用者和後端服務間加入更多的層次,多層的結構出現了。

經典的三層理論将應用劃分為三個層次:

表示層(Presentation Layer),用于處理人機互動。目前最主流的兩種表示層是Windows格式和WebBrowser格式。它主要的責任是處理使用者請求,例如滑鼠點選、輸入、HTTP請求等。

領域邏輯層(Domain Logic Layer),模拟了企業中的實際活動,也可以認為是企業活動的模型。

資料層(Data source Layer),處理資料庫、消息系統、事務系統。

在實際的應用中,三層結構有一些變化。例如,在Windows的.NET系統中,把應用分為三個層次:表示層(Presentation Layer)、業務層(Business Layer)、資料通路層(Data Access Layer),分别對應于經典的三層理論中的三個層次。值得一提的是,.NET系統中表示層可以直接通路資料通路層,即記錄集技術。在ADO.NET中,這項技術已經非常成熟,并通過表示層中的某些資料感覺元件,實作非常友好的功能。這種越層通路的技術通常被認為是不被允許的,因為它可能會破壞層之間的依賴關系。而在Windows平台中,嚴格遵守準則就意味着需要大量額外的工作量。是以,我們看到準則也不是一成不變的。

在J2EE的環境中,三層結構演變為五層的結構。在表示層這裡,J2EE将其分為運作在客戶機上的使用者層(Client Layer),以及運作在服務端上的Web層(Presentation Layer)。這樣做的主要理由是Web Server已經成為J2EE中非常核心的技術,例如JSP和Java Servlet都和它有關系。Web層為使用者層提供表示邏輯,并對使用者的請求産生回應。

業務層(Business Layer)并沒有發生變化,仍然是處理應用核心邏輯之處。而資料層則被劃分為兩個層次:內建層(Integration Layer)和資源層(Resource Layer)。其中,資源層并非J2EE所關心的内容,它可能是資料庫或是其它的老系統,內建層是重要的層次,包括事務處理,資料庫映射系統。

執行個體

這一章的的組織方式和之前的模式有一些差别。我們先從一個例子來體會架構設計中分層的重要性。

上圖是一個業務處理系統的軟體架構圖。在上圖中,我們把軟體分為四個層次。這種層次結構類似于我們前面所談的J2EE的分層。但是和J2EE不同的是,缺少了一個Web Server層,這是因為目前我們還沒有任何對Web Server的需要。

在資源層上,我們有三種資源:資料庫、平台服務、UI。資料庫是企業應用的基礎,而這裡的平台服務指的是作業系統系統或第三方軟體提供的事務管理器的功能。值得注意的是,這裡的事務管理器指的并不是資料庫内部支援的事務,而是指不同的業務實體間事務處理,這對于企業應用有着很重要的意義。因為對于一個企業應用來說,常常需要處理跨子產品、跨軟體、甚至跨平台的會話(Session),這時候,單純的資料庫支援的事務往往就難以勝任了。這方面的例子包括微軟的MTS和Oracle的DBLink。當然,如果說,在你處理的系統中,可以使用資料庫事務來處理大部分的會話的話,那就可以避免考慮這方面的設計了。除了典型的事務管理器,平台還能夠提供其它的服務。對于大部分的企業應用來說,都是內建了多個的平台服務,平台服務對架構的設計至關重要。但是從分層的角度上考慮,上層的設計應該盡可能的和平台無關。和使用平台服務類似的,一般來說,企業應用都不會從頭設計界面,大部分情況下都會使用現有的的UI資源。比如Window平台的MFC中的界面部分。是以,我們把被使用的UI資源也歸到資源層這個層次上。

資源層的上一層是內建層。內建層主要完成兩項工作,第一項是使用資源層的平台服務,完成企業應用中的事務管理。有些事務處理機制已經提供了比較好封裝機制,直接使用資源層的平台服務就可以了。但是對于大多數的應用來說,平台提供的服務往往是比較簡單的,這時候內建層就派上大用場了。第二項是對上一層的對象提供持久性機制。可以說,這是一組起到過渡作用的對象。它實際上使用的是資源層的資料庫功能,并為上一層的對象提供服務。這樣,上一層的業務對象就不需要直接同資料庫打交道。對于那些底層使用關系型資料庫,程式設計中使用面向對象技術的系統來說,目前比較常見的處理持久性的做法是對象/關系映射(OR Mapping)。

在這個層次上,我們可以做一些擴充,來分析層的作用。假設我們的系統需要處理多個資料庫,而不同資料庫之間的處理方式有一定的差異。這時候,層的作用就顯示出來了。我們在內建層中支援對多個資料庫的處理,但對內建層以上的層次提供統一的接口。對于業務層來說,它不知道,也不需要知道資料庫的差别。目前我們自己開發了內建層中的持久類,但是随着功能的擴充,原有的類無法再支援新增加的功能了,新的解決方案是購買商用程式。為了盡可能的保持對業務層次的影響,我們仍然使用原有的接口,但是具體的實作已經不同了,新的代碼是針對新的商業程式來實作的。而對業務層來說,最理想的狀況是不需要任何的改變。當然現實中不太可能出現如此美好的情況,但可以肯定的一點是,引入層次比不引入層次要好的多。

以上列舉的兩個例子都是很好的解決了耦合度的問題。關于分層中的耦合度的問題,我們在下面還會讨論。

業務層的設計比較簡單,暫時隻是把它實作為一組的業務類。類似的,表示層的設計也沒有做更多的處理。表示層的類是繼承自資源層的。這是一種處理的方法,當然,也可以是使用關系,這和具體的實作環境和設計人員的偏好都有關系,并不是唯一的做法。在對軟體的大緻架構有了一個初步了解之後,我們需要進一步挖掘需求,來細化我們的設計。在前面的設計中,我們對業務層的設計過于粗糙了。在我們的應用中,還存在一個舊系統,這個系統中實作了應用規則,從應用的角度來看,這些規則目前仍然在使用,但新的系統中會加入新的規則。在新系統啟用後,舊的系統中的規則處理仍然需要發揮它的作用,是以不能夠簡單的把所有的規則轉移到新系統中。(有時候我們是為了節省成本而不在新系統中實作舊系統的邏輯)。我們第二步的架構設計的細化過程中将會加入對新的要求的支援。

在細化業務層的過程中,我們仍然使用層技術。這時候,我們把原先的業務層劃分為兩個子層。對于大多數的企業應用來說,業務層往往是最複雜的。企業對象之間存在着錯綜複雜的聯系,企業的流程則需要用到這些看似獨立的企業對象。我們希望在業務層中引入新的機制,來達到組織業務類的目的。業務層的組織需要依賴于具體的應用環境,金融行業的應用和制造行業的應用就有着巨大的差距。這裡,我們從一般性的設計思考的角度出發來看待我們的設計:

首先,我們看到,業務層被重新組織為兩個層次。增加層次的主要考慮是設計的重用性。從我們前面對層的認識,我們知道。較高的層次可以重用較低的層次。是以,我們把業務層中的一部分類歸入較低的層次,以供較高的層次使用。降低類層次的主要思路是分解行為、識别共同行為、抽取共性。

在Martin Fowler的分析模式中提供了一種将操作層和知識層分離的處理方法:

Action、Accountability、Party屬于較高層次的操作層(Operational Layer),這一層次的特點是針對于特定的應用。但是觀察Accountability和Party,有很多相似的職責。也就是說,我們對于知識的處理并不合适。是以,Martin Fowler提出了新的層次――屬于較低層次的知識層(Knowledge Layer)。操作層中可重用的知識被整理到知識層。進而實作對操作層的共性抽取。注意到雖然圖中的層次(Level)的概念和層(Layer)有所差别,但是思路是基本一緻的。

另一種分層方法是利用繼承:

該圖中也是來自于分析模式一書。不同的部門有着差異點和共性,将共性提取到父類中是繼承的基本概念。這時候我們可以把父類看作是較低的層次,而把子類看作是較高的層次。對于一組層次結構很深的類來說,也可以從某一個水準線上分離層次。

最後一種方法需要分解并抽象對象的行為。C++的STL中為不同的資料類型和不同的容器實作了Iterator的功能。它的實作是典型的降低層次的行為。我們不考慮它對不同資料類型的支援,因為它使用了比較獨特的模闆(Template)技術,隻考慮它對不同的容器的實作。首先是對共性的分析,不論是數組(Array)還是向量(Vector),要執行周遊操作,都需要知道三個條件:容器的起始點、容器的長度、比對值。這就是一種抽象。通過這種方式,就可以統一不同容器的接口。

以上我們讨論了細分層次的好處和實作政策。下面我們回到前例中,繼續讨論層中的各個部件。

業務實體指的是企業中的一些單獨的對象。例如訂單、客戶、供應商等。由于業務實體可以被很多的業務流程(業務會話)所使用,是以我們把業務實體放在業務實體層中。企業中的業務實體大多是需要持久性的。是以在我們的設計中,業務實體将持久性的職責委托給下一個層次中的持久性包,而不是直接調用資料庫方法。另一種常用的方法是使用繼承――業務實體繼承自持久類。EJB中的Entity Bean就是典型的業務實體,它可以支援自動的持久性機制。

在我們的系統中,規則是一個尴尬的存在。部分的規則處于舊系統中,并使用舊系統的持久性機制。而新系統中有需要支援新的規則。對于上層的使用者來說,并不需要知道新舊系統的規則。如果我們在使用規則時做一個新舊規則的判斷并調用不同的系統函數,那就顯得太傻了。我們的設計對上層而言應該是透明的。

是以我們總共設計了三個包來實作規則的處理。包裝器包裝了舊系統中的規則,這樣做處于幾點考慮:首先,舊系統是面向過程的,是以我們需要用包裝器将函數(或過程)封裝到類中。其次,對舊系統的使用需要額外的程式(或平台服務)的支援,是以需要單獨的類來處理。最後,我們可以使用Adapter模式[GOF 94]将新舊系統不同的接口給統一起來,以使他們能夠一起工作。新的規則類實作了新的規則,較好的做法是定義一個虛協定,具體的表現形式可以是虛基類或接口。然後由其子類實作虛協定。再結合前面介紹的把舊規則的接口轉換為新的接口的方法,就能夠統一規則接口。從圖中我們看到,業務實體需要使用規則,通過統一的接口,業務實體就能夠透明的使用新舊兩種規則。

在定義了規則和标準的規則接口之後,上一層的規則控制器就可以通過調用規則,來實作不同規則組合。是以這個規則控制器就類似于應用規則。在業務交易進行中需要調用規則控制器來實作部分的功能。

請注意,這裡讨論的思路雖然非常的簡單、清晰,但是現實中的處理卻沒有這麼容易。因為為新舊規則制定統一的接口實在是太難了。要能夠使接口在未來也相對的穩定更是難上加難。而在應用已經部署之後,對已釋出的接口的任何一個小改動都意味着很高的成本。在這個問題上,并沒有什麼好的政策,經驗是最重要的。在實際中的一個比較實用的方法是為接口增加一些額外的參數。即便可能這個參數目前并沒有使用到,但是如果為了有可能使用的話,那還是加上吧。舉個例子,對于商業應用軟體而言,資料庫,或者說是關系資料庫一定是不可缺少的部分。大量的操作都需要和資料庫結合起來,是以,可以考慮在方法中加入一個資料庫連接配接字元串的參數。雖然這對于很多方法而言是沒什麼必要的,但是為将來的實踐提供了一個可擴充的空間。國内的某個知名的ERP軟體的設計就采用了這種思路。

本章的主要精力都放在執行個體的研究上,在有了一個基本的概念之後,我們再回來談談分層的一些原則和需要注意的問題。

10.分層 (下)

上篇我們用了大量的篇幅來觀察了一個實際的例子,相信大家已經對分層有了一個比較具體的概念了。在這一篇中我們就對分層在實踐中可能會遇到的問題做一個讨論。分層在架構設計中是一種非常常見的,但是又很不容易用好的技術。是以我們這裡花了很大的氣力來讨論它。

由于這是一篇介紹軟體設計技術的文章,為了盡可能讓更多的人了解,本應該盡可能不涉及到過于具體的技術或平台。但是這個目标可能很難實作,因為軟體設計是沒辦法脫離具體的實作技術的。是以本文能夠做到的是盡可能的不涉及具體的編碼細節。

何時使用分層技術?

分層技術實際上是把技術複雜化了。和以往簡單的CS結構的系統不同,分層往往需要使用特定的技術平台來實作。當然,不使用這些技術平台也是可能的,但是效果可能就沒有那麼好了。支援分層技術的平台有很多,包括目前主流的J2EE和.NET。甚至在不同廠商的開發平台上,要求也不一樣。使用分層技術實作的多層架構,成本要比普通的CS架構高得多。

這就産生了一個非常現實的問題-并不是所有的軟體都适合采用分層技術的。一般來說,小型的軟體使用分層并沒有太大的意義,因為分層導緻的成本超過它所能帶來的好處。在一般的CS結構中,可以把界面控制、邏輯處理和資料庫通路都放在一塊兒。這種設計方式在純粹的多層主義者看來簡直就是十惡不赦。但是對于小型的軟體而言,這并不是什麼大不了的事情。因為從表示層到資料層的整套功能都被囊括在一個功能塊中,同樣能夠實作較好的封裝。而且,如果結構設計的足夠好,也能夠避免表示層、業務層和資料層之間出現過高的耦合度。是以,除非确實需要,不然沒有必要使用分層技術。

尤其在處理一些特殊的項目時,嚴格的區分三層結構并不理想。比如在快速開發windows界面的應用時,往往會用到一些對資料庫敏感的控件,這種處理方法跨越了三個層次,但是卻很實用,成本也比較低。又比如一些架構,給出了從界面層到資料庫的綜合的解決方案,和windows的應用類似,嚴格的三層技術也不适用于這種情況。

如何使用分層技術?

從某種意義上來看,層其實是一個粗粒度的元件。就像我們使用元件技術是為了對系統進行一種劃分一樣,層的一個很大的作用也是如此。其目的是為了系統更容易被了解,不同的部分能夠被較容易的替換。

使用分層技術的依據是軟體開發人員的實際需要。如果你是在使用某些優秀的面向對象的軟體開發平台的話,那它們一般都會建議(或是強制)你使用某一種分層機制。這是你采用分層技術的一大參考。

對于大多數有一定經驗的軟體團隊而言,一般都會積累一些軟體開發經驗。其中包含了很多在某些特定的領域中使用的基礎的類或元件。這些元素構成了一個系統的通用層次。這個層次也是分層時需要考慮的。例如一些應用軟體中使用的一些通用的Currency對象或是Organization對象。分析模式一書對此類的對象進行了充分細緻的闡述。這個層次一般被稱為跨領域層(cross-domain layer),或稱為工具層(utility layer)。

目前的很多軟體都采用了資料庫映射技術。資料庫映射層對于企業應用系統非常的重要,是以也需要納入考慮之列。資料庫映射技術用起來簡單,但是要實作可不容易。如果不是非常有必要,盡可能使用現成的架構,或是采用其中部分的設計思路。試圖建構一個大而全的映射層次的代價是非常高昂的,認識不到這一點會帶來很大的麻煩。資料庫映射技術的知識,我們在下文中還有專門的篇幅來讨論。

如何存放資料(狀态)?

在學習EJB的過程中,最先要了解的一定是有狀态和無狀态的概念。可以說,整個概念是多層體系的核心。為什麼這麼說呢?這裡的狀态指的是類的狀态,例如類的屬性、變量等。由于狀态的不同,類也表現出差異來。而對于多層結構的軟體,建立和銷毀一個類的開銷是很大的,如果該軟體支援分布式的話尤為如此。是以如果系統的不同層次間進行頻繁的調用-建立一個類,再銷毀一個類。這種做法是非常消耗資源的。在應用系統的設計中,一般不單獨使用COM,就是這個原因。是以我們很自然的想到了一種經典的設計-緩沖池。把對象存放在緩沖池中,當需要的時候從池中取出一個,當不需要的時候再把對象放入池中。這種設計思路能夠大幅度的提高效率。但是這對能夠放在池中的對象也提出了苛刻的要求-所有的對象必須是無差異的,也就是無狀态的。隻有這樣才能夠實作緩沖池。

一般來說,對象緩沖池的技術是用在中間的業務層上的。既然中間業務層上不能夠保留有狀态,那就出現了一個狀态轉移的問題。這裡有兩種的選擇,一種是前移,把狀态移到使用者端,最典型的是使用cookie。這種選擇一般是由于狀态和使用者端有關,不需要長時間儲存。另一種選擇是後移,把狀态移到資料層,由資料庫來實作持久性狀态,當需要時才把狀态送出給業務層。這種方式是企業應用軟體中采用最多的,但是也增大了資料庫的負擔。

處理好接口

由于使用了分層技術,是以原先那種在CS結構中類之間存在複雜關系就有必要重新評估了。一般層間的耦合度不宜過大。是以需要慎重的設計層之間的類調用方式。一些分布式軟體體系(例如J2EE)對層之間的調用方式以接口的形式給出了要求。同時,不同層之間僅僅知道目标層的接口,而不知道目标層的具體實作。EJB的home接口和remote接口就是這樣。在COM+體系中,也需要在設計類的同時,把接口公布出來,以供客戶方使用。

在設計層間的接口時,除了考慮開發平台的限制之外,還有一點是開發人員必須考慮的。那就是業務需要。業務層中往往有非常多的對象和方法,它們之間的關系也非常的負責,但對于其它的層次來說,它并不關心這些細節。是以業務層公布的接口必須要簡單,而且和實作無關。是以,可以使用設計模式的Facade模式來簡化層間的接口。這種做法非常有效,EJB中的SessionBean和EntityBean區分就含有這種設計思路。

同樣的,不同層之間的資料傳遞也存在問題。如果不同層的實體節點在一起還好辦,如果不在一起,那就需要使用到分布式技術了。因為不同機器的記憶體位址編碼是不同的,如果接口之間采用對象引用的方式,那一定會出現問題。是以會将對象打包成字元串,發送到目标機器後再還原為對象。所有的分布式平台都提供了對這種技術的支援,這裡就不多說了。但是這種實作技術會對我們的設計思路産生影響,少量的資料直接使用字元串來傳遞,資料量大的話,就需要使用封裝了資料的對象。這類對象的設計需要非常的小心。在設計過程中可以參照開發平台提供的一些标準做法。同樣的,資料的請求的頻率也是難點之一。過于頻繁的操作來自後端的資料會加大系統的開銷。是以,在設計調用方法時同樣需要結合實際應用來考慮。

兼顧效率

一般來說,純粹的面向對象設計者設計出的軟體都會比較完美。但是需要付出一定的代價。在一些大的軟體平台上程式設計的時候,往往需要利用到平台的一些機制。最典型的就是平台的事務機制(最典型的包括J2EE平台的JTS,以及COM+平台的MTS),但是事務機制的實作往往需要平台大量對象的支撐。這種情況下,建立一個支援事務的對象的開銷是很大的。處理這種問題有一種變通的辦法,就是僅僅對需要事務支撐的對象提供事務支援。這就意味着,一個單獨的業務實體類,可能需要根據是否支援事務分為兩種類:對該業務實體的select方法不需要事務的支援,隻有update和delete方法才需要有事務的支援。這是不符合純面向對象設計者的觀點的。但是這種做法卻可以獲得比較優秀的效率。

圖1 将單個的業務實體分為不同的實作

應該承認,這種提高效率的做法加大了複雜度。因為對于用戶端來說,它們并不關心具體的實作技術。要求用戶端在某一種情況下調用這個類,在其它情況下又調用另一個類,這種做法既不符合面向對象的設計思路,也增大了層間耦合度及複雜性。是以,我們可以考慮使用接口或是外觀類(參見設計模式一書中的facade模式),把具體的實作封裝起來,而隻把使用者關心的部分提供給使用者。這方面的技巧我們在下面的章節中還會提到。

以疊代的方式進行分層

軟體設計中的疊代做法同樣可以适用于分層。根據自己的經驗,在一開始就定義好所有的層次是很難的。除非有着非常豐富的經驗,都則實作和原先的設計總有或大或小的差距。是以調整勢在必行。每一次的疊代都能夠對分層技術進行改進,并為後一個項目積累了經驗。

這裡的分層疊代不可以過于頻繁,每一次的疊代都是對架構的重大修改,都是需要投入人力的,而且會影響到軟體開發的進度。但是成功的疊代的效果是非常明顯的,能夠在接下來的開發周期中起到穩定架構,減少代碼量,提升軟體品質的功效。注意,不要讓新潮技術成為分層疊代的推動力。這是開發人員都常犯的毛病,這并不是什麼缺點,隻能稱為一種職業病吧。分層疊代的推動力應該源自于需求的演進以及現有架構的不穩定已經妨礙了軟體進一步的開發。是以這需要團隊中的技術主管對技術有着非常好的把握。

重構能夠對疊代有所幫助。嗅出代碼中隐藏的壞味道并加以改進。應該說,疊代是一種比較激烈的做法,更好的做法是在開發中不斷的對架構、層次進行調整。但這對團隊、技術、方法、過程都有着很高的要求。是以疊代仍然是一種主要的改進手段。

層内的細分

分層的思路還可以适用于層的内部。層内的細分并沒有固定的方式,其驅動因素往往是出于封裝性和重用的考慮。例如,在EJB體系中的業務層中,實體Bean負責實作業務對象,是以一個應用往往擁有大量的實體Bean。而使用者端并不需要了解每一個的實體Bean,對它們來說,隻要能夠完全一些業務邏輯就可以了,但完成這些業務邏輯則需要和多個實體Bean打交道。是以EJB提供了會話Bean,來負責把實體Bean封裝起來,使用者隻知道會話Bean,不知道實體Bean的存在。這樣既保證了實體Bean的重用性,又很好的實作了封裝。

面向接口程式設計

在前面的章節中,我們提到一個接口設計的例子。為什麼我們提倡接口的設計呢?Martin Fowler在他的分析模式一書中指出,分析問題應該站在概念的層次上,而不是站在實作的層次上。什麼叫做概念的層次呢?簡單的說就是分析對象該做什麼,而不是分析對象怎麼做。前者屬于分析的階段,後者屬于設計甚至是實作的階段。在需求工程中有一種稱為CRC卡片的玩藝兒,是用來分析類的職責和關系的,其實那種方法就是從概念層次上進行面向對象設計。是以,如果要從概念層次上進行分析,這就要求你從領域專家的角度來看待程式是如何表示現實世界中的概念的。下面的這句話有些拗口,從實作的角度上來說,概念層次對應于合同,合同的實作形式包括接口和基類。簡單的說吧,在概念層次上進行分析就是設計出接口(或是基類),而不用關心具體的接口實作(實作推遲到子類再實作)。結合上面的論述,我們也可以這樣推斷,接口應該是要符合現實世界的觀念的。

在Martin Fowler的另一篇著作中提到了這樣一個例子,非常好的解釋了接口程式設計的思路:

interface Person {

public String name();

public void name(String newName);

public Money salary ();

public void salary (Money newSalary);

public Money payAmount ();

public void makeManager ();

}

interface Engineer extends Person{

public void numberOfPatents (int value);

public int numberOfPatents ();

}

interface Salesman extends Person{

public void numberOfSales (int numberOfSales);

public int numberOfSales ();

}

interface Manager extends Person{

public void budget (Money value);

public Money budget ();

}

可以看到,為了表示現實世界中人(這裡其實指的是員工的概念)、工程師、銷售員、經理的概念,代碼根據人的自然觀點設計了繼承層次結構,并很好的實作了重用。而且,我們可以認定該接口是相對穩定的。我們再來看看實作部分:

public class PersonImpFlag implements Person, Salesman, Engineer,Manager{

// Implementing Salesman

public static Salesman newSalesman (String name){

PersonImpFlag result;

result = new PersonImpFlag (name);

result.makeSalesman();

return result;

};

public void makeSalesman () {

_jobTitle = 1;

};

public boolean isSalesman () {

return _jobTitle == 1;

};

public void numberOfSales (int value){

requireIsSalesman () ;

_numberOfSales = value;

};

public int numberOfSales () {

requireIsSalesman ();

return _numberOfSales;

};

private void requireIsSalesman () {

if (! isSalesman()) throw new PreconditionViolation ("Not a Salesman") ;

};

private int _numberOfSales;

private int _jobTitle;

}

這是其中一種被稱為内部标示(Internal Flag)的實作方法。這裡我們隻是舉出一個例子,實際上我們還有非常多的解決方法,但我們并不關心。因為隻要接口足夠穩定,内部實作發生再大的變化都是允許的。如果對實作的方式感興趣,可以參考Matrin Fowler的角色模組化的文章或是我在閱讀這篇文章的一篇筆記。

通過上面的例子,我們可以了解到,接口和實作分離的最大好處就是能夠在用戶端未知的情況下修改實作代碼。這個特性對于分層技術是非常适用的。一種是用在層和層之間的調用。層和層之間是最忌諱耦合度過高或是改變過于頻繁的。設計優秀的接口能夠解決這個問題。另一種是用在那些不穩定的部分上。如果某些需求的變化性很大,那麼定義接口也是一種解決之道。舉個不恰當的例子,設計良好的接口就像是我們日常使用的萬用插座一樣,不論插頭如何變化,都可以使用。

最後強調一點,良好的接口定義一定是來自于需求的,它絕對不是程式員絞盡腦汁想出來的。

資料映射層

在各個層的設計中,可能比較令人困惑的就是資料映射層了。由于篇幅的關系,我們不可能在這個問題上讨論太多,隻能是抛磚引玉。如果有機會,我們還可以來談談這方面的話題。

面向對象技術已經成為軟體開發的一種趨勢,越來越多的人開始了解、學習和使用面向對象技術。而大多數的面向對象技術都隻是解決了記憶體中的面向對象的問題。但是鮮有提到持久性的面向對象問題。

面向對象設計的機制與關系模型有很大的不同,這造成了面向對象設計與關系資料庫設計之間的不比對。面向對象設計的基本理論包括耦合、聚合、封裝、繼承、多态,而關系資料模型的理論則完全不同,它的基本原理是資料庫的三大範式。最明顯的一個例子是,Order對象包括一組的OrderItem對象,是以我們需要在Order類中設計一個容器(各個程式設計語言都提供了一組的容器對象及相關操作以供使用)來存儲OrderItem,也就是說Order類中的指針指向OrderItem。假設Order類和OrderItem分别對應于資料庫的兩張表(最簡單的映射情況),那麼,我們要實作二者之間的關系,是通過在OrderItem表(假設名稱一樣)增加指向Order表的外鍵。這是兩種完全不同的設定。資料映射層的作用就是向使用者端隐藏關系資料庫的存在。

自己開發一個對象/關系映射工具是非常誘人的。但是應該考慮到,開發這樣一個工具并不是一件容易的事,需要付出很大的成本。尤其是手工處理資料一緻性和事務處理的問題上。它比你想象的要難的多。是以,擷取一個對象/關系映射工具的最好途徑是購買,而不是開發。

總結

分層對現代的軟體開發而言是非常重要的概念。也是我們必須學習的知識。分層的總體思路并沒有什麼特别的地方,但是要和自己的開發環境、應用環境結合起來,你還需要付出很多的努力才行。

在完成了分層之後,軟體架構其實已經清晰化了。下面的讨論将圍繞着如何改進架構,如何使架構穩定方面的問題進行。

11.精化和合并

對于一個已經初步建立好的模型(分析模型或是設計模型)來說,對其進行精化和合并是必要的步驟。

Context

建立架構願景,為架構的設計定義了主要的設計政策和實作思路。應用分層的原則則對整個的軟體進行了結構上的劃分,并定義了結構的不同部分的職責。而現在,我們需要對初步完成的模型進行必要的改進。

Problem

我們如何對初始架構模型進行改進?

Solution

對模型進行改進的活動可以分為精化和合并兩種。我們先從精化開始。

首先,我們手頭上的初始架構模型已經包括了總原則(參見架構願景模式)和層結構(參見分層模式)兩部分的内容。現在我們要做的工作是根據需求和架構原則來劃分不同的粗粒度元件。粗粒度元件來源于分析活動中的業務實體。把具有很強相關性業務實體組合起來,形成一個集合。集合内部存在錯綜複雜的關系,同時集合向外部提供服務接口。這樣的集合就稱為粗粒度元件。粗粒度元件對外的接口和内部的實作是相區分的。粗粒度元件的形式有很多,Java平台上的Jar檔案、Windows平台上的dll檔案,甚至古老的.o或.a檔案都可以是粗粒度元件的表現形式。設計優秀的粗粒度元件應該隻是完成一項功能,這一點是它與子系統的主要區分。一個系統中可能包括會計子系統、庫存管理子系統。但是提供會計粗粒度元件或是庫存管理粗粒度元件是沒有什麼意義的。因為這樣的粗粒度元件的範圍過于廣泛,難以發揮重用的價值。

粗粒度元件是可以(可能也是必須)跨越層次的。粗粒度元件擁有持久化的行為,擁有業務邏輯,需要表示層的支援。這樣看起來,它所屬的軸向和層次的軸向是互相垂直的。

粗粒度元件來源于需求。需求階段産生的需求說明書或是用例模型将是粗粒度元件開發的基礎。在擁有了需求工件之後,我們需要對需求進行功能性的劃分,将需求分為幾個功能組,這樣我們基本上就可以得到相應的粗粒度元件了。如果系統比較龐大,可以對功能組再做細分。這取決于粗粒度元件的範圍。過小的範圍,将會造成粗粒度元件不容易使用,使用者需要了解不同的粗粒度元件之間的複雜關系,最後的結果也将包含大量的元件和複雜的邏輯。過大的範圍,則會造成粗粒度元件難以重用,導緻粗粒度元件稱為一個子系統。

假設我們需要開發一個人力資源管理系統。經過整理,它的需求大緻分為這樣幾個部分:

  • 組織結構的設計和管理:包括員工職務管理和員工所屬部門的管理。
  • 員工資料的管理:包括員工的基本資料和簡單的考評資料。
  • 日常事務的管理:包括了對員工的考勤管理和工資發放管理。

對于前兩項的功能組,我們認為建立粗粒度元件是比較合适的。但是對于第三項功能組,我們認為範圍過大,因為将之分為考勤管理和工資管理。現在我們得到了四個粗粒度元件。分别是組織結構元件、員工資料元件、員工考勤元件、員工工資元件。

在得到了粗粒度元件之後,我們的工作需要分為兩個部分:第一個部分是定義不同的粗粒度元件之間的關系。第二個部分是在粗粒度元件的基礎上定義業務實體或是定義細粒度元件。

不同的粗粒度元件之間的關系其實就是前文提到的粗粒度元件的外部接口。如果可能,在粗粒度元件之間定義單向的關聯(如上圖所示)可以有效的減少元件之間的耦合。如果必須要定義雙向的關聯,請確定關聯雙方元件之間的一緻性。在上圖中,我們可以清晰的看出,組織結構處于最底層,員工資料依賴于組織結構,包括從組織結構中獲得員工的所屬部門,以及員工職務等資訊。而對于考勤、工資元件來說,需要從員工資料中擷取必要的資訊,也包括了部門和職務兩方面的資訊。這裡有兩種關聯定義的方法,一種是讓考勤元件從組織結構元件中獲得部門和職務資訊,從員工資料中獲得另外的資訊,另一種是如上圖一樣,考勤元件隻從員工資料元件中獲得資訊,而員工資料元件再使用委托,從組織結構中獲得部門和職務的資訊。第二種做法的好處是向考勤、工資元件屏蔽了組織結構元件的存在,并保持了資訊擷取的一緻性。這裡示範的隻是元件之間的簡單關系,現實中的關系不可能如此的簡單,但是處理的基本思路是一樣的,就是盡可能簡化元件之間的關系,進而減少它們之間的耦合度。

考慮另一種的需求情況,在原先的系統的基礎上,我們又增加了會計子系統部分,其中的一個粗粒度元件是對部門、員工進行成本分析。在原先的模型基礎上,我們增加了對分層的考慮。從下圖中,我們可以看到,組織結構元件已經發揮了它的重用性,被成本分析元件使用了。從分層上考慮(參見分層模式),我們将組織結構元件劃分到工具層,而将其它的元件劃分到領域層,并在領域層中進行子系統級的劃分。從某個角度上來說,這種做法類似于一個分析模型的模組化過程。總之,這個過程中,最重要的就是定義好不同的元件的關系。盡管這中分析是初始的、模糊的。

在得到了粗粒度元件模型之後,我們需要對其進行進一步的分析,以得到細粒度的元件。細粒度的元件具有更好的重用性,并使得架構設計的精化工作更進一步。按Jacobson推薦的面向對象軟體工程(OOSE)的做法,我們需要從軟體的目标領域中識别出關鍵性的實體,或者說是領域中的名詞。例如上例中的員工、部門、工資等。然後決定它們應該歸屬于哪些粗粒度元件。先識别細粒度元件還是先識别粗粒度元件并沒有固定的順序。

最初得到的元件模型可能并不完善,需要對其進行修改。可能某個元件中的類太多了,過于複雜,我們就需要對其進一步精化、分為更細的元件,也許某個元件中的類太少了,需要和其它的元件進行合并。也許你會發現某兩個元件之間存在重複的要素,可以從中抽取出共性的部分,形成新的元件。元件分析的過程并沒有一種标準的做法,你隻能夠根據具體的案例來進行分析。到最後,你可能會為其中的幾個類的歸屬而苦惱不已,不要在它們身上浪費太多的時間,盡善盡美的模型并不存在。

最後的模型将會明确的包含幾個經過精化之後的粗粒度元件。粗粒度元件之間的關系也會進行一次重新定義。如果這時候,粗粒度元件之間仍然存在着複雜的關系,也許意味着你的業務邏輯比較複雜,是以這個部分需要你投入比較多的精力來處理。當然,你可以通過一些技巧來減少不同元件之間的耦合程度。這裡有幾種可參考的辦法:

第一種方法是使用外觀(Facade)模式(在分層模式中,我們就提到過外觀模式)。如下圖所示,新引入的BusinessFacade類充當了外觀的角色,将調用者和複雜的業務類隔離了起來,調用者無須知道業務類之間的複雜的關系,就能夠進行業務處理,進而大大降低了調用者和業務類之間的耦合度。這種方法在實踐中經常被采用,适合用在内部關系較為複雜的元件中,也适合用在業務層向表示層釋出接口的情況中。對于外觀模式來說,我們可以在BusinessFacade類的業務方法中提供參數,來實作資料的傳遞。這對于一些資料較少的情景特别的适用。如果當資料種類較多時,也可以使用參數類或值類來達到資料傳送的目的。

第二種方法是使用指令(Command)模式,該模式也來自于設計模式一書。在處理參數時,指令模式使用了一系列的set方法來逐一設定參數,最後調用execute()來執行業務邏輯。指令模式的好處是為調用者提供了統一的接口,調用者并不需要關心具體的業務邏輯,他需要做的就是設定資料,并調用execute()方法。如果遇到業務邏輯需要用到較多的參數,逐個的調用set方法過于麻煩了,也可以提供一個setValues()方法來處理多個參數。當然,該模式也有其弱點,如果業務方法太多,那麼相應的Command類也會随之增多。這是我們不希望看到的。

除了上面介紹的兩種方法以外,還可以使用諸如工廠(Factory)模式、業務代表(Business Delegate)模式等方法來減少不同元件之間的耦合度。應該認識到,不同的設計模式有其不同的上下文環境,在架構設計中使用設計模式(以及分析模式)有助于優化設計,但是請注意模式的上下文環境,誤用或濫用模式反而會導緻設計的失誤。

以上介紹的方法除了能夠降低不同的元件之間的耦合度之外,還可以起到向調用者隐藏實作的功能。這一點對于重構活動(參見Refactoring模式)非常的關鍵,因為它可以有效的緩解在對元件進行重構時将變化擴散到其它的元件中。

精化是對模型進行改進的第一步。完成的模型基本上代表了最終的軟體。但如果我們對其進行認真的檢查,我們會發現模型仍然存在問題。這時候的問題主要展現在設計模型過于肥大了。如果說精化使得模型變得複雜,那麼合并就是使得模型變得簡單。千萬不要以為這兩項工作是互斥的,通過這兩項活動,可以使得模型得到極大的改進。

還記得我們在簡單設計模式中提到的簡單原則嗎?在進入下面的讨論之前,請確定你能夠了解簡單原則,這是靈活的核心原則之一。

在上文中我們提到了一些設計模式的使用,而在整個的靈活架構設計的文章中,我們也大量地讨論了模式。而在很多時候,我們其實是在不恰當的使用模式來解決一些簡單的問題。是以,在使用模式之前,我們應該回顧需求說明書(或是用例模型)上的相關部分,确定是否需要使用模式。

在一次的設計軟體的持久性機制的時候,我選用了DTO(Data Transfer Object)模式作為持久層的實作機制。原因隻是我在前一天晚上看了這個模式,并覺得它很酷。看起來我是犯了模式綜合症了(當學習了一個模式之後,就想方設法的使用它)。在花費了很多的時間學習并實作該模式之後,我發現該模式并沒能夠發揮它應有的作用。原因是,模式的上下文環境并不适合用在目前的軟體中,原本隻需要用JDBC就可以實作的功能,在使用了模式之後,反而變得複雜了。糟糕的是,我不得不向開發人員解釋這個模式,吹捧這個設計模式的精妙之處。但是結果令人不安。開發人員顯然不能夠了解這個模式在這裡發揮了什麼樣的作用。最後,我去掉了這個模式,設計得到了簡化。

同樣的,在開發過程還存在各種各樣的過度設計的例子,尤其是資料庫通路、線程安全、一緻性等方面的設計。這些問題往往需要花費大量的時間來處理,但是他們的價值卻并不高,尤其是小型的系統。當然,在設計一些大型的系統時,這些問題是必須要考慮的。

而當使用設計模式在對不同的元件進行整合的時候,我們也需要對元件的行為進行合并。将不同元件之間的相同的行為合并到一個元件中,尤其是那些關系非常複雜的元件。這樣可以把複雜的關系隐藏到元件内部,而簡化其對外提供的接口。

很難評判設計是否已經完成了。這裡有兩個不同的極端。花費了過多的時間在初始設計上,以及過度的疊代。在初始模型上花費太多的時間并不一定能夠得到盡善盡美的模型,相反的,可能還會因為設計師鑽牛角尖的行為導緻設計模型的失誤。而在過于頻繁的疊代對于改進模型同樣沒有好處,因為實際上,你不是在改進模型,而是在改變模型。請區分這兩種完全不同的行為,雖然它們似乎很相似。在Refactoring模式中,我們還會進一步對疊代和模型改進進行讨論。

一種判斷方法是請編碼人員來評判設計是否完成。設計模型最後是要交給編碼人員,指導編碼人員的開發工作的。是以,如果編碼人員無法了解模型,這個模型設計的再好看,也沒有太大的作用。另一方面,如果編碼人員認為模型已經足夠指導開發工作了,那麼還有什麼必要再畫蛇添足下去了呢?不同水準、不同經驗的編碼人員對模型的要求也不一樣。在我們的工作中,對資深開發人員和開發新手的釋出的模型是不一樣的。對于資深的開發人員而言,可能隻需要對他說,在元件A群組件B之間使用外觀模式,他就能夠了解這句話的意思,并立即着手開發,可是對于沒有經驗的開發人員,就需要從模式理論開始進行講述。對于他們來說,設計模型必須足夠充分,足夠細緻。設定需要把類、方法、參數、功能描述全部設計出來才可以。

複審是避免設計模型出現錯誤的重要手段。強烈建議在架構設計過程中引入複審的活動。複審對于避免設計錯誤有着重大的幫助。複審應該着重于粗粒度元件的分類和粗粒度元件之間的關系。正如後續的Refactoring模式和穩定性模式所描繪的那樣,保持粗粒度元件的穩定性有助于重構行為,有助于架構模型的改進。

12.Refactoring

當架構模型進行疊代的過程中,必然伴随着對模型進行修改和改進。我們如何防止對模型的修改,又如何保證對模型進行正确的改進?

Context

架構模型通過精化、合并等活動之後,将會直接用于指導代碼。而這個時候,往往就會暴露出一些問題出來,通常在實際編碼中,發現架構存在或大或小的問題和錯誤,導緻編碼活動無法繼續。這時候我們就需要對架構模型進行修改了。而架構設計的過程本身是一個疊代的過程,這就意味着在每一次的疊代周期中,都需要對架構進行改進。

Problem

我們如何避免對架構模型進行修改?又如何保證架構進行正确的改進?

Solution

我們從XP中借用了一個詞來形容架構模型的修改過程――Refactoring,中文可以譯作重構。這個詞原本是形容對代碼進行修改的。它指的是在不改變代碼外部行為(可觀察行為)的情況下對代碼進行修改。我們把這個詞用在架構模型上,因為經過精化和合并之後的架構模型往往由很多個粗粒度元件構成。這些元件之間存在一定的耦合度(雖然我們可以令耦合度盡可能的低,但是耦合度一定是存在的),任何一個元件的重構行為都會使變化擴散到系統中的其它元件。這取決于被重構的元件和其它元件之間的相對關系。如果被重構的元件屬于層次較低的工具層上,那麼這次的修改就可以引起模型很大的變動。

在精化和合并模式中,我們提到了改變和改進的差別,是以,我們的對策主要分為兩種:如何防止改變的發生,以及,使用重構來改進軟體架構。

防止改變的發生

在任何時候,需求的變更總是對架構及軟體有着最大的傷害。而需求變更中最大問題是需求蔓延。很多人都有這樣的感覺,項目完成之後,發現初期的計劃顯得那麼陌生。在項目早期對需求進行控制是重要的,但并不是該模式談論的重點。我們更關注在項目中期的需求蔓延問題和晚期的需求控制問題。關于這方面的詳細讨論,請參見穩定化模式。在項目中期,尤其是編碼工作已經開始之後,要盡可能避免出現需求蔓延的情況。需求蔓延是經常發生的,可能是因為使用者希望加入額外的功能,或是随着使用者對軟體了解的加深,發現原有的需求存在一定的不足。完全防止需求蔓延是無法做到的,但是需要對其進行必要的控制。例如,有效的估計變更對開發、測試、文檔、管理、組織等各個方面帶來的影響。

避免發生改變的另一個有效的辦法是從軟體過程着手。疊代法或漸進傳遞法都是可用的方法。一個軟體的架構設計往往是相對複雜的,其中涉及到整體結構、具體技術等問題。一次性考慮全部的要素,就很容易發生考慮不周詳的情況。人的腦容量并沒有我們想象的那麼大。将架構設計分為多個疊代周期來進展,可以減少單次疊代周期中需要模組化的架構數量,是以可以減少錯誤的發生。另一方面,疊代次數的增多的直接結果是時間的延長,此外還有一個潛在的問題,如果由于設計師的失誤,在後期的疊代中出現問題,必然會導緻大量的返工。因為之前的模型已經實作了。在得與失之間,我們如何找到适當的平衡點呢?

疊代次數應該根據不同軟體組織的特點來制定,對于初期的疊代周期而言,它的主要任務應該是制定總原則(使用架構願景模式)、定義層結構和各層的職責(使用分層模式)、解決主要的技術問題上。在這個過程中,可以列出設計中可能會遇到的風險,并根據風險發生的可能性和危害性來排定優先級,指定專人按次序解決這些問題。除此之外,在初期參考前一個項目的經驗,讓團隊進行設計(參見團隊設計模式),這些組織保證也是很重要。初期的疊代過程是防止改變的最重要的活動。

請注意需求中的非功能需求。如果說功能需求定義了架構設計的目标的話,非功能需求就對如何到達這個目标做出了限制。例如,對于實作一個報表有着多種的操作方法,但是如果使用者希望新系統和舊系統進行有效的融合,那麼實作的方式就需要好好的規劃了。請從初期的疊代過程就開始注意非功能需求,因為如果忽略它們,在後期需要花費很大的精力來調整架構模型。試想一下,如果在項目晚期的壓力測試中,發現現有的資料庫通路方法無法滿足使用者基本的速度要求,那對項目進行将會造成多麼大的影響。

注意架構的穩定性。在精化和合并模式中,我們提到了一些模式,能夠降低不同元件之間的耦合度。并向調用者隐藏具體的實作。接口和實作分離是設計模式最大的特點,請善用這一點。

盡可能的推延正式文檔的編寫。在設計的初期,修飾模型和編寫文檔通常都沒有太大的意義。因為此時的模型還不穩定,需要不斷的修改。如果這時候開始投入精力開發文檔,這就意味着後續的疊代周期中将會增加一項維護文檔一緻性的工作了。而這時候的文檔卻無法發揮出它真正的作用。但是,延遲文檔的編寫并不等于什麼都不做,無論什麼時候進行設計,都需要随手記錄設計的思路。這樣在需要的時候,我們就能夠有充分的資料對設計進行文檔化的工作。

對軟體架構進行重構

Martin Fowler的Refactoring一書為我們列舉了一系列的對代碼進行重構方法。架構也是類似的。

重構到模式

Joshua Kerievsky在《Refactoring to Patterns》一書中這樣描述重構和模式的關系:

Patterns are a cornerstone of object-oriented design, while test-first programming and merciless refactoring are cornerstones of evolutionary design

(模式是面向對象設計的基石,而測試優先程式設計和無情的重構則是設計演進的基石)。作者在文中着重強調了保持适度設計的重要性。

在作者看來,模式常常扮演着過度設計的角色。而在解決這個問題的同時又利用模式的優點的解決方法是避免在一開始使用模式,而是在設計演進中重構到模式。這種做法非常的有效,因為在初始設計中使用模式的話,你的注意力将會集中到如何使用模式上,而不是集中在如何滿足需求上。這樣就會導緻不恰當的設計(過度設計或是設計不充分)。是以,在初始設計中,除非非常有把握(之前有類似的經驗),否則我們應當把精力放在如何滿足需求上。在初始模型完成後(參見精化和合并模式中的例子),我們會對架構進行重構,而随着疊代的演進,需求的演進,架構也需要演進,這時候也需要重構行為。在這些過程中,如果發現某些部分的設計需要額外的靈活性來滿足需求,那麼這時候就需要引入模式了。

在軟體開發過程中,我們更常的是遇見設計不充分的情況,例如元件之間耦合度過高,業務層向用戶端暴露了過多的方法等等。很多的時候,産生這種現象是由于不切實際的計劃而導緻的。開發人員不得不為了最終期限而趕工,所有的時間都花費在新功能上,而完成的軟體則被仍在一邊。這樣産出的軟體是無法保證其品質的。對于這種情況,我們也需要對設計進行重構,當然,合理的計劃是大前提所在。團隊的上司者必須向高層的管理者說明,現在的這種做法隻會導緻未來的返工,目前的高速開發是以犧牲未來的速度為代價的。因為低劣的設計需要的高成本的維護,這将抵消前期節省的成本。如果軟體團隊需要可持續的發展,那麼請避免這種殺雞取卵的行為。

是以,使用模式來幫助重構行為,以實作恰當的設計。

測試行為

重構的前提是測試優先,測試優先是XP中很重要的一項實踐。對于編碼來說,測試優先的過程是先寫測試用例,再編寫代碼來完成通過測試用例(過程細節不隻如此,請參看XP的相關書籍)。但是對于架構設計來說,測試行為是發生在設計之後的,即在設計模型完成後,産出相應的測試用例,然後再編碼實作。這時候,測試用例就成為聯系架構設計和編碼活動的紐帶。

另一方面,在設計進行重構時,相應的測試用例也由很大的可能性發生改變。此時往往會發生需要改變的測試代碼超出普通代碼的情況。避免這種情況一種做法是令你的設計模型的接口和實作相分離,并使測試用例針對接口,而不是實作。在精化和合并模式中,我們提到了一些模式,能夠有助于穩定設計和測試用例。Martin Fowler在他的Application Facade一文中,提到使用Facade模式來分離不同的設計部分,而測試則應當針對facade來進行,其思路也是如此。

考慮一個使用者轉帳的用例。銀行需要先對使用者進行權限的稽核,在稽核通過之後才允許進行轉帳(處于簡便起見,圖中忽略了對象的建立過程和調用參數):

需要分别針對三個類編寫測試用例,設計模型一旦發生變化,測試用例也将需要重新編寫。再考慮下面的一種情況:

現在的設計引入了TransferFacade對象,這樣我們的測試用例就可以針對TransferFacade來編寫了,而轉帳的業務邏輯是相對比較穩定的。使用這種測試思路的時候,要注意兩點:首先,這并不是說其它的類就不需要測試用例了,這種測試思路僅僅是把測試的重點放在外觀類上,因為任何時候充分的測試都是不可能的。但其它類的測試也是必要的,對于外觀類來說,任何一個業務方法的錯誤都會導緻最終的測試失敗。其次,當外觀類的測試無法達到穩定測試用例的效果時,就沒有必要使用外觀類了。

隻針對有需要的設計進行重構。

任何時候,請確定重構行為僅僅對那些有重構需要的設計。重構需要花費時間和精力,而無用的重構除了增大設計者的虛榮心之外,并不能夠為軟體增加價值。重構的需要來源于兩點:一是需求的變更。目前的設計可能無法滿足新的需求,是以需要重構。二是對設計進行改進,以得到優秀簡潔的設計。除了這兩種情況,我們不應該對設計模型進行重構。

使用文檔記錄重構的模式。

應該承認,模式為設計提供了充分的靈活性。而這些設計部分往往都是模型的關鍵之處和難點所在,是以需要對模式進行文檔化的工作,甚至在必要的時候,對這部分的設計進行教育訓練和指導。確定你的團隊能夠正确的使用文檔來設計、了解、擴充模式。我們在解決方案的前一個部分提到了盡可能延遲文檔的建立。而在設計重構為模式的時候,我們就需要進行文檔化的工作了。因為模式具有靈活性,能夠抵抗一定的變更風險。

重構并保持模式的一緻性

正如上一節所說的那樣,模式并不是一個很容易了解的東西,雖然它保持了設計的靈活性和穩定性。對于面向對象的新手而言,模式簡直就像是飛碟一樣,由于缺少面向對象的設計經驗,他們無法了解模式的處理思路,在實踐中,我們不隻一次的碰到這種情況。我們不得不重頭開始教授關于模式的課程。是以,最後我們在軟體設計采用一定數量的模式,并確定在處理相同問題的時候使用相同的模式。這樣,應用的模式就成為解決某一類的問題的标準做法,進而在一定程度上降低了學習的曲線。

保持模式的一緻性的另一個方面的含義是将模式作為溝通的橋梁。軟體開發是一種團隊的行為。是以溝通在軟體開發中扮演着很重要的角色。試想一下,開發人員在讨論軟體設計的時候,隻需要說"使用工廠模式",大家就都能夠明白,而不是費勁口舌的說明幾個類之間的關系。這将大大提高溝通的效率。此外,模式的使用和設計的重構對于提高團隊的程式設計水準,培養後備的設計人員等方面都是很有意義的。

13.穩定化

靈活方法的興起對設計提出了新的要求,其最核心的一點是針對無法在項目一開始就固化的需求進行演進型的設計。在項目一開始就進行細緻、準确的架構設計變得越來越難,是以,架構設計在項目的進展中被不斷的改進,這相應導緻了編碼、測試等活動的不穩定。但是,軟體最終必須是以穩定的代碼形式傳遞的。是以,架構設計必須要經曆從不穩定到穩定的過程。而架構設計能夠穩定的前提就是需求的穩定。

需求當機

靈活方法和傳統方法的差別在于對待變化的态度。傳統的做法是在編碼活動開始之前進行充分、細緻的需求調研和設計工作,并簽署合同。確定所有的前期工作都已經完成之後,才開始編碼、測試等工作。如果發生需求變化的情況,則需要進行嚴格的控制。而在現實中,這種方法往往會由于對開發人員和客戶雙方需求了解的不一緻,需求本身的變化性等問題而導緻項目前期就完全固化需求變得不現實。結果要麼是拒絕需求的改變而令客戶的利益受損,要麼是屈從于需求的改變而導緻項目失控。靈活方法則不同,它強調擁抱變化。對于易變的需求,它使用了一系列實踐,來馴服這隻烈馬。其核心則是疊代式開發。應該承認,做到掌握需求并不是一件容易的事,而疊代開發也很容易給開發團隊帶來額外的高昂成本。要做到這一點,需要有其它實踐的配合(下文會提到)。是以,我們在疊代開發進入到一定的階段的時候,需要進行需求當機。這時候的需求當機和上面提到的一開始就固化需求是不一樣的。首先,使用者經曆過一次或幾次的疊代之後,對軟體開發已經有了形象的認識,對需求不再是霧裡看花。其次,通過利用原型法等實踐,使用者甚至可能對軟體的最終形式已經有了一定的經驗。這樣,使用者提出的需求基本上可以代表他們的真實需求。即便還有修改,也不會對軟體的架構産生惡劣的影響。最後,需求當機的時點往往處于項目的中期,這時候需求如果仍然不穩定,項目的最後成功就難以得到保證。

在需求當機之前,不要過分的把精力投入到文檔的制作上,正确的做法是保留應有的資訊,以便在稍後的過程中完成文檔,而不是在需求未确定的時候就要求格式精美的文檔。在格式化文檔上很容易就會花費大量的時間,如果需求發生改變,所有的投入都浪費了。文檔的投入量應該随着項目的進行而增大。但這決不是說文檔不重要,因為你必須要保留足夠的資訊,來保證文檔能夠順利的建立。

確定有專人來接受對變更需求的請求,這樣可以確定需求的變化能夠得以控制。這項工作可以由項目經理(或同類角色)負責,也可以由下文所說的變更委員會負責。小的、零散的需求很容易對開發人員産生影響,而他們有更重要的任務――把項目往前推進。此時項目經理就像是一個緩沖區,由他來決定需求的分類、優先級、工作量、對現有軟體的影響程度等因素,進而安排需求變更的計劃――是在本次疊代中完成,還是在下一次疊代中完成。

建立需求變更委員會是一種很好的實踐,它由項目的不同類型的涉衆組成,可能包括管理、開發、客戶、文檔、品質保證等方面的人員。他們對需求變更做出評估及決定,評估需求對費用、進度、及各方面的影響,并做出是否以及如何接受需求的決定。由于委員會往往涉及到整個項目團隊,是以效率可能會成為它的主要缺點。在這種情況下,一方面可以加強委員會的管理,一方面可以保證委員會隻處理較大的需求變更。

在項目的不同時候都需要對需求進行不同程度的限制,這聽起來和我們提倡的擁抱變化有些沖突。其實不然。對需求進行限制的主要目的是防止需求的膨脹和蔓延,避免不切實際的功能清單。我們常常能夠提到諸如 "這項功能很酷,我們的軟體也要包含它"以及"我們的對手已經開發出這項功能了,最終的軟體必須要包含這項功能"之類的話語。這就是典型的需求蔓延的征兆。在項目開始時正确的估計功能進度,在項目中期時控制需求,在項目晚期是杜絕新增需求,甚至剪切現有需求。通過三種方法來保證軟體能夠保時保質的推出。

穩定架構

即便是需求已經成功的當機了,我們仍然面對一個不夠穩定的架構。這是必然的,而不穩定的程度則和團隊的能力,以及對目标領域的了解程度成反比。是以,架構也需要改進。前一個模式中,我們讨論了對架構的重構,其實這就是令架構穩定的一種方法。經驗資料表明,一系列小的變化要比一次大變化容易實作,也更容易控制。是以在疊代中對架構進行不斷重構的做法乍看起來會托慢進度,但是它為軟體架構的穩定奠定了基礎。重構講究兩頂帽子的思維方式,即這一個時段進行功能上的增加,下一個時段則進行結構的調整,兩個時段決不重複,在對增加功能時不考慮結構的改進,在改進結構時也同樣不考慮功能的增加。而在架構進行到穩定化這樣一個階段之後,其主要的職責也将變為對結構的改進了。從自身的經驗來看,這個階段是非常重要的,對軟體品質的提高,對加深項目成員對目标領域的認識都有莫大的幫助。而這個階段,也是很容易提煉出通用架構,以便軟體組織進行知識積累的。

在這個階段中,讓有經驗的架構師或是進階程式員介入開發過程是非常好的做法。這種做法來自于軟體評審的實踐。無論是對于改進軟體品質,還是提高項目成員素質,它都是很有幫助的。

架構穩定的實踐中暗含了一個開發方法的穩定。程式員往往喜歡新的技術、新的工具。這一點無可厚非。但是在項目中,采用新技術和新工具總是有風險的。可能廠商推出的工具存在一些問題沒有解決,或者該項技術對原有版本的支援并不十分好。這些都會對項目産生不良的影響。是以,如果必須在項目中采用新技術和新工具的話,有必要在項目初期就安排對新事物進行熟悉的時間。而在架構進入穩定之前,工具的用法、技術的方法都必須已經完成試驗,已經向所有成員推廣完畢。否則必須要在延長時間和放棄使用新事物之間做一個權衡。

保證架構穩定的優秀實踐

在文章的開頭,我們就談到說在項目起始階段就制定出準确、詳細的架構設計是不太現實的。是以,靈活方法中有很多的實踐來令最初的架構設計穩定化。實際上,這些實踐并非完全是靈活方法提出的新概念。靈活方法隻是把這些比較優秀的實踐組織起來,為穩定的架構設計提供了保證。以下我們就詳細讨論這些實踐。

在不穩定的環境中尋求穩定因素。什麼是穩定的,什麼是不穩定的。RUP推薦使用業務實體(Business Entity)法進行架構設計。這種方法的好處之一是通過識别業務實體進而建立起來的架構是相對穩定的。因為業務實體在不穩定的業務邏輯中屬于穩定的元素。大家可以想象,公司、雇員、部門這些概念,幾十年來并沒有太大的變化。對于特定的業務也是一樣的。例如對于會計總帳系統來說,科目、餘額、分戶賬、原始憑證,這些概念從來就沒有太大的變化,其對應的行為也相差不大。但是某些業務邏輯就完全相反了。不同的系統業務邏輯不同,不同的時點業務邏輯也有可能發生變化。例如,對于不同的制造業來說,其成本核算的邏輯大部分都是不一樣的。即便行業相同,該業務邏輯也沒有什麼共性。是以,穩定的架構設計應該依賴于穩定的基礎,對于不穩定的因素,較好的做法是對其進行抽象,抽象出穩定的東西,并且把不穩定的因素封裝在單獨的位置,避免其對其它子產品的影響。而這種思路的直接成果,就是下一段提到的針對接口程式設計的做法。例如對于上面提到的成本核算來說,雖然它們是易變的、不穩定的,但是它們仍然存在穩定的東西,就是大部分制造業企業都需要成本核算。這一點就非常的重要,是以着意味着接口方法是相對固定的。

保持架構穩定性的另一種方法是堅持面向接口程式設計的設計方法。我們在分層模式中就提到了面向接口程式設計的設計方法,鼓勵抽象思維、概念思維。從分層模式中提到的示例中(詳見分層模式下篇的面向接口程式設計一節),我們可以看出,接口的一大作用是能夠有效的對類功能進行分組,進而避免客戶程式員了解和他不相關的知識。設計模式中非常強調接口和實作分離,其主要的表現形式也正是如此,客戶程式員不需要知道具體的實作,對他們來說,隻需要清楚接口釋出出的方法就可以了。

從另一個方面來看,之是以要把接口和實作相分離,是因為接口是需求中比較穩定的部分,而實作則是和具體的環境相關聯的。下圖為Java中Collection接口公布出的方法。可以看到,在這個層次上,Collection接口隻是根據容器的特性定義了一些穩定的方法。例如增加、删除、比較運算等。是以這個接口是相對比較穩定的,但是對于具體的實作類來說,這些方法的實作細節都有所差别。例如,對于一個List和一個Array,它們對于增加、删除的實作都是不一樣的。但是對于客戶程式員來說,除非有了解底層實作的需要,否則他們不用了解List的add方法和Array的add方法有什麼不同。另一方面,将這些方法實作為固定的、通用的接口,也有利于接口的開發者。他們可以将實作和接口相分離,此外,隻要滿足這些公布的接口,其它軟體開發團隊同樣能夠開發出合用的應用來。在目前這樣一個講求合作、講求效率的大環境中。這種開發方法是非常重要的。

java.util

Interface Collection

boolean add(Object o)
boolean addAll(Collection c)
void clear()
boolean contains(Object o)
boolean containsAll(Collection c)
boolean equals(Object o)
int hashCode()
boolean isEmpty()
Iterator iterator()
boolean remove(Object o)
boolean removeAll(Collection c)
boolean retainAll(Collection c)
int size()
Object[] toArray()
Object[] toArray(Object[] a)

重構。代碼重構是令架構趨于穩定的另一項方法。準确而言,重構應該是程式員的一種個人素質。但是在實際中,我們發現,重構這種行為更加适合作為開發團隊的共同行為。為什麼這麼說呢?最早接觸重構概念的時候,我對面向對象的認識并不深入。是以對重構的概念并不感冒。但随着經驗的積累,面向對象思維的深入。我漸漸發現,重構是一種非常優秀的代碼改進方式,它通過把原子性的操作,逐漸的改進代碼品質,進而達到改進軟體架構的效果。當程式員逐漸熟練運用重構的時候,他已經不再拘泥于這些原子操作,而是自然而然的寫出優秀的軟體。這是重構方法對各人行為的改進。另一方面,對于一個團隊來說,每個人的程式設計水準和經驗都不一而足,是以軟體的各個子產品品質也都是參差不齊的。這種情況下,軟體品質的改進就已經不是個人的問題了,而該問題的難度要比前一個問題大的多。此時重構方法更能夠發揮其威力。在團隊中提倡使用、甚至半強制性使用重構,有助于分享優秀的軟體設計思路,提高軟體的整體架構。而此時的重構也不僅僅局限在代碼的改進上(指的是Martin Fowler在重構一書中提到的各種方法),還涉及到分析模式、設計模式、優秀實踐的應用上。同時,我們還應該看到,重構還需要其它優秀實踐的配合。例如代碼複審和測試優先。

總結

令架構趨于穩定的因素包括令需求當機和架構改進兩個方面。需求當機是前提,架構改進是必然的步驟。在面向對象領域,我們可以通過一些實踐技巧來保持一個穩定的架構。這些實踐技巧展現在從需求分析到編碼的過程中。穩定化模式和下一篇的代碼驗證模式有很多的關聯,細節問題我們會在下一篇中讨論。

14.代碼驗證

要保證架構的穩定和成功,利用代碼對架構進行驗證是一種實用的手段。代碼驗證的核心是測試,特别是單元測試。而測試的基本操作思路是測試優先,它是靈活方法中非常重要的一項實踐,是重構和穩定核模式的重要保障。

面向對象體系中的代碼驗證

代碼驗證是保證優秀的架構設計的一種方法,同時也是避免出現象牙塔式架構設計的一種措施。我們在上一篇穩定化中提到說架構設計最終将會展現為代碼的形式,是以使用形式化的代碼來對架構進行驗證是最有效的。

由于是代碼驗證,是以就離不開編寫代碼,而代碼總是和具體的語言、編譯環境息息相關的。在這裡我們主要讨論面向對象語言,代碼示例采用的Java語言。利用面向對象語言來進行架構設計有很多的好處:

  • 首先,面向對象語言是一種更優秀的結構化語言,比起非面向對象語言,它能夠更好的實作封裝、降低耦合、并允許設計師在抽象層次上進行思考。這些因素為優秀的架構設計提供了條件。
  • 其次,面向對象語言可以允許設計師隻關注在架構代碼上,而不用關心具體的實作代碼。當然,這并不是說非面向對象的語言就做不到這一點,隻是面向對象語言的表現更優秀一些。
  • 最後,面向對象語言可以進行很好的重用。這就意味着,設計師可以利用原有的知識、原有的軟體體系,來解決新的問題。

此外,利用Java語言,還可以獲得更多的好處。Java語言是一種面向接口的語言。我們知道,Java語言本身不支援多重內建,所有的Java類都是從Object類繼承下來的。這樣,一個繼承體系一旦确定就很難再更改。為了能夠達到多重繼承的靈活性,Java引入了接口機制,使用接口和使用抽象類并沒有什麼不同的地方,一個具體類可以實作多個接口,而用戶端可以通過申明接口類型來使用,如下面這樣:

List employees=new Vctor();

如果需要将Vctor換成LinkedList,那麼除了上面的建立代碼,其它的代碼不需要再做更多的修改。而Vctor這個具體類除了實作List這個接口以外,還實作了Cloneable、Collection、 RandomAccess、Serializable。這說明除了List接口之外,我們還可以通過以上所列的接口來通路Vector類。是以接口繼承能夠成為類繼承的補充手段,發揮十分靈活的作用。同時又避免了多重繼承的複雜性。但是接口中隻能夠定義空方法,這是接口的一個缺陷。是以在實際程式設計中,接口和抽象類通常是一起使用的。我們在Java的java.util包中看到Collection接口以及實作Collection接口的AbstractCollection抽象類就是這方面的例子。你可以從AbstractCollection抽象類(或其某個子類)中繼承,這樣你就可以使用到AbstractCollection中的預設代碼實作,由于AbstractCollection實作了Collection接口,你的類也實作Collection接口;如果你不需要利用AbstractCollection中的代碼,你完全可以自己寫一個類,來實作Collection接口(這個例子中不太可能發生這種情況,因為工具類的重用性已經實作設計的非常好了)。Java中有很多類似的例子。Java語言設計并不是我們讨論的重點,更加深入的讨論可以參看專門的書籍,這裡我們就不作太多的介紹了。

以上花了一些篇幅來讨論面向對象設計和面向接口設計的一些簡單的預備知識。這些知識将成為代碼驗證的基礎。

接口和架構

這裡的接口指的并不是Java中的Interface的概念,它是廣義的接口,在Java語言中具體表現為類的公有方法或接口的方法。在COM體系或J2EE體系中還有類似但不完全相同的表現。對于一個系統的架構來說,最主要的其實就是定義這些接口。通過這些接口來将系統的類聯系在一起,通過接口來為使用者提供服務,通過接口來連接配接外部系統(例如資料庫、遺留系統等)。是以,我們為了對架構進行驗證的要求,就轉化為對接口的驗證要求。

對接口進行驗證的基本思路是保證接口的可測試性。要保證接口具有可測試性,首先要做的是對類和類的職責進行分析。這裡有幾條原則,可以提高接口的可測試性。

1、 封裝原則

接口的實作細節應該封裝在類的内部,對于類的使用者來說,他隻需要知道類釋出出的公有方法,而不需要知道實作細節。這樣,就可以根據類的共有方法編寫相應的測試代碼,隻要滿足這些測試代碼,類的設計就是成功的。對于架構來說,類的可測試性是基礎,但是光保證這一條還不夠。

2、 最小職責原則

一個類(接口)要實作多少功能一直是一個不斷争論的問題。但是一個類實作的功能應該盡可能的緊湊,一個類中隻處理緊密相關的一些功能,一個方法更應該隻做一件事情。這樣的話,類的測試代碼相應也會比較集中,保證了類的可測試性。回憶在分層模式中我們讨論的那個例子,實作類為不同的使用者提供了不同的接口,這也是最小原則的一個展現。

3、 最小接口原則

對于釋出給使用者使用的方法,需要慎之再慎。一般來說,釋出的方法應該盡可能的少。由于公布的方法可能被客戶頻繁的使用,如果設計上存在問題,或是需要對設計進行改進,都會對現有的方法造成影響。是以需要将這些影響減到最小。另一方面,一些比較輕型的共有方法應該組合為單個的方法。這樣可以降低使用者和系統的耦合程度,具體的做法可以通過外觀模式,也可以使用業務委托模式。關于這方面的讨論,可以參考分層模式。較少的接口可以減輕了測試的工作量,讓測試工作更加集中。

4、 最小耦合原則

最小耦合原則說的是你設計的類和其它類的互動應該盡可能的少。如果發現一個類和大量的類存在耦合關系,可以引入新的類來削弱這種耦合度。在設計模式中,中介模式和外觀模式都是此類的應用。對于測試,尤其是單元測試來說,最理想的情況是測試的類是一個單純的類,和其它的類沒有任何的關系。但是現實中這種類是極少的,是以我們能夠做的是盡可能的降低測試類和其它的類的耦合度。這樣,測試代碼相對比較簡單,類在修改的時候,對測試代碼的影響也比較小。

5、 分層原則

分層原則是封裝原則的提升。一個系統,往往有各種各樣的職責,例如有負責和資料庫打交道的代碼,也有和使用者打交道的代碼。把這些代碼根據功能劃分為不同的層次,就可以對軟體架構的不同部分實作大的封裝。而要将類的可測試性的保證發展為對架構的可測試性的保證。就需要對系統使用分層原則,并在層的級别上編寫測試代碼。關于分層的詳細讨論,請參見分層模式。

如果你設計的架構無法滿足上述的原則,那麼可以通過重構來對架構加以改進。關于重構方面的話題,可以參考Martin Fowler的重構一書和Joshua Kerievsky的重構到模式一書。

如果我們深入追究的話,到底一個可驗證的架構有什麼樣的意義呢?這就是下一節中提到的測試驅動和自動化測試的概念。

測試驅動

測試驅動的概念可能大家并不陌生。在RUP中的同樣概念是測試優先設計(test-first design),而在XP中則表現為測試優先程式設計(test-first programming)。其實我們在日常的工作中已經不知不覺的在進行測試驅動的部分工作了,但是将測試驅動提高如此的高度則要歸功于靈活方法。測試驅動的基本思想是在對設計(或編碼)之前先考慮好(或寫好)測試代碼,這樣,測試工作就不僅僅是測試,而成為設計(或代碼)的規範了。Martin Fowler則稱之為"specification by example"

在靈活測試領域。一種做法是将需求完全表述為測試代碼的形式。這樣,軟體設計師的需求工作就不再是如何編寫需求來捕獲使用者的需要,而是如何編寫測試來捕獲使用者的需要了。這樣做有一個很明顯的好處。軟體設計中的最緻命的代碼是在測試工作中發現代碼不能夠滿足需求,發生這種情況有很多的原因,但是其結果是非常可怕的,它将導緻大量的返工。而将需求整理為測試代碼的形式,最後的代碼隻要能夠經過測試,就一定能夠滿足需求。當然,這種肯定是有前提的,就是測試代碼要能夠完整、精确的描述需求。做到這一點可不容易。我們可以想象一下,在對使用者進行需求分析的時候,基本上是沒有什麼代碼的,甚至連設計圖都沒有。這時候,要寫出測試代碼,這是很難做到的。這要求設計師在編寫測試代碼的時候,系統的整體架構已經成竹在胸。是以這項技術雖然擁有美好的前景,但是目前還遠遠沒有成熟。

雖然我們沒有辦法完全使用以上的技術,但是借用其中的思想是完全有可能的。

首先,測試代碼取代需求的思想之是以好,是因為測試代碼是沒有歧義的,能夠非常精确的描述需求(因為代碼級别是最細的級别),并緊密結合架構。是以,從需求分析階段,我們就應該盡可能的保持需求文檔的可測試性。其中一個可能的方式是使用CRC技術。CRC技術能夠幫助設計人員分析需求中存在的關鍵類,并找出類的職責和類之間的關系。在RUP中也有類似的技術。業務實體代表了領域中的一些實體類,定義業務實體的職責和關系,也能夠有助于提高設計的可測試性。無論是哪一種方法,其思路都是運用分析技術,找出業務領域中的關鍵因素,并加以細化。

其次,測試驅動認為,測試已經不僅僅是測試了,更重要的是,測試已經成為一種契約。用于指導設計和測試。在這方面,Bertrand Meyer很早就提出了Design by Contract的概念。從軟體設計的最小的單元來看,這種契約實際上是定義了類的制造者和類的消費者之間的接口。

最後,軟體開發團隊中的所有相關人員如果都能夠清楚架構測試代碼,那麼對于架構的設計、實作、改進來說都是有幫助的。這裡有一個關于測試人員的職責的問題。一般來說,我們認為測試人員的主要職責是找出錯誤,問題在于,測試人員大量的時間都花費在了找出一些開發人員不應該犯的錯誤上面。對于現代化的軟體來說,測試無疑是非常重要的一塊,但是如果測試人員的日常工作被大量原本可以避免的錯誤所充斥的話,那麼軟體的品質和成本兩個方面則會有所欠缺。一個優秀的測試人員,應該把精力集中在軟體的可用性上,包括是否滿足需求,是否符合規範、設計是否有缺陷、性能是不是足夠好。除了發現缺陷(注意,我們這裡用的是缺陷,而不是錯誤),測試人員還應該找出缺陷的原因,并給出改正意見。

是以,比較好的做法是要求開發人員對軟體進行代碼級别的測試。是以,給出架構的測試代碼,并要求實作代碼通過測試是提高軟體品質的有效手段。在了解了測試驅動的思路之後,我們來回答上一節結束時候的問題。可驗證架構的最大的好處是通過自動化測試,能夠建立一個不斷改進的架構。在重構模式中,我們了解了重構對架構的意義,而保證架構的可測試性,并為其建立起測試網(下一節中讨論),則是架構能夠得以順利重構的基本保證。我們知道,重構的基本含義是在不影響代碼或架構外部行為的前提條件下對内部結構進行調整。但是,一旦對代碼進行了調整,要想保證其外部行為的不變性就很難了。是以,利用測試驅動的思路實作自動化測試,自動化測試是架構外部行為的等價物,不論架構如何演化,隻要測試能夠通過,說明架構的外部行為就沒有發生變化。

針對接口的測試

和前文一樣,這裡接口的概念仍然是廣義上的接口。我們希望架構在重構的時候能夠保持外部行為的穩定。但要做到這一點可不容易。釋出的接口要保證穩定,設計師需要有豐富的設計經驗和領域經驗。前文提到的最小接口原則,其中的一個含義就是如此,釋出的接口越多,今後帶來的麻煩就越多。是以,我們在設計架構,設計類的時候,應該從設計它們的接口入手,而不是一上手就思考具體的實作。這是面向對象思想和面向過程思想的一大差别。

這裡,我們需要回顧在穩定化這一模式中提到的從變化中尋找不變因素的方法。穩定化模式中介紹的方法同樣适用于本模式。隻有接口穩定了,測試腳本才能夠穩定,測試自動化才可以順利進行。将變化的因素封裝起來,是保持測試腳本穩定的主要思路。變化的因素和需要封裝的程度根據環境的不同而不同。對一個項目來說,資料庫一般是固定的,那麼資料通路的代碼隻要能夠集中在固定的位置就已經能夠滿足變化的需要了。但是對于一個産品來說,需要将資料通路封裝為資料通路層(或是OR映射層),針對不同的資料庫設計能夠動态替換的Connection。

測試網

本章的最後一個概念是測試網的概念。如果嚴格的按照測試優先的思路進行軟體開發的話。軟體完成的同時還會産生一張由大量的測試腳本組成的測試網。為什麼說是測試網呢?測試腳本将軟體包裹起來,軟體任何一個地方的異動,測試網都會立刻反映出來。這就像是蜘蛛網一樣,能夠對需求、設計的變更進行快速、有效的管理。

測試網的腳本主要是由單元測試構成的。是以開發人員的工作除了編寫程式之外,還需要編織和修補這張網。編織的含義是在編寫代碼之前先編寫測試代碼,修補的含義是在由于軟體變更而導緻接口變更的時候,需要同步對測試腳本進行修改。額外的工作看起來似乎是加大了開發人員的工作量。但在我們的日常實踐中,我們發現事實正好相反,一開始開發人員雖然會因為建構測試網而導緻開發速度下降,但是到了開發過程的中期,測試網為軟體變動節約的成本很快就能夠抵消初始的投入。而且,随着對測試優先方法的熟悉和認同,建構測試網的成本将會不斷的下降,而起優勢将會越來越明顯:

  • 能夠很容易的檢測出出錯的代碼,為開發人員掃除了後顧之憂,使其能夠不斷的開發新功能,此外,它還是代碼日建立的基礎。
  • 為測試人員節省大量的時間,使得測試人員能夠将精力集中在更有效益的地方。

此外,構成測試網還有一個額外的成本,如果開發團隊不熟悉面向對象語言,那麼由于接口不穩定導緻的測試網的變動會增大其建構成本。

總結

從以上的讨論可以看出,架構和代碼是分不開的,架構脫離了代碼就不能夠稱得上是一個好的架構。這是架構的目标所決定的,架構的最終目标就是成為可執行的代碼,而架構則為代碼提供了結構性的指導。是以,用代碼來驗證架構是一種有效的做法。而要實作這個做法并不是一件容易的事情,我們需要考慮代碼級别的架構相關知識(我們讨論的知識雖然局限在面向對象語言,但是在其它的語言中同樣可以找到類似的思想),并利用它們為架構設計服務。

15.進一步閱讀

靈活架構設計一文到目前已經全部結束,由于架構設計是一個很大的話題,要在一篇文章中完全把架構設計講清楚是很難的。是以本文的最後一個章節中提供了一組書籍和文章,這些資料都和架構設計有關,本文的寫作過程也從中獲益良多,故而推薦給有興趣的讀者。

Refactoring To Patterns(Joshua Kerievsky)勿庸置疑,模式是軟體設計的一種有效的工具。但是在将模式和現實中的軟體設計關聯起來時,很多人往往迷惑于模式到底是如何應用的。結果是表現出兩種極端:一是用不好模式,二是過度的濫用模式。模式是他人設計經驗的總結,但是它在提供優秀的設計思路的同時也會增加一定的複雜性。是以,不了解模式應用的上下文環境而錯誤的使用模式是非常危險的。不但達不到原先的效果,而且會導緻設計難以了解和設計團隊溝通的困難。文章一開始,作者就批評了濫用模式的做法。那麼,到底要怎樣才算是正确的使用模式呢?作者借鑒了Martin Fowler的重構方法。通過實際的例子,讨論如何把一個普通的、不夠靈活、不具備擴充性的設計重構為一個優美的設計模式。是以,本文的核心在于,如何識别哪些設計需要重構?如何判斷重構的時機?如何評價重構前後的優缺點?以及,如何進行重構?本書目前正在寫作中,從http://industriallogic.com可以找到其草稿。在透明的網站和umlchina上,也可以找到部分的譯稿。在閱讀架構重構模式後,你可以再翻閱此文,這樣你就可以了解到該模式在代碼級别上的實作。

Effective Java(Joshua Bloch)此書的定位于程式設計習慣(Idiom)和良好的OO思維上。任何的設計都無法脫離最終的代碼。是以,擅長于架構設計的設計師一定也擁有渾厚的編碼功力。優秀的軟體過程是由大量優秀的實踐拚接而成,架構設計也是一樣的,它是由大量的代碼級的實踐累積起來的。此外,本書的很多讨論都是關于如何進行一個優秀的OO設計的。在本文中,我們很多關于具體設計的讨論都是基于OO設計的,在穩定化模式中我們也讨論了OO設計優秀之處。是以,在了解架構設計的基本思路後,閱讀此書,你可以進一步的了解和強化OO架構的設計思路。順便一提,本書的中文版即将面世。

Writing Effective Use Case(Alistair Cockburn)文如其名,本書主要介紹了如何編寫用例的知識。在架構設計中,我們不斷的強調需求的重要性,可以說,沒有需求,就沒有架構。那麼,需求應該如何組織呢?用例就是一種不錯的需求組織方式,注意,用例并不能夠完全代替需求,類似于業務流程、非功能需求之類的需求都不是用例所擅長的。本書的精華就在于它完整的介紹了叙述型用例的各個方面,從用例的範圍、角色、層次等方面描述了用例的構成,并讨論了用例的很多相關知識。更為寶貴的是,本書中包含了大量的執行個體。相較一些介紹用例圖的書而言,本書的定位更加的實踐化。一個優秀的需求的價值在于,它能夠很容易的轉換為軟體開發過程中其它實踐所需要的工件。如果我們仔細的體悟本書的話,我們會發現書中的很多思路都是基于此的。本書在市面上可以找到中文版和英文版兩種版本。

Thinking in Patterns(Bruce Eckel)Bruce Eckel 的另外兩本書《Thinking in C++》和《Thinking in Java》可以說是非常的出名。後者更是有三個版本,前兩個版本都有中文譯本,候捷老師更是親自翻譯了第二個版本,第三個版本目前正在寫作中,從Bruce Eckel的網站(http://www.mindview.net)上可以下載下傳到。《Thinking in Patterns》目前也仍然處于寫作中,但已經略具規模了。Bruce Eckel從不同的應用角度來讨論模式之間的不同以及模式的内涵。例如,對工廠模式的讨論是從封裝對象建立的角度開始讨論的,對擴充卡模式的讨論則是從改變接口的角度開始讨論的。模式的關鍵在于應用,閱讀本書,你能夠體會到這一點。

Java 與模式(閻宏)如果說上述的一些好文都出自國外專家之手,那麼這本書就是絕對的中文原創。本書的重點是讨論隐藏在模式背後的面向對象規律,并一一對各種設計模式進行分析和執行個體研讨。使用很多有趣的例子和運用哲學思維來解釋模式是本書的兩大特色。在閱讀該書的時候,請注意區分技術細節和架構代碼。設計模式的好處就在于能夠根據上下文環境,設計出一個具有靈活性、擴充性、低耦合度的架構來。這和架構設計的思路是一樣的,不要在軟體開發過程的早期考慮太多的細節。

Patterns of Enterprise Application Architecture(Martin Fowler)這是一本絕對的讨論架構設計模式的書了,但這裡的架構是特指企業資訊系統的架構,這和本文讨論的問題域是一樣的。根據三層結構的理論,本書的模式大緻可以分為5類:表示層模式、邏輯層模式、資料層模式、分布式模式、以及一些基礎的模式。書的早期版本采用了這種分類法,在出版之後,模式的分類進一步細化。例如資料層模式就被進一步的區分為資料源架構模式、對象-關系行為模式、對象-關系結構模式、對象-關系中繼資料映射模式等。本書的内容覆寫面很廣,Martin Fowler在素材的組織上擁有非常優異的才能,當年的《重構》一書就是這方面的例證。閱讀本書,你會對架構設計的技術層面有着很深的了解,但是,應該注意,書中的一些模式雖然看起來簡單,但是如果要真正實作,卻需要花費大量的精力,是以,聽從《Refactoring To Patterns》一書和本文重構模式的建議吧,隻有在需要的時候才把設計重構為模式。

Dealing with Roles(Martin Fowler)這隻是一篇小短文,讨論的重點是關于角色處理的知識,但作者從面向對象的基礎知識出發,讨論了如何根據需求的不同,來進行不同的設計,并用實際的例子,示範了設計是如何變化的。這種思想和本文提倡的思想是非常的相似的,架構設計不能夠獨立于需求而存在。建議不論是對面向對象設計有興趣還是對軟體工程有興趣的人都可以閱讀此文。在Martinfowler的網站上(http://www.martinfowler.com)可以找到本文,次外,網站上還有其它一些優秀作品,《Dealing with Properties》就是其中的一篇。我曾經為《Dealing with Roles》一問撰寫了一篇讀書筆記,釋出在點空間上(http://www.dotspace.twmail.net/patternscolumn/analysis%20patterns/RoseModelingNotes_S.htm),如果有興趣,也可以指導一二。

《Framework Process Patterns》(James Carey,Brent Carlson)本書的作者是IBM公司的成員,他們有着面向對象作業系統和企業應用架構的設計經驗,而後者,這是著名的IBM SanFrancisco架構。他們把架構設計中學習到的知識整理為過程模式的形式,書中并沒有太多的理論,但處處都展現出了作者的豐富經驗。在閱讀本書的時候,要時刻牢記其推介的架構設計的特點,再結合自己工作的具體情況,來了解和應用這些模式。不要盲目的把書中介紹的模式應用于自身,這是我的忠告。本書的中文版由我和一位朋友翻譯,将不日面世。

IBM Sanfrancisco 架構,這并不是一本書,而是一個現實中的産品。IBM根據市場經驗,設計了一個企業應用架構,定位于為企業應用開發者提供通用的元件。從這個産品中,你可以充分的了解到模式是如何應用在一個成熟的産品中的。要了解這個産品的設計思路,關鍵是要先了解它的層次劃分。SanFrancisco架構總共分為三個層次:Foundation Layer、Common Business Objects Layer、Core Business Process Layer。Foundation Layer定義了基礎的類以及類的使用政策,例如工廠類來負責所有對象的建立;Common Business Objects Layer定義了企業中的一些通用對象,例如公司、帳戶、客戶等,而Core Business Process Layer定義了企業應用所需要的關鍵業務流程,包括會計架構、應收應付、訂單處理、庫存管理幾個方面。這三個層次可以進行獨立的重用,越高的層次的重用價值越大。在了解這樣一個産品的時候,我們要有這樣的思路,對于一個大型的産品來說,一緻性有時候是重于其它的價值的,例如,在對象建立方面,産品統一使用了工廠模式,而在屬性處理上,統一使用了動态屬性的設計方式。雖然有些地方并不需要用到這兩種設計模式,但是為了保持設計的一緻性,還是必須使用該模式。這一點對于普通的項目開發或是小産品開發來說可能未必适用,但是對于大型的項目或産品來說就顯得非常的重要了。

Applying Patterns(Frank Buschmann)這是一篇用過程模式語言來描述如何在實際中應用設計模式的文章。文章短小精悍,把設計模式的應用總結為幾種模式,沒有提供具體的執行個體是個遺憾。對正在學習設計模式的人而言,花一些時間了解别人是如何應用設計模式是很有必要的。在點空間上可以找到原文連結和繁體版譯文。本文的架構願景模式就參考了這篇文章中的内容。

重構(Martin Fowler)其實本書已經不用再介紹了,他的價值就在于他能夠把程式員日常的很多優秀做法提升到理論的階段,并為更多的程式員提供指導。這也是我在上文誇獎Martin Fowler具有優異的組織才能的一大原因。遺憾的是,本書一直沒有中文譯本,不過這個遺憾即将結束,候捷和透明正在合譯此書,相信不久之後就可以一飽眼福。http://www.refactoring.com是Martin Fowler建立的重構的讨論站點,上面也會很多的相關内容。而關于重構的另一方面的消息是,現在已經有越來越多的模組化工具将重構作為重要的特性之一,這無疑能夠為程式員節省大量的精力。

http://www.agiledata.org(Scott W. Ambler)這是Scott W. Ambler 最新維護的一個網站,也代表了Agile方法發展的一個方向――如何以靈活的姿态進行資料庫項目的開發。在讀過站點的内容之後,你會了解到如何做好資料庫項目的開發。目前,本站點還在Scott W. Ambler的維護下不斷的更新。資料庫設計一直不是面向對象陣營強調的一個重點,基本的觀點認為,關鍵是類的設計足夠規範,資料庫并不是主要問題。但是在實際的項目中,資料庫,特别是關系型資料庫往往是無法忽略的部分,包括資料庫模式的設計、性能優化、資料庫連接配接管理、資料操縱語言。除此之外,遺留資料庫、并發問題、安全,關系資料到對象的映射,業務邏輯處理,這些都是需要在架構設計的時候就加以考慮的問題。在本文中并沒有專門的章節對資料庫相關的知識進行讨論,因為資料庫的讨論最好是結合具體的資料庫進行。如果大家在架構設計中存在資料庫方面的問題,可以參考這個網站。

Designing for Scalability with Microsoft Windows DNA(Sten Sundblad)目前關于讨論微軟體系平台設計的優秀書籍不多,而本書正是其中之一。本書介紹了DNA體系下設計一個分層架構所要注意的問題。其核心思想是避免純理論的面向對象設計。例如,書中在介紹領域對象的時候,建議将隻讀對象和可寫對象分開處理,這樣,隻讀對象就不需要COM+的支援,能夠提高效率,但這是不符合面向對象的設計的封裝思路的。另外,為了能夠使用對象緩沖池技術,本書提議在設計業務對象的時候不要包括狀态資料,這和類包括資料和行為的基本思路也是相斥的。從這本書中,我們可以了解到現實系統的設計和經典面向對象思想之間的辨正關系。

設計資料層元件并在層間傳遞資料(Angela Crocker、Andy Olsen 和 Edward Jezierski)這是另一篇讨論windows體系平台的文章。微軟的産品适合于小型的開發,一方面和具體的技術有關,另一方面也和體系結構的設計有關。windows體系結構的特點是快速開發,是以在一些小型的項目中,使用微軟産品的開發速度較快,但是随着項目規模的增大,快速開發伴随着的結構性欠佳的問題就逐漸顯露出來了。是以,文章的主要内容就是如何優化結構。其主要的思路是對系統進行分層,并實作層間資料傳遞的政策。這兩點不論是在哪一類型的體系中都是關鍵性的問題,在分層模式中,我們也曾經對這兩個問題做了大篇幅的讨論。和Java體系相比,Window體系有其特殊的一面,也能夠起到他山之石的效果。

EJB Design Patterns(Floyd Marinescu)本書分為兩個部分,第一個部分重點讨論了如何在一個分層體系中應用模式語言,并分别針對架構設計、資料傳輸(即上一段中讨論的層間傳送資料)、事務和持久性、服務端和用戶端互動、主鍵生成政策等主題讨論了可能的設計模式。第二部分讨論了EJB設計過程中的實踐活動和過程。雖然本文的所有内容都是針對EJB設計的,但是其思路同樣可以借鑒于其它體系。本書的電子書在Middleware網站上可以下載下傳到。