《Microsoft.NET企業級應用架構設計(第2版)》——第2章 為成功而設計 2.1“大泥球”
本節書摘來自異步社區《Microsoft.NET企業級應用架構設計(第2版)》一書中的第2章,第2.1節,作者: 【意】Dino Esposito(埃斯波西托) , Andrea Saltarello(索爾塔雷羅)著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看
第2章 為成功而設計
我們認為成功的軟件項目是在充分了解業務需求的情況下采用靠譜解決方案的項目。我們認為成功設計的軟件是在項目成功的前提下能夠(在任何可能的地方)重用現有代碼和基礎設施,並根據可用的技術和廣為人知的最佳實踐不斷改善的軟件。
今天,成功設計的軟件對於任何類型、任何規模的商業來說都是至關重要的,但更為關鍵的是避免質量低下的軟件。爛的軟件會使組織在很多地方遭受損失,比如說,響應很慢的頁麵會導致訪問者離開你的網站,笨拙的用戶界麵會帶來入口瓶頸,導致你提供的服務不得不麵對處理隊列,甚至未處理異常也會觸發不可控的連鎖反應,造成不可預測的後果。
軟件項目很少符合預期。我們覺得每個手捧本書的讀者都對這條表述有所了解。那麼,什麼妨礙了軟件設計的成功?如果我們要對造成軟件項目無法完全滿足預期的原因尋根究底,我們將會無可避免地觸及“大泥球”(BBM)。
BBM是一個用來描述“軟件災難”的優雅說辭。
在我們的定義裏,軟件災難是指係統的發展出了問題,不受控製,並且很難修複。有時候,設計有問題的行業係統打上補丁也能勉強工作,但最終會變成遺留代碼等候其他人處理。一般而言,我們認為團隊應該總是把成功設計軟件作為目標,即使“磁帶盒”的故事能夠名垂青史。事實上,根據Space.com的報道,1961年進入太空的第一個宇航員Yuri Gagarin在發射之前按照指示撕開了一個磁帶盒,並對某個齒輪做了調整。
注意:
BBM的一個最新的絕佳案例是HealthCare.gov。它由超過50個供應商組成的集團構建,理論上歸美國聯邦政府管治。實際上,從外麵來看,沒有任何一個參與構建的供應商對整體質量進行負責。大多數組件沒有經過集成測試,甚至不同組件之間的對接也沒有及時測試。到最後,如果有人對項目的做法有所顧慮,要麼被直接忽略,要麼用商業理由或期限緊迫忽悠過去。但最終,他們還是千方百計讓網站運行起來了。
2.1 “大泥球”
大泥球(Big Ball of Mud,BBM)這個詞幾年前就有了,它是指一個係統幾乎沒有組織,到處都有隱藏的依賴關係以及大量重複的數據和代碼,各層和關注點也沒有清晰標識,也就是意大利麵代碼叢林。這個詞是由伊利諾伊大學的Brian Foote和Joseph Yoder創造的,在他們的論文裏有討論。
在這篇論文裏,作者沒有把BBM指責成最糟糕的實踐;他們隻是建議架構師和開發者時刻準備應對BBM風險,以及學習如何控製它。換句話說,幾乎任何超過一定規模的軟件項目都會麵臨BBM威脅。學習如何識別和處理BBM是避免軟件災難的唯一途徑。
2.1.1 “大泥球”的成因
關於BBM入侵軟件項目有幾個基本事實。首先,也是最重要的,BBM並非一夜形成,起初也沒有那麼大。其次,沒有單一開發者可以從頭開始創建BBM。BBM總是團隊的產物。
為了找到問題的根源,我們給出幾個可能導致BBM的主要成因,通常發生在協作中。
1.未能捕獲客戶的所有需求
架構師和開發者構建軟件,特別是企業軟件,都有清晰的目的。軟件的目的是通過高級聲明來表達的,裏麵包含了客戶想要達到的目標以及想要解決的問題。軟件工程科學有一個完整的分支處理軟件需求,並把需求劃分成不同層次—業務需求、利益相關者需求、功能性需求、測試需求,或許還有更多。
關鍵是你怎麼把一長串表述粗略的需求變成通過編程語言編碼的具體特性。
在第1章“今天的架構師和架構”裏,我們把確認需求列為架構師的主要職責之一。需求通常來自多種渠道,體現相同係統的不同視角。因此不必驚訝於某些需求相互矛盾,或者某些需求在不同的利益相關者的眼裏有著明顯不同的重要性。分析需求以及決定哪些需求直接對應某個特性隻是架構師工作的第一階段。
當入選的特性列表提交驗證時就會進入第二階段。建議的特性列表必須滿足所有利益相關者的完整需求列表。某些利益相關者的某些需求被砍掉是可以接受的。
是的,可以接受,隻要你可以合理地解釋為什麼砍掉那些需求。
為了設計係統解決問題,你必須完全理解這個問題及其領域。這不一定馬上就能成事,也不是隨便讀一下需求就能成事。有時候,你不得不說“不”。大多數情況下,你不得不問“為什麼”,然後討論添加一個新的特性支持一組特定的需求有何利弊。
我們在過去幾年裏得到的教訓是,根據不完整的需求理解來寫的代碼,隻要能跑起來也比花幾天時間尋找一個完美的解決方案更有幫助。就這點而言,敏捷開發方式更多是基於常識而不是理論。
確認需求需要溝通以及溝通技能。有時候溝通就是無法奏效。有時候雙方相信的東西是都錯的,然後雙方最後都得嚐試“救火”。於是,開發者學會抱怨沒有得到足夠的細節,而業務人員反駁說每一條都在文檔裏詳細列明。
溝通問題的根源是業務人員和開發者使用不同的詞匯,使用和期待不同精度的描述。此外,除了開發者,幾乎每個人都認為編程遠比實際的容易。添加一個新的需求對於業務和銷售人員來說就像在文檔裏添加一行新的內容那麼簡單。實際上,係統的某些適應性是必要的,但會有附加代價。
因為新增或者修改需求產生的軟件適應性是有代價的,但沒人願意付出這個代價,所以某些特性的適應性是通過刪除其他特性來實現的,或者更常見的情況是,通過砍掉重構、測試、調試和文檔來實現。當這種情況出現時,其實也經常出現,大泥球就會產生。
重要:
在上一章裏,我們討論了我們通常如何處理需求。我們想在這裏簡要回顧一下。基本上,我們把原始需求分門別類,使用ISO/IEC的分類作為起點。實際的分組流程就像在Microsoft Office Excel工作表裏為每個分類創建一個標簽那樣簡單。接著,我們檢查各個標簽,在裏麵添加或刪除需求,這樣做更有效。我們也會細心檢查幾乎是空的標簽,嚐試深入了解那些方麵。最後,這個流程使我們更主動地尋求更多、更清晰的信息,從已知的數據裏提取或者索要更多細節。
2.在係統發展時堅持RAD
一開始,項目看起來很容易管理。客戶說這個項目不會發展成為一個大的複雜的係統。因此,你可能會選擇某種形式的快速應用程序開發(Rapid Application Development,RAD),不太注意能讓應用程序隨規模更好向上擴展的設計方麵。
如果事實證明係統會發展,RAD方案就會顯示出它固有的局限性。
雖然RAD方案對於以數據為中心的小型簡單應用程序(如CRUD應用程序)來說可能剛好合適,但事實證明它對於包含大量經常改變的領域規則的大型應用程序來說是一個危險的方案。
3.不準確的估算
業務人員總是想在他們確認委托和打開錢包之前準確地了解他們將會得到什麼。但是,業務人員是根據高級特性和行為來理解的。一旦他們說他們想要網站隻對驗證用戶開放,他們相信自己已經把這個問題說清楚了。他們不認為有需要指明用戶應該可以通過一大堆社交網絡登錄。如果後麵說這點沒有提到,他們會發誓已經寫在文檔裏了。
相反,開發者想要準確地了解他們需要構建什麼,以便做出合理估算。在這個階段,開發者根據具體細節思考,把業務特性分成更小的部分。問題是,隻有清楚定義和確認所有需求才能準確估算。而估算會因需求改變而改變。
不確定性占主導地位。以下是一個常見的場景。
業務團隊和開發團隊就特性和安排初步達成協議,每個人都很滿意。開發團隊要先提供一個原型。
開發團隊交付原型,安排一個演示會議。
這個演示會議讓大家對正在構建的係統有了更加具體的了解。業務人員在某種程度上也拓寬了他們對係統的視野,並要求做出一些改變。
開發團隊很樂意添加更多項目內容,但需要額外的時間。結果,構建這個係統變得更加昂貴。
但是,對於業務人員來說,這是同一個係統,沒有理由付出不同代價。他們所做的隻是把精確度提高一個水平!
當你做估算時,指出哪裏存在不確定性是極其重要的。
4.缺乏及時的測試
在軟件項目裏,測試會出現在各種層次。
你有單元測試,判斷軟件的每個組件是否滿足功能性需求。在重構代碼時,單元測試對於發現功能的回歸也是很重要的。
你有集成測試,判斷軟件是否兼容環境和基礎設施,以及兩個或多個組件能否協同工作。
你有驗收測試,判斷完成的係統,包括所有功能,是否滿足客戶的所有需求。
單元測試和集成測試與開發團隊有關,目的是讓團隊對軟件的質量有信心。測試結果可以告訴團隊是否做對以及做好。
就單元測試和集成測試而言,一個關鍵的方麵是測試在什麼時候寫和運行。
就單元測試而言,有一個廣泛的共識是,你應該一邊寫代碼一邊測試,並把測試的運行與構建流程整合起來。但是,運行單元測試通常比運行集成測試更快。集成測試可能需要更長時間來設置,每次運行之前也可能需要重設。
在一個項目裏,你從其他個人開發者或者團隊得到一些組件,這些組件一開始可能沒有辦法很好地協同工作。有鑒於此,最好把集成工作逐步攤分到整個項目,這樣能使問題盡早顯現。把集成測試放到最後會導致很大風險,因為這會導致你沒有時間在不引入補丁以及在補丁之上再引入補丁的情況下修複問題。
5.不明確的項目所有權
HealthCare.gov這個案例告訴我們,很多供應商一起構建的係統必須有一個明確的項目所有權。
擁有這個項目的供應商或者個人需要對整體質量負責,他的職責包括檢查每個部分是否達到最高質量以及兼容係統的其他部分。這號人物可以推動測試及時完成,以便盡早發現集成問題。與此同時,這號人物還可以協調團隊與利益相關者之間的安排和需要,以便每個供應商都能在不損害其他合作夥伴的情況下完成自己的任務。
當項目的領導關係沒有明確定義,或者像HealthCare.gov那樣定義了但沒有嚴格執行時,對項目負責就隻能依靠個別供應商的美好意願了,但每個供應商都沒有理由處理他們合同以外的事情。尤其在壓力之下,更容易隻關注眼下可工作的東西,而不顧及整合和設計等方麵。
當這一切發生時,即使隻是幾個迭代,意大利麵代碼也會產生。
6.忽略“危機”狀態
技術困難在軟件項目裏是常見的事物,而不是新奇的事物。
麵對這種困難,保持含煳和安撫客戶都是沒有意義的。即使項目順利完成,隱藏問題也不會讓你得到額外的獎勵。但是,如果項目失敗了,隱藏問題肯定會為你帶來很多額外的麻煩。
作為一名架構師,你應該盡力做到開源軟件那樣開放、坦率。
如果你識別出某種危機,讓他們知道,告訴他們你在做什麼以及打算做什麼。就修複提供詳細的計劃可能是工作的最難部分。但是,利益相關者需要知道發生什麼事,以及明確團隊朝著正確方向前進。有時候,詳細計劃的更新和已經完成的工作量足以避免你的壓力增大到超出你能承受的範圍。
如何識別出與大泥球有關的“危機”狀態和成功項目?
再次說明,這是常識問題。對困難保持開放和坦率會為你敲響警鍾,這樣不好的事情就有可能在無可挽回之前被製止。
2.1.2 “大泥球”的征兆
毫無疑問,作為一名架構師,項目經理,或者兩者兼有,你應該盡最大努力避免BBM。但是,有沒有一些清晰不含煳的征兆可以表明你正處在泥球滾動的軌道上呢?
下麵給出一些普遍存在的跡象,它們會提醒你設計是否朝著有問題的方向發展。
1.僵硬,因而脆弱
你可以掰彎一塊木頭嗎?如果你保持這樣做會有什麼後果?
一塊木頭通常是一種僵硬物質,具有抵抗變形的特點。當施加足夠的力時,就會造成永久變形,木頭也會斷裂。
僵硬的軟件呢?
僵硬軟件具有某種抵抗變化的特點。抵抗程度是根據回歸來衡量的。你對一個類做出改變,改變的影響會順延到一組依賴的類。結果很難預計一個改變(任何改變,即使是最簡單的)實際將會耗費多長時間。
如果你敲打一塊玻璃,或者任何其他易碎物質,你會把它弄成碎片。類似地,當你在軟件裏引入一個更改並且把它分散到多個地方時,這個軟件毫無疑問就會變得“易碎”。
就像在現實生活裏一樣,易碎性和僵硬性在軟件裏也是成對出現的。當改變軟件裏的一個類導致(很多)其他類因為(隱藏的)依賴遭到破壞時,你就很清楚地看到壞設計的征兆了,你需要盡快修複。
2.易於使用,但不易於重用
假設你有一個軟件在一個項目裏工作良好,你想在另一個項目裏重用它。但是,在新的項目裏複製這個類或者鏈接這個程序集並不管用。
為什麼會這樣?
當你把相同的代碼移到另一個項目卻不管用時,通常是因為依賴性或者它的設計沒有考慮共享。二者之中,依賴性是最大問題。
真正的問題並不僅僅是依賴性,還有依賴的數量和深度。為了在另一個項目裏重用一塊功能,你不得不導入更多功能。到了最後,不再進行任何重用,代碼會從頭開始重寫。
這也不是好設計的跡象。這種負麵設計效果通常被稱為不可移動性(Immobility)。
3.易於變通,不易於修複
在修改一個軟件的類時,你通常會找到兩種或更多方式來做。大多數情況下,一種方式絕妙、優雅,並且符合設計,但實現起來很困難,也很辛苦。相反,另一種方式編碼起來很順暢,也很快速,但它隻是一種修補。
你應該怎麼做?
事實上,兩種方式都可以用來解決問題,取決於給定的期限以及你的經理的安排。
一般而言,變通方案(workaround)比正確的解決方案看起來更快更易實現並不是一個理想的情況。事實上,問題並不僅僅是單純的額外勞動。有時候,你就是害怕基本變通方案之外的選擇。回歸才是真正讓你感到害怕的東西。如果你有好的充足的單元測試,至少你可以肯定任何回歸一旦出現都能很快捕獲。但接著你又開始想,單元測試又不能修複代碼,或許變通方案就是解決問題的最快方式了!
一個特性更易修補(hack)而不是修複(fix)對於你的整個設計來說並不是一個很好的評述。它隻是意味著類之間存在太多沒有必要的依賴,而你的類也沒有形成特別有凝聚力的代碼。因此,這已經足夠嚇唬你遠離正確的解決方案了,它可能比快速修複更有強迫感,也需要更深層次的重構。
這種負麵設計效果通常被稱為粘稠性(Viscosity)。
2.1.3 使用指標檢測BBM
文字上,另一個經常用來表達在爛代碼上構建軟件密集型係統的詞語是技術債務(Technical Debt,TDBT)。
Ward Cunningham提出的債務比喻非常形象,正如財務債務,爛代碼也會隨著時間增長,累積需要償付的利息。從長遠來說,TDBT會變成一個重擔,影響甚至妨礙後續的償付措施。改編溫斯頓·丘吉爾爵士的一句名言,我們可以說,“對於開發團隊來說,TDBT就像一個站在木桶裏的人嚐試通過手柄把它抬起。”
TDBT是一個抽象的概念,要有工具來測量甚至去除,就像要求你會施展魔法一樣。但是,仔細觀察一些事實,並對此進行一些分析總結出一些指標。雖然結果不一定就是你想要的,但至少它們提高了你的警覺。
下麵來看一些指標和工具,它們可以幫你判斷BBM是否在試圖咬你。
1.靜態代碼分析
一般而言,團隊的人知道大多數問題出在哪裏,但有時候你需要提供與代碼有關的問題的證據,而不是僅僅在口頭上提及它們。靜態代碼分析工具為你掃描和探測代碼,為日後討論提供一份有用的報告。
Microsoft Visual Studio 2013有它自己的靜態代碼分析器,可以計算代碼覆蓋率和圈複雜度(Cyclomatic Complexity)。其他類似的框架包含在CodeRush和ReSharper等產品(本章稍後會有討論)裏,或者同一個供應商也提供獨立的產品。
一個有趣的靜態分析工具是NDepend,它也可以為你創建依賴圖,便於查看最有問題的區域的數量和位置。
重要:
不管看起來怎麼樣,靜態代碼分析工具既不會告訴你技術債務的根源是什麼,也不會告訴你需要做什麼才能減少它。它們隻是為你的決定提供輸入,而不是為你做決定。
2.知識孤島
另一組可以用來識別某種TDBT的有趣指標是團隊的瓶頸技能的數量。如果團隊隻有一個人擁有某些技能,或者負責某個子係統或者模塊,一旦這個人離開公司或者突然不可用了就會出大問題。
知識孤島或者信息孤島通常用來描述代碼的所有權落在個人的肩膀上。這對於個人來說可能不是問題,但對於團隊來說絕對是個問題。
術語:
我們剛才使用的兩個術語在不同的團隊裏可能有不同的含義。這些術語是模塊和子係統。在這裏,我們隻是用這些術語來表示代碼塊,除此之外沒有其他特別的含義。子係統通常是指整個係統的一個子集;模塊則是子係統的一個部分。
最後更新:2017-06-01 16:31:30