閱讀226 返回首頁    go 微軟 go windows


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

本節書摘來自異步社區《Microsoft.NET企業級應用架構設計(第2版)》一書中的第2章,第2.3節,作者: 【意】Dino Esposito(埃斯波西托) , Andrea Saltarello(索爾塔雷羅)著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看

2.3 走出混亂

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

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

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

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

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

2.3.1 有一種奇怪的東西叫作“遺留代碼”
在軟件裏,你通常會繼承你必須維護、支持或者使之保持現狀並與新的東西整合的現有代碼。這種代碼通常被稱為遺留代碼。但是,架構師和開發者所麵臨的主要挑戰不是與現有的遺留代碼抗爭,而是不要創建更多遺留代碼。

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

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

遺留代碼在某種程度上與軟件災難有關。但是,項目裏有遺留代碼本身不是災難。當項目裏的遺留代碼是你造成的時候,事情就開始惡化了。如何把遺留代碼(不管是你的還是從別人那裏繼承的)變成更可管理的代碼,不會妨礙整個項目的進化和按照預期展開呢?
2.3.2 在3招之內將殺(checkmate)
總而言之,從軟件災難恢複類似於從創傷恢複。假設有一次放假,你決定出去長跑,後來遭受一些嚴重損傷,比如嚴重的跟腱拉傷。

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

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

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

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

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

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

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

舉個例子,看一下圖2-1。這幅圖表示了一個混亂的代碼庫的一個小的部分包含了兩個關鍵組件,C1和C2,它們與其他組件有太多依賴。
screenshot
這裏的目的是把圖2-1的布局轉成某種帶有更多獨立區塊的東西。你需要理解的是,你不一定在一開始就得到正確的設計。隔離痛點隻是第一步,而且你不應該太在意你引入的層的大小。回到醫生那個類比,有時候,醫生會在整條腿上綁上繃帶,即使受傷的地方隻是踝關節附近。但是,幾天休息之後,醫生可能減少繃帶,隻覆蓋踝關節附近的地方。

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

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

值得注意的是,代碼塊甚至可能跨越邏輯層,有時候也會跨越物理層。就像醫生的繃帶,減少隔離痛處所覆蓋的區域是這項工作的最終目的。
screenshot
術語:
本書將會大量提及領域驅動設計(DDD),尤其從第5章“發現領域架構”開始。我們認為這裏提到的隔離區塊概念與DDD的綁定上下文概念有著非常重要的關係。在DDD項目裏,綁定上下文也可以是一個遺留代碼黑盒。在DDD裏,綁定上下文有一個與之相關的獨特模型,可以由多個模塊組成。最終,一些模塊可能會共享相同的模型。
3.測試覆蓋
一旦你把係統重構成一堆嚴格分離的區塊,你的係統應該還能工作—隻是從設計的角度來說更貼切。但是,你不能就此停下腳步。因此,強烈建議你在這個時候引入測試,它們可以告訴你係統在進一步重構之後是否仍然工作。

在這裏,測試通常是指集成測試,顧名思義,測試可能會跨越多個模塊、邏輯層和物理層。這種測試很難設置,比如說,需要使用專門的仿真數據填充的數據庫,需要連接服務,這種測試還要長時間運行。但是,它們是絕對要有的,也是絕對要運行的。在前麵提到deFeathers的論文裏,他使用了“測試覆蓋”這個術語來表示為後續更改定義行為不變性的測試。

注意:
測試在任何重構工作裏都扮演著關鍵角色。任何重構之後,你都肯定需要可以檢測是否出現任何回歸的測試來結束這個過程。但是,在某些情況下,你可能會發現在你開始隔離痛塊之前就準備好測試很有幫助。
術語:
邏輯層(Layer)這個術語通常是指邏輯邊界。相反,物理層(Tier)是指物理邊界。更具體地說,當我們提到邏輯層時,我們指的是在相同的進程空間裏邏輯分離的代碼塊(即類或者程序集)。物理層意味著物理距離以及不同的進程空間。物理層通常也是一個部署目標。一些代碼放到邏輯層還是物理層隻是選擇和設計的問題。邊界才是真正的問題。一旦你有了清晰的邊界,你就能決定哪些屬於邏輯層,哪些又屬於物理層。比如說,在ASP.NET項目裏,你可以讓一些應用程序的邏輯部署在與核心ASP.NET和頁麵一樣的應用程序池的進程裏,也可以部署到在另一台IIS機器上寄宿的不同的物理層裏。
4.持續重構
在測試覆蓋的首個迭代之後,你應該有一個穩定的係統了,也在某種程度上控製了它的行為。你甚至可能向測試輸入一些數據,然後檢測出來的行為。接著,你進入重構循環,在這個過程中,你嚐試簡化你所創建的黑盒結構。在這種情況下,你從黑盒裏取出一些代碼,然後通過新的接口重用它。來看一下圖2-3。
screenshot
如你所見,C1和C2現在已經從子係統移出來,並且封裝到一個新的可測試的黑盒裏。重複這個過程,你可以逐漸減少黑盒的大小。一個好的影響是,現存的集成測試現在可以重寫成更加接近單元測試的形式了。

2.3.3 決定是否添加人手
Frederick P. Brooks在他的《The Mythical Man Month》(Addison-Wesley,1995)裏有一句名言:向一個已經延遲的項目添加人手隻會使之更加延遲。確實是這樣,這樣做不可能對日程安排有太大影響(這句是我們的)。然而,當項目延遲時,第一個湧進腦海的就是增加勞動力。但是,項目的活動有順序的約束(比如說,調試要在開發之後),添加勞動力根本沒有任何好處。根據Brooks的說法,孕育一個孩子需要9個月,9個女人不可能在一個月內生出一個孩子。

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

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

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

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

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

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

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

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

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

最後更新:2017-06-06 07:36:00

  上一篇:go  《Android 應用測試指南》——導讀
  下一篇:go  看大牛如何複盤遞歸神經網絡!