天天看點

《Microsoft.NET企業級應用架構設計(第2版)》——第2章 為成功而設計 2.1“大泥球”

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

我們認為成功的軟體項目是在充分了解業務需求的情況下采用靠譜解決方案的項目。我們認為成功設計的軟體是在項目成功的前提下能夠(在任何可能的地方)重用現有代碼和基礎設施,并根據可用的技術和廣為人知的最佳實踐不斷改善的軟體。

今天,成功設計的軟體對于任何類型、任何規模的商業來說都是至關重要的,但更為關鍵的是避免品質低下的軟體。爛的軟體會使組織在很多地方遭受損失,比如說,響應很慢的頁面會導緻通路者離開你的網站,笨拙的使用者界面會帶來入口瓶頸,導緻你提供的服務不得不面對處理隊列,甚至未處理異常也會觸發不可控的連鎖反應,造成不可預測的後果。

軟體項目很少符合預期。我們覺得每個手捧本書的讀者都對這條表述有所了解。那麼,什麼妨礙了軟體設計的成功?如果我們要對造成軟體項目無法完全滿足預期的原因尋根究底,我們将會無可避免地觸及“大泥球”(bbm)。

bbm是一個用來描述“軟體災難”的優雅說辭。

在我們的定義裡,軟體災難是指系統的發展出了問題,不受控制,并且很難修複。有時候,設計有問題的行業系統打上更新檔也能勉強工作,但最終會變成遺留代碼等候其他人處理。一般而言,我們認為團隊應該總是把成功設計軟體作為目标,即使“錄音帶盒”的故事能夠名垂青史。事實上,根據space.com的報道,1961年進入太空的第一個宇航員yuri gagarin在發射之前按照訓示撕開了一個錄音帶盒,并對某個齒輪做了調整。

注意:

bbm的一個最新的絕佳案例是healthcare.gov。它由超過50個供應商組成的集團建構,理論上歸美國聯邦政府管治。實際上,從外面來看,沒有任何一個參與建構的供應商對整體品質進行負責。大多數元件沒有經過內建測試,甚至不同元件之間的對接也沒有及時測試。到最後,如果有人對項目的做法有所顧慮,要麼被直接忽略,要麼用商業理由或期限緊迫忽悠過去。但最終,他們還是千方百計讓網站運作起來了。

大泥球(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的有趣名額是團隊的瓶頸技能的數量。如果團隊隻有一個人擁有某些技能,或者負責某個子系統或者子產品,一旦這個人離開公司或者突然不可用了就會出大問題。

知識孤島或者資訊孤島通常用來描述代碼的所有權落在個人的肩膀上。這對于個人來說可能不是問題,但對于團隊來說絕對是個問題。

術語:

我們剛才使用的兩個術語在不同的團隊裡可能有不同的含義。這些術語是子產品和子系統。在這裡,我們隻是用這些術語來表示代碼塊,除此之外沒有其他特别的含義。子系統通常是指整個系統的一個子集;子產品則是子系統的一個部分。

繼續閱讀