天天看點

《Microsoft.NET企業級應用架構設計(第2版)》——2.3 走出混亂

本節書摘來自異步社群《microsoft.net企業級應用架構設計(第2版)》一書中的第2章,第2.3節,作者: 【意】dino esposito(埃斯波西托) , andrea saltarello(索爾塔雷羅)著,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

即使做了最好準備,也不管團隊的努力如何,系統的設計都可能在某個時刻陷入困境。bbm的形成通常是一個緩慢的過程,會在一段相對較長的時間裡使設計惡化。在這個過程裡,你的類到處都有修補和變通方案,最終大片代碼開始變得難以維護和進化。

這個時候問題就很嚴重了。

管理者面臨與魔鬼交易的抉擇,要麼采用更多修補和變通方案,要麼根據稽核的需求和新的架構選擇做一次徹底的重新設計。

重新設計一個完整系統與完全從頭開始開發之間的差別是什麼?就采取的措施而言,差別是極小的,如果存在任何差別的話。但心理層面的選擇是不同的。如果要求重新設計,管理層傳遞的資訊是團隊正在迎頭趕上并且快速修複東西。如果要求重寫,管理層則是在承認失敗。在軟體項目裡很少願意接受失敗。

當管理層要求對現有的代碼庫進行重大整改時,就證明了團隊制造了一個軟體災難。現有的代碼庫就變成了某種令人感到難受的遺留代碼了。

2.3.1 有一種奇怪的東西叫作“遺留代碼”

在軟體裡,你通常會繼承你必須維護、支援或者使之保持現狀并與新的東西整合的現有代碼。這種代碼通常被稱為遺留代碼。但是,架構師和開發者所面臨的主要挑戰不是與現有的遺留代碼抗争,而是不要建立更多遺留代碼。

遺留代碼就是字面意思表達的那種東西。它是代碼,它是遺留的。根據牛津詞典,遺留這個詞是指前任留下或者轉交的東西。此外,這個字典還為這個詞添加了一個軟體特定的定義,意思是某些東西廢棄了但因廣泛使用而很難替換掉。

我們對遺留代碼的一般定義是任何你擁有但不想擁有的代碼。遺留代碼被繼承下來是因為它可工作。設計得好和寫得好的可工作代碼與設計得差和寫得差的可工作代碼之間沒有根本差別。當你不得不着手處理遺留代碼,并通過某種方式維護和進化它時,問題就來了。

遺留代碼在某種程度上與軟體災難有關。但是,項目裡有遺留代碼本身不是災難。當項目裡的遺留代碼是你造成的時候,事情就開始惡化了。如何把遺留代碼(不管是你的還是從别人那裡繼承的)變成更可管理的代碼,不會妨礙整個項目的進化和按照預期展開呢?

2.3.2 在3招之内将殺(checkmate)

總而言之,從軟體災難恢複類似于從創傷恢複。假設有一次放假,你決定出去長跑,後來遭受一些嚴重損傷,比如嚴重的跟腱拉傷。

對于非常活躍的人來說,嗯,這就是一場災難。

你去看醫生,醫生進行了一些簡單但有效的療程。第一,醫生要求你停止任何實體活動,包括在某些情況下行走。第二,醫生在受傷的踝關節附近甚至在整條腿上綁上繃帶。第三,醫生建議你在感覺好一點的時候嘗試走動,如果感覺不舒服就停止。這種方法行得通,很多人都能成功完成這個療程。

相同的療程也可以用到軟體創傷上,而且通常都行得通。更确切地說,這個政策理論上是有效的,但實際效果取決于創傷的嚴重程度。

1.停止新的開發工作

在實施把寫的爛得代碼變成更可管理的東西的任何政策之前都必須先停止系統的開發工作。事實上,添加新的代碼隻會讓設計得爛的系統變得更加糟糕。但是,停止開發工作并不意味着你要停止這個系統的工作。它隻意味着,直到你把目前系統重組成更可維護的代碼庫,可以在現有特性不必做出讓步的情況下接受新的特性,才開始添加新的特性。

重新設計一個正在進化的系統就像抓住一個正在亂跑的小雞。你要有一個非常好的狀态才能做到。但是,失敗過一次的團隊真能在這個時候進入良好的狀态嗎?

2.隔離痛處

就像醫生在疼痛的踝關節附近綁上繃帶一樣,架構師應該用層包圍寫的爛的代碼塊。但是,這裡的代碼塊(block of code)具體是指什麼?指出“代碼塊”不是什麼會不會更容易?

代碼塊不是一個類,而是某種跨越多個類的東西,如果不考慮多個子產品的話。代碼塊實際上辨別了系統的行為(正在執行的函數),包含了牽涉在内的所有軟體元件。關鍵是辨別出宏函數,并為它們每個定義一個不變的接口。你定義一個層實作這個接口并且成為這個行為的外觀。最後,你修改代碼,使每個需要觸發那個行為的地方都通過這個外觀(facede)來實施。

舉個例子,看一下圖2-1。這幅圖表示了一個混亂的代碼庫的一個小的部分包含了兩個關鍵元件,c1和c2,它們與其他元件有太多依賴。

《Microsoft.NET企業級應用架構設計(第2版)》——2.3 走出混亂

這裡的目的是把圖2-1的布局轉成某種帶有更多獨立區塊的東西。你需要了解的是,你不一定在一開始就得到正确的設計。隔離痛點隻是第一步,而且你不應該太在意你引入的層的大小。回到醫生那個類比,有時候,醫生會在整條腿上綁上繃帶,即使受傷的地方隻是踝關節附近。但是,幾天休息之後,醫生可能減少繃帶,隻覆寫踝關節附近的地方。

類似地,隔離軟體痛處是一項疊代性的工作。圖2-2給出了隔離圖2-1的痛處的一種可能的方式。

看到圖2-2時,你可能會認為_c_1和_c_2重複了;不過,這隻是中間步驟,但為了得到牽涉同一個調用方的兩個嚴格分離的子系統,這是必要的一步。分離的子系統應該像黑盒一樣。

值得注意的是,代碼塊甚至可能跨越邏輯層,有時候也會跨越實體層。就像醫生的繃帶,減少隔離痛處所覆寫的區域是這項工作的最終目的。

《Microsoft.NET企業級應用架構設計(第2版)》——2.3 走出混亂

術語:

本書将會大量提及領域驅動設計(ddd),尤其從第5章“發現領域架構”開始。我們認為這裡提到的隔離區塊概念與ddd的綁定上下文概念有着非常重要的關系。在ddd項目裡,綁定上下文也可以是一個遺留代碼黑盒。在ddd裡,綁定上下文有一個與之相關的獨特模型,可以由多個子產品組成。最終,一些子產品可能會共享相同的模型。

3.測試覆寫

一旦你把系統重構成一堆嚴格分離的區塊,你的系統應該還能工作—隻是從設計的角度來說更貼切。但是,你不能就此停下腳步。是以,強烈建議你在這個時候引入測試,它們可以告訴你系統在進一步重構之後是否仍然工作。

在這裡,測試通常是指內建測試,顧名思義,測試可能會跨越多個子產品、邏輯層和實體層。這種測試很難設定,比如說,需要使用專門的仿真資料填充的資料庫,需要連接配接服務,這種測試還要長時間運作。但是,它們是絕對要有的,也是絕對要運作的。在前面提到defeathers的論文裡,他使用了“測試覆寫”這個術語來表示為後續更改定義行為不變性的測試。

注意:

測試在任何重構工作裡都扮演着關鍵角色。任何重構之後,你都肯定需要可以檢測是否出現任何回歸的測試來結束這個過程。但是,在某些情況下,你可能會發現在你開始隔離痛塊之前就準備好測試很有幫助。

邏輯層(layer)這個術語通常是指邏輯邊界。相反,實體層(tier)是指實體邊界。更具體地說,當我們提到邏輯層時,我們指的是在相同的程序空間裡邏輯分離的代碼塊(即類或者程式集)。實體層意味着實體距離以及不同的程序空間。實體層通常也是一個部署目标。一些代碼放到邏輯層還是實體層隻是選擇和設計的問題。邊界才是真正的問題。一旦你有了清晰的邊界,你就能決定哪些屬于邏輯層,哪些又屬于實體層。比如說,在asp.net項目裡,你可以讓一些應用程式的邏輯部署在與核心asp.net和頁面一樣的應用程式池的程序裡,也可以部署到在另一台iis機器上寄宿的不同的實體層裡。

4.持續重構

在測試覆寫的首個疊代之後,你應該有一個穩定的系統了,也在某種程度上控制了它的行為。你甚至可能向測試輸入一些資料,然後檢測出來的行為。接着,你進入重構循環,在這個過程中,你嘗試簡化你所建立的黑盒結構。在這種情況下,你從黑盒裡取出一些代碼,然後通過新的接口重用它。來看一下圖2-3。

《Microsoft.NET企業級應用架構設計(第2版)》——2.3 走出混亂

如你所見,c1和c2現在已經從子系統移出來,并且封裝到一個新的可測試的黑盒裡。重複這個過程,你可以逐漸減少黑盒的大小。一個好的影響是,現存的內建測試現在可以重寫成更加接近單元測試的形式了。

2.3.3 決定是否添加人手

frederick p. brooks在他的《the mythical man month》(addison-wesley,1995)裡有一句名言:向一個已經延遲的項目添加人手隻會使之更加延遲。确實是這樣,這樣做不可能對日程安排有太大影響(這句是我們的)。然而,當項目延遲時,第一個湧進腦海的就是增加勞動力。但是,項目的活動有順序的限制(比如說,調試要在開發之後),添加勞動力根本沒有任何好處。根據brooks的說法,孕育一個孩子需要9個月,9個女人不可能在一個月内生出一個孩子。

是以問題就變成:當項目延遲時你應該做什麼?你永遠都不應該考慮添加人手嗎?這取決于項目延遲原因的實際分析結果。

1.需要更多時間

項目延遲的明顯因素是每個特性需要的時間比預計的多。如果任務本身是順序執行的,團隊有更多的人意味着需要更多的管理工作,可能導緻配置設定的資源沒有達到最佳效用。

這個現實把焦點轉移到估算上。軟體人員的工作量很難準确估算。軟體人員通常會認為事情最終會好起來,一切隻需再多幾個小時的工作就可以修複。這種态度也使監督進度變得困難。本章最開始的引言已經表明一切—一個項目每次延遲一天。如果進度可以及時跟蹤,進度落後不需要很長時間就可以得到修複。在最壞的情況下,這個額外的時間可以分攤到一段更長的時間裡。

但即使技術工作量的估算是正确的,另一個方面也常常被忽略。任何項目都有直接成本和間接成本。直接成本包括薪水和差旅費。間接成本包括裝置和行政事務。此外,還有一些成本無法确知的東西:會議,修訂,以及所有沒和每個人溝通或者沒被完全了解的小任務。估算疏漏是很常見的,而疏漏隻能通過經驗彌補—你的直接經驗或者這方面專家的直接經驗。很明顯,你應該總是盡力清楚地寫下每個需要完成的小任務,并且把它們的時間考慮進來。

一個務實的方案是在項目完成時總是比較一下實際成本和估算。如果發現有出入,把它換算成百分比,不管是低了還是高了,下次你可以使用這個因子去乘以你的估算。

一般而言,根據實際的工作模式,軟體項目有兩個主要的變化模式:固定價格或者時間/物資。對于前面那種情況,如果你意識到需要更多時間,作為一名項目經理,你可能會嘗試尋找一種方式來重新調整計劃安排。你嘗試降低品質—縮減開發時間和測試,減少文檔和任何對通過本次疊代的最終測試并非完全必要的活動。你也可能嘗試重新商議,仔細回顧需求,看看是否有任何已經達成協定的東西可以重新界定為需求更改。如果你使用時間/物資模式,你可以設法計算過去疊代裡估算時間和實際時間之間的內插補點,然後用它來修正每個新的疊代的估算。

2.需要更多專業技能

項目延遲的另一個原因可能是某些人并不勝任這項任務導緻實作某些特性需要更長時間。如果你需要更多專業技能,不要害怕把你能找到的最好人才帶進來。但是,當你需要有才能的人時,應該清楚為什麼你想要把他們帶進來。你可以找專家給你的人做教育訓練,或者找他們解決問題。沒有哪個做法比另一個更好。

教育訓練帶來附加價值,因為教育訓練的結果留在公司裡面,期待增值作用。同時,教育訓練(即使提供專門定制的課件)針對的主題通常在一個比較通用的層次,需要一些額外的時間才能在目前項目裡采用。另一方面,尋求咨詢理論上更有效,但你要允許專家完全接觸代碼、人員和文檔。代碼庫越是錯綜複雜,所需的時間可能越長,結果的可靠性也可能越低。

專家不會變魔法,魔法在軟體裡并不存在。如果一個專家會變魔法,它可能隻是戲法。生活中也是如此。

繼續閱讀