演化架構(evolutionary architecture)和緊急設計(emergent design)都是將 重要的決策推遲到最後責任時刻(Last Responsible Moment)的敏捷技術。在本 系列的第一期文章中,系列作者 Neal Ford 將定義架構和設計,然後指明了一些 關於整個系列的基本概念。
軟件架構和設計一直都沒有一個明確的定義,因為軟件開發作為一門學科,尚 未完全理解其中的復雜度和內涵。但是要發表關於這些主題的論述,您必須從某 個位置開始。本系列涉及演化架構和緊急設計,因此將從一些定義、注意事項和 其他基礎設置入手。
定義架構
軟件中的架構是開發人員談論最多但是最難理解的概念之一。在會議中,關於 架構的對話及相關討論頻繁出現,但是我們仍然只具有含糊的定義。在討論架構 時,我們實質上是在談論幾個不同但是相關的方面,這些方面通常可以劃分為應 用程序架構 和企業架構 這兩個主要類別中。
應用程序架構
應用程序架構描述組成應用程序的主要部分。例如,在 Java 世界裡,應用程 序架構都描述兩個內容:用於構建特定應用程序的框架組合 — 我稱其為框架級 架構 — 以及更多傳統的邏輯關注點分離,我一直稱這些內容為應用程序架構。 將框架架構作為一個獨立部分,因為大多數面向對象語言的從業者已經發現單獨 的類不能實現良好的重用(您最後一次從 Internet 中下載一個單獨的類以供某 個項目使用是什麼時候?)。目前面向對象語言中的重用部分都是庫或框架。當 您用提供豐富框架的語言(如 Java 語言)啟動一個新項目時,首要的架構關注 點之一就是應用程序的框架級架構。這種重用設計在 Java 世界中獲得了巨大成 功,以至於我已經開始認為我們應當停止把 Java 編程稱為面向對象的語言,而 應當稱其為面向框架的語言。在許多方面,框架級架構代表特定構建塊所描述的 物理架構。
應用程序架構的另一個有趣的方面描述應用程序的邏輯部分如何整合在一起。 這屬於設計模式和其他結構描述的領域,並且因而趨向於更具抽象性和邏輯性, 而非物理性。例如,您可以說 Web 應用程序遵循模型-視圖-表示器(Model- View-Presenter)模式,而無需詳細說明您使用哪個框架實現邏輯安排。這種邏 輯安排是在開始處理應用程序的新組件時,最有可能增添到工作空間白板 (whiteboard)中的內容之一。
企業架構
企業架構關注如何使企業作為一個整體(通常意味著在大型組織內運行的應用 程序)來使用應用程序。關於企業架構與應用程序架構之間關系的常見比喻是把 企業 比作城市規劃,把應用程序 比作建築結構。城市規劃者必須考慮獲得水、 電、污水和其他服務才能使城市運轉。一棟大樓使用的自來水不能多於提供給它 的配額。企業架構需要為應用程序考慮同樣的事情:您不可以允許一個應用程序 使用所有網絡帶寬,而如果基礎設施服務崩潰,就會出現大量問題。
企業架構在過去幾年裡得到了很多關注,這都是因為面向服務架構(Service -Oriented Architecture,SOA)。SOA 是一個獨立的龐大主題,因此本系列未來 幾期文章將把它處理為特殊案例。它擁有自己的有趣方面,因為它在規定應用程 序構造的特性時,它模糊了企業架構與應用程序架構之間的界限。
前面幾段內容提供了這些重要概念的表面定義,但是它們可用作其他更有趣、 更細致的架構定義的出發點(包括通過其他定義得到的一些定義)。
目前的定義
許多有才識的人都曾試著去定義軟件架構,因此我將從他們的成果中獲取一些 思路。在 Martin Fowler 的經典白皮書 “Who Needs an Architect?”中,他討 論了幾種定義。他引用了 Extreme Programming 郵件列表中的第一個定義:
“制定 IEEE 定義的 RUP 把架構定義為 ‘環境中最高級別的系統概念。軟件 系統的架構(在特定時間點上)是通過接口交互的重要組件的組織或結構,這些 組件由越來越小的組件和接口組成。’”
如上所述,此定義在應用程序架構領域內非常恰當。雖然有些含糊,但是它捕 捉到了架構職責的本質:最高級別的概念。
Fowler 隨後引用了 Ralph Johnson 的話,Ralph Johnson 在此郵件列表的回 復中對前面的定義提出了爭議 :
“更好的定義是:‘在大多數成功的軟件項目中,從事該項目的專家開發人員 對設計系統的設計存在共識。這種共識被稱為 “架構”。這種共識包括如何將系 統分為組件以及組件如何通過接口進行交互。’”
Johnson 提出了一個很好的觀點,強調軟件開發對通信的依賴要多於對技術的 依賴,而該架構實際上代表關於系統的共識,而不是特定於語言、框架和其他短 暫存在的技術。
在前述論文中,Fowler 自己提供了一個我最喜歡的架構定義:
“架構是重要的事物,無論它是什麼。”
這個定義有一點含糊,但同時也是描述性的。關於架構和設計的許多爭論都反 復思考對於重點的誤解。對於業務分析師和企業架構師來說,他們所認為的重要 內容是有差別的。此定義很好地概況了這樣一個概念:您必須在自己的環境中 定 義術語,然後才可以嘗試定義其他內容。
Fowler 的定義還強調了定義像架構一樣微妙的內容的另一個重要方面。“重 要內容” 不僅因個人和團體而異;這些差別實際上可能互相排斥。例如,實際上 所有 SOA 都在靈活性與速度之間實現了一種平衡。現在使用的舊客戶機/服務器 系統的速度幾乎肯定比取代它的基於 Web 的、portlet 式引擎、基於服務的版本 快。除非新應用程序是由很糟的開發人員編寫的,否則提供靈活性的附加層意味 著用戶的響應時間將增加,使響應變得更慢。架構師可能會對用戶說,“順便說 一句,我們正在安裝的新 SOA 內容將為我們提供更多好處,但是您的工作現在將 花費更多時間,抱歉”。可能那就是架構師比開發人員工資高的原因。
然後就剩下我特別喜歡的架構定義:
“以後很難更改的內容。”
此定義最符合演化架構的概念。演化架構中的核心理念之一是盡可能晚地推遲 決定,這將允許您替換在最近的體驗中表現更好的其他方法。許多此樣式的架構 的構建塊出現在整個系列文章中並且推動了本系列的創作。
在停止討論架構之前,如果我不討論 “架構師” 這個職位,就是不負責任。 令人力資源部門苦惱的是這個行業中有這樣一個定義糟糕的職位。許多組織都希 望提拔它們最好的開發人員 — 對於以後很難更改的內容作出重要決定的人員 — 但是除了 “架構師” 之外都沒有一個恰當的行業術語,也沒有通用的職位描述 ,因此每家公司都定義此角色的含義。某些架構師類似於電影黑客帝國第二部結 束時的 Architect(Fowler 將其歸類為 Architectus Reloadus)。這些架構師 最後一次編寫代碼是在十多年以前,而現在他們為您的公司作出重要決定。他們 使用的惟一一個軟件開發工具是 Visio。
另一種架構師角色是 Fowler 稱為 Architectus Oryzus(以我的一個同事 David Rice 命名)的角色。這些架構師與處理最困難部分的其他開發人員合作, 積極地向項目貢獻代碼。他們的職責還包括與其他項目相關人員交流以確保每個 人都具有相同的看法、使用相同的定義並且理解他們需要理解的系統的各部分。 很明顯,這種活躍的角色對於實現演化架構目標至關重要,並因此將在本系列中 反復出現。
定義設計
大多數開發人員都已經有非常好的設計感覺,因此我不會花大量時間來定義它 。它表示如何整合一款軟件的具體細節。它包括常見的設計模式、重構、框架和 開發人員的其他日常關注點。設計大致包括 BDUF(Big Design Up Front)與 Cowboy Hacking 之間的內容,如圖 1 所示:
圖 1. 設計圖
圖 1 中的圖譜左側表示您可以預見在開發軟件時出現的所有成百上千個關注 點,並且嘗試限制您對這些關注點的響應。您將在後續文章中閱讀關於這句話的 更多內容。因為我不花大量時間定義 設計並不表示我不會花大量時間討論它。在 編寫第一行代碼之前,本系列的大部分內容將介紹如何伴隨著開發展開設計(而 不是一成不變)的各個方面。
架構及設計關注點
比較演化 與突發
注意本系列稱為 演化架構和突發設計。演化 與突發 為什麼會有區別?我的 同事向我指出,突發架構 不是一個熱門的概念。如果您接受架構是以後難於更改 的部分這一前提,就很難接受逐步顯現的架構。架構關注在啟動應用程序之前必 須存在的基礎元素。但是,僅僅因為您無法讓架構顯現並不意味著它無法演化。 如果您已經創建了一個靈活的架構並且避免作出無法撤回的決定,則可以允許它 在出現新關注點時演化。
使用手頭現有的架構和設計定義,我希望深入研究一些全局性的關注點。所有 這些主題都與基礎級架構和設計有關,因此預先介紹這些內容使我可以在本系列 後期文章中引用這些內容。首先,我將討論技術債務(technical debt),然後 討論復雜度,最後討論過度的一般性(rampant genericness)。
本金和利息
每位開發人員都開始注意到技術債務 的概念,您可能由此而迫於外部壓力( 例如日程壓力)在設計中有所折衷。技術債務類似於信用卡債務:您目前沒有足 夠的資金,因此您借用未來的資金。同樣地,您的項目沒有足夠的時間來做正確 的事情,因此您使用一種及時的解決方案並希望在未來的一段時間回過頭來進行 改進。糟糕的是,許多經理人似乎都不理解技術債務,因而不願意重新完成舊的 工作。
構建軟件並不是挖壕溝。如果在挖溝時有所折衷,則只能得到一條寬度不均勻 或者深度不同的溝。今天挖一條有缺陷的溝與您明天挖出一條好溝沒什麼關系。 但是您今天構建的軟件是您明天所構建內容的基礎。現在為了權宜而做的折衷將 導致軟件中的熵 不斷增大。在 The Pragmatic Programmer 一書中,Andy Hunt 和 Dave Thomas 談到了軟件中的熵以及為什麼它有這樣一種不利影響。熵是復雜 度的度量標准,而如果您現在由於某個問題的及時解決方案增加了復雜度,則必 須在項目的剩余生命周期內為此付出一些代價。
假設您需要向一個現有的長期項目中添加新功能。這些新功能擁有某些內在的 復雜度。但是,如果您已經擁有技術債務,則必須解決系統中那些折衷部分才能 添加新功能。因此,添加新功能的成本將反映財務狀況。圖 2 顯示了在設計簡約 的系統(例如,擁有很少技術債務或者沒有技術債務的系統)中和在包含大量技 術債務的典型系統中,在添加新功能所需付出的努力方面的差別。
圖 2. 技術債務與利息
您可以把內在復雜度視為本金,而把前幾條作為權宜之計的捷徑所強加的額外 工作視為利息。復雜度本身是一個非常有趣的題目。
本質復雜度與偶發復雜度的比較
我們在軟件中解決的問題具有內在復雜度,我將它稱為本質(essential)復 雜度。由導致技術債務的折衷引發的復雜度是不同的。它包含使軟件變得復雜的 所有外部強加方法,並且這種復雜度是不應有的。我將此稱為次要(accidental )復雜度。我在 The Productive Programmer 一書中定義並深入討論了這些術語 。這些術語一般不是一成不變的:它們存在於圖譜上,就像設計一樣。一些示例 將幫助闡明差別。
我的一個同事為一家具有工會組織的公司做過一個工資系統。該工會為其某些 成員爭取的一項福利是在狩獵季節開始時有一天額外的休假(嘿,他們一定是有 優秀的談判代表)。涉及的員工只在一家工廠工作,但是提供額外的休假是整個 公司的工資系統的合法部分。這項更改給軟件增添了許多復雜度,但是它是本質 復雜度,因為它屬於待解決的業務問題。
圖譜中更遠一點始終出現另一個示例:輸入表單的字段級安全性。許多業務人 員都認為 他們需要對每個字段的安全特性進行精細控制。實際上,一旦實現這樣 的精細控制,他們幾乎總是很討厭它,因為這將給需要定義和維護所有元數據的 用戶帶來負擔。我們的一個項目中的業務人員真的非常需要此功能,因此我們為 他們在其中一個屏幕中實現了部分此功能。在他們粗略意識到使用此功能所需的 工作後,他們決定,由於訪問應用程序的惟一方法是通過一間上了鎖的辦公室, 因此他們可以采用更粗粒度的安全性。這是一個在業務人員認識到他們認為必需 的功能之後做出設計決定的優秀示例。
在圖譜中朝向偶發復雜度的最遠端是純管道實踐,如 Enterprise JavaBeans (EJB)技術的前兩個版本和 BizTalk 等工具。許多項目都需要這些工具所引入 的額外負載,但是它們只是給使用它們的大多數項目增加了復雜度而已。
有三個問題可能會產生偶發復雜度。我已經討論了第一個:由於日程或其他外 部壓力而導致臨時大量削減代碼。第二個是復制,The Pragmatic Programmers 中稱其為對 DRY(不要重復自己,Don't Repeat Yourself)原則的違背。復制是 軟件開發中最能讓人在不知不覺中降低軟件質量的因素,因為在開發人員甚至還 沒有覺察的情況下,它會蔓延到許多位置。一個明顯的例子是復制並粘貼代碼, 但是也存在大量更復雜的示例。例如,幾乎所有使用對象-關系映射工具(例如 Hibernate 或 iBatis)的項目都存在大量復制。您的數據庫模式、XML 映射文件 和後端 POJO 擁有略為不同但是重復的信息。通過創建這些信息的標准來源並生 成其他部分有可能解決此問題。復制對項目有害,因為它不利於做出結構更改或 重構為更好的代碼。如果您知道需要在三處不同的位置更改某個內容,那麼即使 有利於代碼的長期運行,也應該避免這樣做。
偶發復雜度的第三個誘因是不可逆性。您做出的無法逆轉的所有決定都將最終 導致某種程度的偶發復雜度。不可逆性將同時影響架構和設計,盡管它的影響在 架構級別上比較常見並且比較有破壞性。盡量避免作出不可逆轉或者難於逆轉的 決定。我的一些同事信奉在最後責任一刻 做決定。這並不表示您應當把決定推遲 得太久,只要推遲得比較久就足夠了。什麼時候才是決定某些架構關注點的最後 責任時刻?做決定的時間拖得越晚,您為自己留下的可能性就越多。問問您自己 :“我是不是現在就需要做那個決定?” 以及 “我做什麼能讓我推遲那個決定 ?” 如果在決策過程中應用一些技巧,那麼您將會對您可以推遲的內容感到十分 驚訝。
前面所述的框架級架構和應用程序架構之間的區別將與 “最後責任時刻” 原 則聯系起來。應用程序架構一般為邏輯架構。例如,假定您知道需要分離模型-視 圖-表示器(Model-View-Presenter)關注點。通常,通過選擇滿足部分或全部需 求的框架,您可以成功實現這種邏輯架構的物理實現。看看您是否可以推遲該決 定,因為一旦物理實現就位後,它將限制必須考慮的其他類型的決定。盡可能推 遲框架決定將使您獲得受實際情況影響較小的更好選擇。
過度的一般性
架構與設計的最後一個全局關注點,我將之稱為過度的一般性。Java 世界裡 似乎有一個弊病:通過嘗試使解決方案盡可能一般化來過度設計解決方案。這樣 做的動機十分明顯:如果我們構建許多擴展層,我們稍後可以在其上更輕松地構 建更多層。但是,這是一個危險的陷阱。因為一般性將增加熵,所以您將破壞在 項目初期中通過有趣的方式演化設計的能力。增加過多靈活性將使對代碼庫的每 一次更改都變得更加復雜。
當然,您不可以忽略可擴展性。敏捷的移動性在決定如何實現功能時很重要: YAGNI(You Ain't Gonna Need It)。這是避免過度設計簡單功能的信條。只實 現目前需要的功能,在以後您需要更多功能時,可以再進行添加。我看到過某些 Java 項目為了實現一般性和可擴展性,在架構與設計方面使用了大量折衷,最後 導致項目失敗。這是個令人感到諷刺的教訓,因為本來希望盡可能延長項目的生 命周期,結果反而縮短了生命周期。了解如何把握可擴展性與過度設計之間的微 妙界限十分困難,而且它是我將反復說到的主題。
路線圖
本文包含大量文字敘述而沒有源代碼,因此不同於本系列中所有其他後續文章 。討論像架構與設計這樣的復雜主題時,固有的問題之一就是必須具備可以確保 所有人都在同一個情境中的上下文設置。我已經為本系列的其余文章打好了基礎 ,我將在其中深入探究與演化架構和緊急設計相關的具體領域。每篇文章將深入 探究這些概念中的一個或兩個概念的特別說明,其中包含大量詳細信息和源代碼 。下一部分:我將通過測試驅動開發(我已經將其命名為 測試驅動設計)討論緊 急設計。