架構師和開發團隊應該如何協作?組織架構怎麼設定最好?
軟體架構構成了一個系統的骨架。它定義了當面對不同的功能性和非功能性需求時的系統行為。一方面,傳統瀑布式方法對專案開發的所有階段提出了硬性約束要求,因此傳統瀑布式方法顯得僵化。另一方面,敏捷運動讓我們擁抱改變,即使是處於開發階段後期的改變。儘管我們正推動自己從僵化的開發模式邁向更靈活的模式,軟體架構由於其系統骨架的定位,天然地對變化敏感。因此關鍵之處在於,敏捷運動擁抱的軟體架構必須是可持續的——具備可持續概念的軟體架構,支援在專案複雜度不斷增加的同時,系統能以漸進式的、簡單的以及可維護的方法進行擴充套件。
在這篇文章裡,我回顧了自己在傳統瀑布式軟體架構和敏捷軟體架構下的工作經歷。描述了兩者在以下三個方面表現出來的相似性及差異性:
- 軟體架構扮演的具體角色
- 軟體架構的時間跨度
- 軟體架構的輸出
軟體架構的定義(實際上你也能新增你自己下的定義)成百上千。存在這麼多種定義的原因在於每個人都是基於自身情境下定義。我對IEEE給出的定義特別推崇,這個定義描述的基本概念非常形象化。此外,該定義描述出了軟體架構的精髓本質,同時適用於瀑布式和敏捷流程,而不是隻能匹配某一個。在本文的後續部分,我會引述到該定義:
一個系統的基本組織結構、基本組成構件和互相之間的關係,以及構件於外部環境間的關係。同時,軟體架構為後續的設計和架構演化提供了指導性原則。
瀑布式軟體架構傳統瀑布式開發的特徵在於其由一系列有明確的開始和結束時間的階段構成,每個階段包含確定的活動集。所有階段串接在一起,每個階段嚴重依賴於前一個階段的交付產出。圖1闡述了瀑布式開發過程涉及到的常見階段。
圖1:傳統的瀑布式模型
軟體架構工作通常在軟體需求確定後開始啟動,認為在此時,關於系統應做什麼,已經確定好了。下一步是審查負責確定軟體架構的人以及當前階段的實際輸出結果。
傳統軟體架構軟體架構實踐中通常由軟體架構師完成。軟體架構師擁有豐富的技術知識和經驗——往往是由公司中已經達到一定等級的開發人員晉升而來的。軟體架構師負責分析軟體需求,並基於這些需求為未來系統的演化做某些技術決策。許多公司通常是一個專案對應一個軟體架構師,但在一些更大的公司裡,軟體架構師們可能以團隊的形式共同合作。當專案所在領域非常複雜或者專案的週期很長,比如長達2到3年甚至更久時,通常情況下就會有軟體架構師團隊。不是所有的公司都有指定的軟體架構師角色——許多公司把這部分職責委託給他們的高階開發人員承擔。在本文的後續部分,我所談的是指由軟體架構師執行的具體活動項,而不是執行活動項的人,除非另有明確說明。
傳統的軟體架構師有4大主要特徵:
- 關注大格局——軟體架構師應當思考,系統在未來的幾個月(有時間甚至是幾年)會變成什麼樣子。此外,他還應當一併考慮到所有其他相關的系統(比如第三方系統和資料庫),以及系統間的通訊問題。
- 遵從性導向——軟體架構師應考慮到可能的遵從性問題。可能的有法律規範、許可證、標準或其它,身為軟體架構師他應確保系統在未來能夠滿足這些至關重要的標準。
- 繪製藍圖——軟體架構師的一項重要交付是從不同角度描述架構設計的文件和圖表集。開發團隊利用這些交付件開始進行系統構建。
- 不太多的實際操作經驗——軟體架構師的工作目標是產出最終文件以供開發人員使用。儘管軟體架構師通常擁有開發經驗,但他很少參與到開發過程中去——而是指導開發人員構建已設計好的系統。在某些時候他甚至可能調去參加另一個專案,留下開發人員自己完成開發。
我曾經為全球最大的啤酒公司的其中之一做一個軟體專案。專案用了2到3年的時間,使用典型的瀑布式方法,在不同的階段有對應的負責人——軟體架構師、開發人員、測試人員。我是一個小團隊裡的開發,我們根據軟體架構師傳達的指示和指導意見,執行系統的開發工作。最初軟體架構師提供在場支援,但過了不久他轉到另一個專案去了,因此就減小了在這個專案上的工作量。當新的依賴不斷出現,有時候很難照著擬定的軟體架構推進,因為和已有架構設計的規定不相符。儘管嘗試過讓我們的高階開發人員接管架構設計,但專案還是逐漸成為所謂的義大利麵條式程式碼(spaghetti code),每個人都害怕去改程式碼,因為很可能在哪個地方就出問題了。遺憾的是,專案已經來不及做任何重大的改變了,無法回到正軌,所以儘管專案最終釋出了,但日後仍然被停掉了。
敏捷運動傳統的瀑布式架構的性質是一次性活動,活動有明確的起止時間,而敏捷軟體架構是一個持續不斷的過程,也許沒有終點。敏捷軟體架構使我們可以對架構設計實施更改,如果需要的話,可以定期實施。擁抱變化的一大機制是在專案裡運用迭代和增量開發。在Scrum裡,這些迭代被稱之為sprint,如圖2所示,典型的一個sprint的週期約為2-4星期。週期視窗如此之小,所以能對提出的任何改變做快速討論。此外,敏捷非常關注團隊的協作,團隊成員之間存在的任何問題應立即解決掉,以防止出現誤解及溝通不暢的情況。
圖2:Scrum中的一個典型Sprint
敏捷運動使得人們可以擁抱專案中的變化,但它並沒有告訴你應當以多快的速度響應變化。軟體架構設計作為系統的骨幹支柱,對變化非常敏感。比如說,在專案中期,你覺得你能更改專案使用的平臺或程式語言嗎?這樣的更改,即便很罕見,也需要通過多輪的迭代才能完成。這種改變甚至能把你重新拉回到專案的啟動階段。當牽扯到軟體架構設計時,有些型別的變化就比較苦楚,需要較多的執行時間。
敏捷軟體架構師Scrum定義了三類角色:
- 產品負責人(product owner)——負責提供具體業務領域的資訊
- scrum master——負責推進團隊的溝通和協作
- 開發團隊(development team)——負責實現使用者story以及軟體編寫
為了將傳統軟體架構師角色轉換為適配敏捷世界,我們需要先分析下一些可能的變種。構建Scrum團隊的一個方法是讓開發團隊和一個單獨的軟體架構師團隊一起緊密地合作。圖3說明了多Scrum團隊的構建場景。
圖3:一個單獨的軟體架構師團隊和多個開發團隊的合作
憑這種方法確實可以完成團隊的構建,但存在兩個問題:
- 軟體架構師團隊可能會變成需求方,而開發團隊成為實現方,這正是瀑布式方法下出現的典型情況。最後呈現的結果是,軟體架構師定義專案的前景,留給開發人員去完成實現。如果開發人員沒有足夠的參與,不被尊重對待,他們的積極性被下降。
- 上面這樣做的後果,導致一部分開發人員可能也想加入軟體架構師團隊,這樣就能夠給其他開發人員分配工作。Trustpilot提供了一個例子,他們有一個核心團隊和開發團隊,但總的來說卻破壞了工作風氣和彼此的協作,於是他們把核心團隊的所有成員都移到一個開發團隊中去,收到了積極的效果。
另一種做法是將軟體架構師直接置於開發團隊中,如圖4所示。
圖4:每個開發團隊都有一名軟體架構師
這種情況下,敏捷軟體架構師的責任發生了一些變化:
- 在當前和巨集圖願景之間保持平衡——敏捷軟體架構師需要同時思考兩點,當前開發過程中發生的事情以及將其與整個系統的巨集圖願景進行對齊。
- 實際操作經驗——敏捷軟體架構師同時也是開發人員,參與系統的實現工作。這樣使得敏捷軟體架構師能夠獲得關於做出的架構決策的第一手反饋資訊。
- 建立原型,明智決策——當需要做出重大的技術決策時,快速建立原型可以揭示這個決策是否可行以及它會如何影響到現有系統。再者,與全體開發團隊的溝通非常關鍵,團隊合作的效果遠好於獨自一人埋頭苦幹。
- 關注可持續性——極其重要的一點是,架構設計決策造就可持續的軟體架構,從長期來看,可持續的軟體架構能很好地支撐起專案。個人責任感和情懷是其中不可或缺的一部分。敏捷軟體架構師是開發團隊的一份子,所以如前所述他能得到自己所做決策的第一手反饋。相比瀑布式方法,軟體架構師的決策傳遞到開發團隊並由開發團隊負責執行,敏捷方法提升了個人責任感。
如果想在不同的開發團隊(也可能是不同的專案)之間共享軟體架構師資源,可以選擇構建擁有獨立軟體架構師團隊的組織結構。除此之外,如果從事的領域很複雜,需要考慮的視角很多,也可以採用擁有獨立軟體架構師團隊的組織結構。在這類情況,必須保證軟體架構師與開發人員的合作緊密,並展現出了對開發人員的支援。敏捷方法關注協作,將軟體架構師從開發人員從分離出來,使得協作變得困難了。結果開發過程變得更貼近於瀑布式模型。我個人更青睞第二種變體,在那軟體架構師處於開發團隊中,因此團隊成員之間的溝通交流更有效。在一個更高的層次上架構師仍能(也應當能)協調一致。
敏捷軟體架構的時間跨度敏捷軟體架構的一個重要方面是何時開始進行架構設計。不同於瀑布式模型對每一個階段都做了明確定義,在敏捷的世界裡,不存在一個所有人都同意開始的確定的時間點。一個典型的做法是引入sprint #0,這是一個特殊的sprint,開發環境已經配置好了,一些重要的決策已確定(比如程式語言、平臺、資料庫等等)。
這種方法有個常見的陷阱,即人們傾向於延長sprint #0,因為總會發現事情“幾乎就快準備好了”。經常聽到“再給一個星期,我們就能開始進入常規的sprint”這樣的話。很多時候你會發現自己已經在開發系統了,但使用者story還沒見著,因為“提前幫忙完成功能實現真的很酷”。這種情況你應當預先商定出sprint #0的結束日期,可以設定在一個常規sprint的持續週期內,或者類似相近的時間。
可能有人會疑惑,萬一到常規sprint應啟動的時候,還沒完成架構設計,要怎麼辦。嗯,其實這也沒關係。事實上有可能永遠不會有準備好的一天。那也沒關係。軟體架構設計是一個持續不斷的過程。你應當經常性地重新看回來,去修正系統的骨幹。在架構設計不能給予支援保證時,你是無法進行系統開發的。Simon Brown說:
控制原則敏捷團隊沒必要建立敏捷軟體架構。但一個好的架構確能做到敏捷。
我們生活在一個複雜的世界,每一個業務領域也都是如此複雜。當構建一個軟體的架構時,真的很容易從一開始就把事情複雜化了,進而讓後續開發更容易出錯。以下兩條原則是做決策時事實上的標準:
- 保持簡單,愚笨(KISS,Keep It Simple, Stupid)
- 你不需要它(YAGNI,You Aren’t Gonna Need It)
如果在那一刻我們真的需要一個具體的功能和做成決策,這兩條原則試圖讓我們對此做慎重的思考。如果我們把做決策推遲到一個更晚的時段,就能保持架構的簡單,並因此在一個更長的時間裡方便管理。軟體架構變得複雜的一個通常做法是引入抽象——可能變成一個新的花式層,以一種格式複製資料,然後轉換為另一種格式,或者為了讓程式碼具備可測試性,可能創建出許許多多的類、介面、工廠等等。
不過,在運用這兩條原則時還要提防一處陷阱,我們傾向於把所有事情都延緩處理直到最後一刻。到那個時候,可能已經變得太難實施所需的變更了。為了避開陷阱,我們的任務難得多,因為:
我們不應在最後一刻做出決策,而應在最有責任這麼做的時刻做出決策。
當我準備做出架構上的決策決定時,如果做這個決定的確定性很高,我通常的做法是先做一些不那麼花時間的小的準備。我也會去諮詢我的同事,我們一起討論問題。
真實世界之痛我曾經做過一個輪渡票務線上銷售的專案。這是一個複雜的系統,它需要和4個其它第三方系統進行通訊。一開始我們的首要關注點是基於使用者story完成功能實現。儘管我們知道我們需要一個更復雜巧妙的快取機制,但那時還不必要——我們得先完成當前的使用者story,於是我們選擇了延緩處理。然後到後面,由於與其它第三方系統的大量通訊,系統變慢了。我們別無選擇,只能停止使用者story開發,一門心思撲在快取機制上——但這時事情已經不好辦了。
如何組織文件瀑布式方法要求編寫大量的文件,因為需要用文件來在不同的階段(以及每個階段的參與者)之間進行資訊傳遞。編寫文件的過程不僅耗時間,而且由於文件存在對功能的不當描述,還經常造成誤解。更進一步,難以保持文件的及時更新,因為開發傾向於快速推進,很多時候不會再理會文件了。正如我們使用敏捷來迭代地編寫程式碼,同樣可以如此處理文件。我們開始只描述系統的重要方面,然後在需要時持續地加入更多新的資訊。
哪些內容應寫入文件切勿對同一個東西以不同的方式做多次的文件化處理。舉個例子,有工具能幫助你從程式碼中生成圖表——和建立獨立的圖表相比,這樣做方便很多,圖表很容易過時的。除此之外,假如你為了描述系統的某一方面而建立了視覺化工件,就沒必要再使用文字(除非你想要增加一些不能用視覺方式表達的細節)建立一大堆文字文件去描述同一件事情。這裡有一點很重要,你和你的團隊應對使用的繪圖符號有一致的理解。
怎樣文件化可能會想到使用UML來對軟體架構進行文件化。UML是標準語言,每個人都能理解,大學裡也教,因此UML一定是團結起組織內每個人的不二選擇。我的經驗卻顯示,實踐中很少有人使用UML。原因之一可能是UML提供的描述系統的方法非常多,可以從不同的視角進行描述,所以不經常使用UML的人會感到挫敗。
在敏捷業界,沒有特定的工具用來文件化軟體架構。可以使用塗寫白板、便利貼、文字文件、wiki等等(見圖5)。實踐中,只使用2到3種不同的格式會比較穩妥些,要不然資訊可能會變得難以儲存和檢索。比如說,在白板上塗寫一陣後,你可能需要對其進行拍照,這樣就儲存下來了圖表的電子版本。如果後面你要再次編輯,這時你得選擇到底是直接數字編輯照片,還是重新在白板上畫一遍然後再照一張相。
我用圖表工具來生成系統構件的簡圖。通常用Microsoft Visio或draw.io(已整合在Google Drive中),不過還有大量的其它工具可選,線上和離線的都有。如果開會時在白板上做了繪製,我會在會議結束後用圖表工具重畫一遍一模一樣的圖,以保持我畫的東西的格式統一,彼此不存在大的差異。如果我需要對圖表新增額外的註解,我一般會另行建立文字文件。
圖5:白板畫和便利貼形式的文件化
總結軟體架構定義了未來系統的骨架。它不只是由線點組成的圖畫,而是一系列管理支配著系統開發的完整決策,包括程式碼本身。應細緻地考慮每一個做出的決策與決定,它們都是一種權衡折衷。敏捷理念要求對變化持開放心態,甚至是來自專案晚期的變化,和傳統的瀑布式模型不同,瀑布式模型希望需求比較穩定。不過,系統骨架的變更往往並不容易實施,甚至可能把你拉回到開發階段。因此,不要等到最後一刻才做出該做的決策決定,這點很重要,而應該在最有責任這麼多的時候做出決定,為此甚至可以不惜冒點小風險,實現某些在那個時間點不做要求的東西。再者,敏捷軟體架構要求綜合全面地看待巨集圖願景和“現在”,構建出一個每個人都能在上面添磚加瓦的可持續的平臺。