【DevCloud · 敏捷智庫】暴走在釋出前夜的開發,你怕不怕?

tony0087發表於2021-09-11

摘要:每個月都有2天開發團隊要通宵熬夜,大家苦不堪言。有個別的開發同學,罵完公司罵同事,罵完同事罵客戶的,甚至連自己都不放過……

 

來自一個CEO的敘述

 

在一次企業交流會上,一個公司的CEO提道,“我們公司做敏捷開發的轉型有一段時間了,採用的是4週一迭代,相比之前的瀑布式開發,我們可以在每一個月就讓客戶看到我們的成果物,這確實為公司和客戶搭建起了良好的溝通橋樑。但是,也出現了一個不好的情況,就是開發和客戶之前的矛盾激化了,由於採用了迭代,所以每個月都有2天開發團隊要通宵熬夜,大家苦不堪言。有個別的開發同學,罵完公司罵同事,罵完同事罵客戶的,甚至連自己都不放過……”

那些暴走在釋出前夜的開發,你是否也遇見過呢?

 

感同身受的無奈和憤怒

 

來自開發的一波怒氣

“我暈,這誰提的程式碼啊,上來就白頁面了,還玩個球啊!”

“我倒,我本地都是好用的啊,怎麼部署到環境上不行了呢!”

“我去,這哪個大兄弟提了這麼些程式碼,太能寫了。我和他程式碼衝突多到想哭!”

“我的程式碼讓哪個孫子給覆蓋了,我一下午白寫了,別讓我查出來誰幹的哈!”

“催催催,催個大腦袋啊,你行你來,不行就別說話,合程式碼這事是人乾的活嗎!”

“幾位大哥,我知道問題出在哪了,我這還有段程式碼沒提交上去呢,嘿嘿,不好意思哈。”“我*!”

 

來自領導的心酸無奈

 

領導:“每次釋出前,我都不太想去你們開發那,太壓抑了!,你說咋辦?”

骨幹:“咋辦,還能咋辦,換人唄。一個個都不懂咋開發還能咋辦。”

領導:“不是都經過筆試面試進來的嗎?咋還能不懂開發呢?”

骨幹:“開發不是開發完了就行,得好用啊。這幫小年輕就知道埋頭coding,然後扣出來的都是一堆不好用的程式碼,不好用倒也沒啥,直接就往程式碼庫裡提交啊!”

領導:“這幫小年輕這麼虎嗎?”

骨幹:“這還不算是虎的呢,還那種程式碼衝突了的,不管三七二十一直接就忽略衝突提交,我有好幾回,一拉取最新程式碼,一面紅色啊。”

領導:“那你多帶帶他們,告訴他們怎麼做啊!“

骨幹:“我都告訴過很多遍了,每次都說‘我測了啊!’,‘誒?奇怪了!’,‘不好意思哥,我忘提交了’,就這幫小年輕,我是真心帶不動哈。我還是覺得以前瀑布挺好的,要不就變回去得了。“

領導:“那不行啊,客戶現在挺認可我們的。用敏捷一個月就能看到新做的東西。效果好,客戶滿意度高。你還是想想辦法吧。“

骨幹:“我看還是換人吧。我是無法阻止別人不**的“

領導:“……“

 

問題出在哪裡

 

不知道讀者讀到這裡是否感同身受了一下下,如果沒有的話,那麼恭喜你,你很幸運的加入到了一個優秀的團隊或者公司。

不過,可能你的IT生涯是不完整的……

近些年來,整個IT圈子都在宣揚DevOps,也因此很多人都知道Dev的工作是給應用系統增加新的功能/修復Bug,而Ops的工作是要保持系統的穩定和高效能,而DevOps後就是要調和了Dev和Ops的矛盾、打破二者的壁壘,以更好的面對變化。大家滿懷著希望開始了敏捷和DevOps,可是往往在新潮和流行的“上層建築”往往發現了“形而下”的落地困難。比如Dev側自身內部的問題。

在上面舉例中的開發的那波怒火和領導的無奈中,誠然有開發人員自身能力素質的問題,但這並不能算是問題,因為所有人都是從菜鳥過來了,沒有人是天生的王者。“怒氣”到底因為什麼?為什麼會有怒氣呢?

這個怒氣幾乎都來源於在團隊協作中的“別人”,其實就是在溝通上產生了問題,這可以歸結為工作方式方法的問題,說得更直白就是沒有遵循一個良好的軟體開發實踐。

在傳統的瀑布開發的時候,開發往往會在最後“奮力一搏”完成了專案的部署工作,然後就進入了“休息”的狀態,如果有bug就修改bug,如果有其他的(如文件補充,但願你沒有這樣的經歷)做其他的,處於被動觸發這種相對輕鬆的姿態,所以他們從某種程度上說,是可以接受“別人”的問題的,畢竟忍耐一下就過去了。

而在敏捷的迭代開發中,每次迭代都要產出潛在的可交付成果物,部署和釋出是“永不止境”的,所以就沒有彷彿能看到黎明吹響勝利號角的“奮力一搏”,合程式碼的人率先遭遇一波傷害,緊接著其他的開發一個個承受著傷害(如果出現Bug),然後大家最後再在一起承受某個或某些誰都不知道的問題帶來的打擊……所以往往每次的釋出都是一次心志的磨練和煎熬,尤其以大工作量週期以月為單位迭代為最——這個背後的元兇其實就是“整合”!

 

有什麼好辦法

 

雖然相對傳統制造業軟體顯得“年輕”,但是也經歷了無數的大風大浪,這種問題並不專屬於某些公司,這種煩惱和無奈也並非只有他們才經歷過。早在軟體開發的“上古”時代,軟體界的大神——Martin Fowler就有過這樣的經歷:

“我還可以生動記起第一次看到大型軟體工程的情景。我當時在一家大型英國電子公司的QA部門實習。我的經理帶我熟悉公司環境,我們進到一間巨大的,充滿了壓抑感和格子間的的倉庫。我被告知這個專案已經 開發了好幾年,現在正在整合階段,並已經整合了好幾個月。我的嚮導還告訴我沒人知道整合要多久才能結束”。

Martin Fowler不僅在他實習的期間認識到整合是一件很耗時並難以預測的過程,並且很多專案和團隊並不把整合當回事。所以為了解決整合所帶來的問題以及很多人思想上的不以為意,Martin Fowler 提出了——持續整合。

持續整合 是一種軟體開發實踐。在持續整合中,團隊成員頻繁整合他們的工作成果,一般每人每天至少整合一次,也可以多次。每次整合會經過自動構建(包括自動測試)的 檢驗,以儘快發現整合錯誤。

從其定義上來看,持續整合可以很好的解決開發們的“怒氣”的問題,開發的怒氣根本上來說就是由於在協作中缺少“溝通”所造成的,進而將問題推到了“別人”身上,試想如果整個開發的過程中,每個團隊的成員彼此做的功能,或者說所提交的程式碼是“透明”的,那麼就可以很大程度上減少這種“別人”的問題了。並且這種“透明”化的週期無需太長時間,最長為一天,最短於幾個小時內,從而可以很好的解決了開發團隊整合的問題,降低了交付的風險。

那麼,具體的落地應該有哪些呢?

 

應該如何落地

 

欲善其功必先利其器,在談落地實踐之前,首先看看要實現持續整合需要有哪些工具。

基礎工具一:版本控制系統。持續整合最基本的前提條件是對其程式碼庫的版本控制,即:對於程式碼庫的每一項變更,都必須被安全地存放到專有的版本控制系統中,目前最主流的版本控制系統當屬Git。

基礎工具二:構建工具。構建工具能夠透過處理應用的原始碼,自動生成所需的軟體(包)。軟體工具的構建步驟取決於所選用的技術棧。如,Java的應用,可使用Maven作為構建工具。

講完了持續整合的定義和基礎工具後,那麼持續整合的過程是怎麼樣的呢?我們一起看看Martin Fowler是怎麼帶著我們玩轉持續整合的吧。(以下內容來自Marin Fowler的持續整合)

舉個簡單的例子:現在假設要完成一個軟體的一部分功能,具體任務是什麼並不重要,我們先假設這個 feature 很小,只用幾個小時就可以完成。

一開始,將已整合的原始碼複製一份到 本地計算機。這可以透過從原始碼管理系統的 mainline 上 check out 一份原始碼做到。

現在拿到了工作複製,接下來需要做一些事情來完成任務。這包括修改產品程式碼和新增修改自動化測試。在持續整合中,軟體應該包含完善的可自動執行的測試——自測試程式碼。這一般需要用到某一個流行的 XUnit 測試框架。

一旦完成了修改,就會在自己的計算機上啟動一個自動化 build。這會將工作複製中的原始碼編譯並連結成為一個可執行檔案,並在之上執行自動化測試。只有當所有的 build 和測試都完成並沒有任何錯誤時,這個 build 過程才可以認為是成功的。

當本地build 成功後,就可以考慮將改動提交到原始碼倉庫。但麻煩的情況在於別人可能已經在我之前修改過 mainline。這時我需要首先把別人的修改更新到自己的工作複製中,再重新做 build。如果別人的程式碼和自己的有衝突,就會在編譯或測試的過程中引起錯誤。自己有責任改正這些問題,並重復這一過程,直到自己的工作複製能透過 build 並和 mainline 的程式碼同步。

 

一旦本地的程式碼能透過 build,並和 mainline 同步,就可以把我的修改提交到原始碼倉庫。

 

然而,提交完程式碼不表示就完事大吉了。還要做一遍整合 build,這次在整合計算機上並要基於 mainline 的程式碼。只有這次 build 成功了,修改才算告一段落。因為總有可能會忘了什麼東西在自己的機器上而沒有更新到原始碼倉庫。只有提交的改動被成功的整合了,這次工作才能算結束。

如果兩個開發者的修改存在衝突,這通常會被第二個人提 交程式碼前本地做 build 時發現。即使這時僥倖過關,接下來的整合 build 也會失敗掉。不管怎樣,錯誤都會被很快檢測出來。此時首要的任務就是改正錯誤並讓 build 恢復正常。在持續整合環境裡,必須儘可能快地修復每一個整合 build。好的團隊應該每天都有多個成功的 build。錯誤的 build 可以出現,但必須儘快得到修復。

這樣做的結果是你總能得到一個穩定的軟體,它可能有一些 bug,但可以正常工作。每個人都基於相同的穩定程式碼進行開發,而且不會離得太遠,否則就會不得不花很長時間整合回去。Bug被發現得越快,花在改正上的 時間就越短。

上述基本上就是持續整合的過程和步驟了。那麼基於此持續整合又又哪些關鍵的實踐呢?主要有如下幾個:

 

只維護一個原始碼

 

在軟體專案裡需要很多檔案協調一致才能 build 出產品。跟蹤所有這些檔案是一項困難的工作,尤其是當有很多人一起工作時。所以,一點也不奇怪,軟體開發者們這些年一直在研發這方面的工具。這些工具稱為 原始碼管理工具,或配置管理,或版本管理系統,或原始碼倉庫,或各種其它名字。大部分開發專案中它們是不可分割的一部分。但可惜的是,並非所有專案都是如 此。雖然很罕見,但我確實參加過一些專案,它們直接把程式碼存到本地驅動器和共享目錄中,亂得一塌糊塗。

所以, 作為一個最基本的要求,你必須有一個起碼的原始碼管理系統。成本不會是問題,因為有很多優秀的開源工具可用。當前較好的開源工具是 Subversion。(更 老的同樣開源的 CVS 仍被廣泛使用,即使是 CVS 也比什麼都不用強得多,但 Subversion 更先進也更強大。)有趣的是,我從與開發者們的交談中瞭解到,很多商業原始碼管理工具其實不比 Subversion 更好。只有一個商業軟體是大家一致同意值得花錢的,這就是 Perforce。

一旦你有了原始碼管理系統,你要確保所有人都知道到哪裡去取程式碼。不應出現這樣的問題:“我應該到哪裡去找xxx檔案?” 所有東西都應該存在原始碼倉庫裡。

即便對於用了原始碼倉庫的團隊,我還是觀察到一個很普遍的錯誤,就是他們沒有把 所有東西都放在原始碼倉庫裡。一般人們都會把程式碼放進去,但還有許多其它檔案,包括測試指令碼,配置檔案,資料庫Schema,安裝指令碼,還有第三方的庫,所 有這些build時需要的檔案都應該放在原始碼倉庫裡。我知道一些專案甚至把編譯器也放到原始碼倉庫裡(用來對付早年間那些莫名其妙的C++編譯器很有效)。 一個基本原則是:你必須能夠在一臺乾淨的計算機上重做所有過程,包括checkout和完全build。只有極少量的軟體需要被預裝在這臺乾淨機器上,通 常是那些又大又穩定,安裝起來很複雜的軟體,比如作業系統,Java開發環境,或資料庫系統。

你必須把 build需要的所有檔案都放進原始碼管理系統,此外還要把人們工作需要的其他東西也放進去。IDE配置檔案就很適合放進去,因為大家共享同樣的IDE配 置可以讓工作更簡單。

版本控制系統的主要功能之一就是建立 branch 以管理開發流。這是個很有用的功能,甚至可以說是一個基礎特性,但它卻經常被濫用。你最好還是儘量少用 branch。一般有一個mainline就夠 了,這是一條能反映專案當前開發狀況的 branch。大部分情況下,大家都應該從mainline出發開始自己的工作。(合理的建立 branch 的 理由主要包括給已釋出的產品做維護和臨時性的實驗。)

一般來說,你要把build依賴的所有檔案放進程式碼管理 系統中,但不要放build的結果。有些人習慣把最終產品也都放進程式碼管理系統中,我認為這是一種壞味道——這意味著可能有一些深層次的問題,很可能是無 法可靠地重新build一個產品。

 

自動化Build

 

通常來說,由原始碼轉變成一個可執行的系統是一個複雜的過程,牽扯到編譯,移動檔案,將 schema 裝載到資料庫,諸如此類。但是,同軟體開發中的其它類似任務一樣,這也可以被自動化,也必須被自動化。要人工來鍵入各種奇怪的命令和點選各種對話方塊純粹是浪費時間,也容易滋生錯誤。

在大部分開發平臺上都能找到自動化 build 環境的影子。比如 make,這在 Unix 社群已經用了幾十年了,Java 社群也開發出了 Ant,.NET 社群以前用 Nant,現在用 MSBuild。不管你在什麼平臺上,都要確保只用一條命令就可以執行這些指令碼,從而 build 並執行系統。

一 個常見的錯誤是沒有把所有事都放進自動化 build。比如:Build 也應該包括從原始碼倉庫中取出資料庫 schema 並在執行環境中設定的過程。我要重申一下前面說過的原則:任何人都應該能從一個乾淨的計算機上 check out 原始碼,然後敲入一條命令,就可以得到能在這臺機器上執行的系統。

Build 指令碼有很多不同的選擇,依它們所屬的平臺和社群而定,但也沒有什麼定勢。儘管大部分的 Java 專案都用 Ant,還是有一些專案用 Ruby(Ruby Rake 是一個不錯的 build 指令碼工具)。我們也曾經用 Ant 自動化早期的 Microsoft COM 專案,事實證明很有價值。

一個大型 build 通常會很耗時,如果只做了很小的修改,你不會想花時間去重複所有的步驟。所以一個好的 build 工具應該會分析哪些步驟可以跳過。一個通用的辦法是比較原始檔和目標檔案的修改時間,並只編譯那些較新的原始檔。處理依賴關係要麻煩一些:如果一個目標文 件修改了,所有依賴它的部分都要重新生成。編譯器可能會幫你處理這些事情,也可能不會。

根據你的需要,你可能 會想 build 出各種不同的東西。你可以同時 build 系統程式碼和測試程式碼,也可以只 build 系統程式碼。一些元件可以被單獨 build。Build 指令碼應該允許你在不同的情況中 build 不同的 target。

我們許多人都用 IDE,許多 IDE 都內建包含某種 build 管理功能。然而,相應的配置檔案往往是這些 IDE 的專有格式,而且往往不夠健壯,它們離了 IDE 就無法工作。如果只是 IDE 使用者自己一個人開發的話,這還能夠接受。但在團隊裡,一個工作於伺服器上的主 build 環境和從其它指令碼里執行的能力更重要。我們認為,在 Java 專案裡,開發者可以用自己的 IDE 做 build,但主 build 必須用 Ant 來做,以保證它可以在開發伺服器上執行。

 

讓你的Build自行測試

傳統意義上的 build 指編譯,連結,和一些其它能讓程式執行起來的步驟。程式可以執行並不意味著它也工作正常。現代靜態語言可以在編譯時檢測出許多 bug,但還是有更多的漏網之魚。

一種又快又省的查 bug 的方法是在 build 過程中包含自動測試。當然,測試並非完美解決方案,但它確實能抓住很多 bug——多到可以讓軟體真正可用。極限程式設計(XP)和測試驅動開發(TDD)的出現很好地普及了自測試程式碼的概念,現在已經有很多人意識到了這種技巧的 價值。

經常讀我的著作的讀者都知道我是 TDD 和 XP 的堅定追隨者。但是我想要強調你不需要這兩者中任何一個就能享受自測試程式碼的好處。兩者都要求你先寫測試,再寫程式碼以透過測試,在這種工作模式裡測試更多 著重於探索設計而不是發現 bug。這絕對是一個好方法,但對於持續整合而言它並不必要,因為這裡對自測試程式碼的要求沒有那麼高。(儘管我肯定會選擇用 TDD 的方式。)

自測試程式碼需要包含一套自動化測試用例,這些測試用例可以檢查大部分程式碼並找出 bug。測試要能夠從一條簡單的命令啟動。測試結果必須能指出哪些測試失敗了。對於包含測試的 build,測試失敗必須導致 build 也失敗。

在過去的幾年裡,TDD 的崛起普及了開源的 XUnit 系列工具,這些工具用作以上用途非常理想。對於我們在 ThoughWorks 工作的人來說,XUnit 工具已經證明了它們的價值。我總是建議人們使用它們。這些最早由 Kent Beck 發明的工具使得設定一個完全自測試環境的工作變得非常簡單。

毋庸置疑,對於自動測試的工作而言,XUnit 工具只是一個起點。你還必須自己尋找其他更適合端對端測試的工具。現在有很多此類工具,包括FIT,Selenium,Sahi,Watir,FITnesse, 和許多其它我無法列在這裡的工具。

當然你不能指望測試發現所有問題。就像人們經常說的:測試透過不能證明沒有 bug。然而,完美並非是你要透過自測試 build 達到的唯一目標。經常執行不完美的測試要遠遠好過夢想著完美的測試,但實際什麼也不做。

 

每人每天要向mainline提交程式碼

 

整合的主要工作其實是溝 通。整合可以讓開發者告訴其他人他們都改了什麼東西。頻繁的溝通可以讓人們更快地瞭解變化。

讓開發者提交到 mainline 的一個先決條件是他們必須能夠正確地 build 他們的程式碼。這當然也包括透過 build 包含的測試。在每個提交迭代裡,開發者首先更新他們的工作複製以與 mainline 一致,解決任何可能的衝突,然後在自己的機器上做 build。在 build 透過後,他們就可以隨便向 mainline 提交了。

透過頻繁重複上述過程,開發者可以發現 兩個人之間的程式碼衝突。解決問題的關鍵是儘早發現問題。如果開發者每過幾個小時就會提交一次,那衝突也會在出現的幾個小時之內被發現,從這一點來說,因為 還沒有做太多事,解決起來也容易。如果讓衝突待上幾個星期,它就會變得非常難解決。

因為你在更新工作複製時也 會做 build,這意味著你除了解決原始碼衝突外也會檢查編譯衝突。因為 build 是自測試的,你也可以查出程式碼執行時的衝突。後者如果在一段較長的時間還沒被查出的話會變得尤其麻煩。因為兩次提交之間只有幾個小時的修改,產生這些問題 只可能在很有限的幾個地方。此外,因為沒改太多東西,你還可以用 diff-debugging 的技巧來找 bug。

總的來說,我 的原則是每個開發者每天都必須提交程式碼。實踐中,如果開發者提交的更為頻繁效果也會更好。你提交的越多,你需要查詢衝突錯誤的地方就越少,改起來也越快。

頻繁提交客觀上會鼓勵開發者將工作分解成以小時計的小塊。這可以幫助跟蹤進度和讓大家感受到進展。經常會有人一開始根 本無法找到可以在幾小時內完成的像樣的工作,但我們發現輔導和練習可以幫助他們學習其中的技巧。

 

每次提交都 應在整合計算機上重新構建 mainline

使用每日提交的策略後,團隊就能得到很多經過測試的 build。這應該意味著 mainline 應該總是處於一種健康的狀態。但在實踐中,事情並非總是如此。一個原因跟紀律有關,人們沒有嚴格遵守在提交之前在本地更新並做 build 的要求。另一個原因是開發者的計算機之間環境配置的不同。

結論是你必須保證日常的 build 發生在專用的整合計算機上,只有整合 build 成功了,提交的過程才算結束。本著“誰提交,誰負責”的原則,開發者必須監視 mainline 上的 build 以便失敗時及時修復。一個推論是如果你在下班前提交了程式碼,那你在 mainline build 成功之前就不能回家。

我知道主要有兩種方法可以使用:手動 build,或持續整合伺服器軟體。

手動 build 描述起來比較簡單。基本上它跟提交程式碼之前在本地所做的那次 build 差不多。開發者登入到整合計算機,check out 出 mainline 上最新的原始碼(已包含最新的提交),並啟動一個整合 build。他要留意 build 的程式,只有 build 成功了他的提交才算成功。(請檢視 Jim Shore 的描述。)

持續整合伺服器軟體就像一個監視著原始碼倉庫的監視器。每 次原始碼倉庫中有新的提交,伺服器就會自動 check out 出原始碼並啟動一次 build,並且把 build 的結果通知提交者。這種情況下,提交者的工作直到收到通知(通常是 email)才算結束。

在 ThoughtWorks,我們都是持續整合伺服器軟體的堅定支持者,實際上我們引領了 CruiseControl 和  最 早期的開發,兩者都是被廣泛使用的開源軟體。此後,我們還做了商業版的 Cruise 持續整合伺服器。我們幾乎在每一個專案裡都會用持續整合伺服器,並且對結果非常滿意。

不是每個人都會用持續整合伺服器。Jim Shore 就清楚地表達了為什麼他更偏好手動的辦法。我同意他的看法中的持續整合並不僅僅是安裝幾個軟體而已,所有的實踐 都必須為了能讓持續整合更有效率。但同樣的,許多持續整合執行得很好的團隊也會發現持續整合伺服器是個很有用的工具。

許多組織根據安排好的日程表做例行 build,如每天晚上。這其實跟持續整合是兩碼事,而且做得遠遠不夠。持續整合的最終目標就是要儘可能快地發現問題。Nightly build 意味著 bug 被發現之前可能會待上整整一天。一旦 bug 能在系統裡呆這麼久,找到並修復它們也會花較長的時間。

做好持續整合的一個關鍵因素是一旦 mainline 上的 build 失敗了,它必須被馬上修復。而在持續整合環境中工作最大的好處是,你總能在一個穩定的基礎上做開發。mainline 上 build 失敗並不總是壞事,但如果它經常出錯,就意味著人們沒有認真地在提交程式碼前先在本地更新程式碼和做 build。當 mainline 上 build 真的失敗時,第一時間修復就成了頭等大事。為了防止在 mainline 上的問題,你也可以考慮用 pending head 的方法。

當團隊引入持續整合時,這通常是最難搞定的事情之一。在初期,團隊會非常難以接 受頻繁在 mainline 上做 build 的習慣,特別當他們工作在一個已存在的程式碼基礎上時更是如此。但最後耐心和堅定不移的實踐常常會起作用,所以不要氣餒。

 

保持快速 build

持續整合的重點就是快速反饋。沒有什麼比緩慢的 build 更能危害持續整合活動。這裡我必須承認一個奇思怪想的老傢伙關於 build 快慢標準的的玩笑(譯者注:原文如此,不知作者所指)。我的大部分同事認為超過1小時的 build 是不能忍受的。團隊們都夢想著把 build 搞得飛快,但有時我們也確實會發現很難讓它達到理想的速度。

對大多數專案來說,XP 的10分鐘 build 的指導方針非常合理。我們現在做的大多數專案都能達到這個要求。這值得花些力氣去做,因為你在這裡省下的每一分鐘都能體現在每個開發者每次提交的時候。持 續整合要求頻繁提交,所以這積累下來能節省很多時間。如果你一開始就要花1小時的時間做 build,想加快這個過程會相當有挑戰。即使在一個從頭開始的新專案裡,想讓 build 始終保持快速也是很有挑戰的。至少在企業應用裡,我們發現常見的瓶頸出現在測試時,尤其當測試涉及到外部服務如資料庫。

也許最關鍵的一步是開始使用分階段build(staged build)。分階段 build(也被稱作 build 生產線)的基本想法是多個 build 按一定順序執行。向 mainline 提交程式碼會引發第一個 build,我稱之為提交 build(commit build)。提交 build 是當有人向 mainline 提交時引發的 build。提交 build 要足夠快,因此它會跳過一些步驟,檢測 bug 的能力也較弱。提交 build 是為了平衡質量檢測和速度,因此一個好的提交 build 至少也要足夠穩定以供他人基於此工作。

一旦提交 build 成功,其他人就可以放心地基於這些程式碼工作了。但別忘了你還有更多更慢的測試要做,可以另找一臺計算機來執行執行這些測試。

一個簡單的例子是兩階段 build。第一階段會編譯和執行一些本地測試,與資料庫相關的單元測試會被完全隔離掉(stub out)。這些測試可以執行得非常快,符合我們的10分鐘指導方針。但是所有跟大規模互動,尤其是真正的資料庫互動的 bug 都無法被發現。第二階段的 build 執行一組不同的測試,這些測試會呼叫真正的資料庫並涉及更多的端到端的行為。這些測試會跑上好幾小時。

這種情況下,人們用第一階段作為提交 build,並把這作為主要的持續整合工作。第二階段 build 是次級build,只有 在需要的時候才執行,從最後一次成功的提交 build 中取出可執行檔案作進一步測試。如果次級 build 失敗了,大家不會立刻停下手中所有工作去修復,但團隊也要在保證提交 build 正常執行的同時儘快修正 bug。實際上次級 build 並非一定要正常執行,只要 bug 都能夠被檢查出來並且能儘快得到解決就好。在兩階段 build 的例子裡,次級 build 經常只是純粹的測試,因為通常只是測試拖慢了速度。

如果次級 build 檢查到了 bug,這是一個訊號,意味著提交 build 需要新增一個新測試了。你應該儘可能把次級 build 失敗過的測試用例都新增到提交 build 中,使得提交 build 有能力驗證這些 bug。每當有 bug 繞過提交測試,提交測試總能透過這種方法被加強。有時候確實無法找到測試速度和 bug 驗證兼顧的方法,你不得不決定把這個測試放回到次級 build 裡。但大部分情況下都應該可以找到合適加入提交 build 的測試。

上面這個例子是關於兩階段 build,但基本原則可以被推廣到任意數量的後階段 build。提交 build 之後的其它 build 都可以同時進行,所以如果你的次級測試要兩小時才能完成,你可以透過用兩臺機器各執行一半測試來快一點拿到結果。透過這個並行次級 build 技巧,你可以向日常 build 流程中引入包括效能測試在內的各種自動化測試。(當我過去幾年內參加 Thoughtworks 的各種專案時,我碰到了很多有趣的技巧,我希望能夠說服一些開發者把這些經驗寫出來。)

 

在模擬生產環境中進行測試

測試的關鍵在於在受控條件下找出系統內可能在實際生產中出現的任何問題。這裡一個明顯的因素是生產系 統的執行環境。如果你不在生產環境做測試,所有環境差異都是風險,可能最終造成測試環境中執行正常的軟體在生產環境中無法正常執行。

自然你會想到建立一個與生產環境儘可能完全相同的測試環境。用相同的資料庫軟體,還要同一個版本;用相同版本的作業系統;把所有生產環 境用到的庫檔案都放進測試環境中,即使你的系統沒有真正用到它們;使用相同的IP地址和埠;以及相同的硬體;

好吧,現實中還是有很多限制的。如果你在寫一個桌面應用軟體,想要模擬所有型號的裝有不同第三方軟體的桌上型電腦來測試顯然是不現實的。類似的,有些生產環境可 能因為過於昂貴而無法複製(儘管我常碰到出於經濟考慮拒絕複製不算太貴的環境,結果得不償失的例子)。即使有這些限制,你的目標仍然是儘可能地複製生產環 境,並且要理解並接受因測試環境和生產環境不同帶來的風險。

如果你的安裝步驟足夠簡單,無需太多互動,你也許能在一個模擬生產環境裡執行提交 build。但事實上系統經常反應緩慢或不夠穩定,這可以用 test double 來解決。結果常常是提交測試為了速度原因在一個假環境內執行,而次級測試執行在模擬真實的生產環境中。

我注意到越來越多人用虛擬化來搭建測試環境。虛擬機器的狀態可以被儲存,因此安裝並測試最新版本的build相對簡單。此外,這可以讓你在一臺機器上執行多個測試,或在一臺機器上模擬網路裡的多臺主機。隨著虛擬化效能的提升,這種選擇看起來越來越可行。

 

讓每個人都能輕易獲得最新的可執行檔案

軟體開發中最困難的部分是確定你的軟體行為符合預期。我們發現事先清楚並正確描述需求非常困難。對人們而言,在一個有缺陷的東西上指出需要修改的地方要容易得多。敏捷開發過程認可這種行為,並從中受益。

為了以這種方式工作,專案中的每個人都應該能拿到最新的可執行檔案並執行。目的可以為了 demo,也可以為了探索性測試,或者只是為了看看這周有什麼進展。

這做起來其實相當簡單:只要找到一個大家都知道的地方來放置可執行檔案即可。可以同時儲存多份可執行檔案以備使用。每次放進去的可執行檔案應該要透過提交測試,提交測試越健壯,可執行檔案就會越穩定。

如果你採用的過程是一個足夠好的迭代過程,把每次迭代中最後一個 build 放進去通常是明智的決定。Demo 是一個特例,被 demo 的軟體特性都應該是演示者熟悉的特性。為了 demo 的效果值得犧牲掉最新的 build,轉而找一個早一點但演示者更熟悉的版本。

 

每個人都能看到進度

持續整合中最重要的是溝通。你需要保證每個人都能輕易看到系統的狀態和最新的修改。

溝通的最重要的途徑之一是 mainline build。如果你用 Cruise,一個內建的網站會告訴你是否正有 build 在進行,和最近一次 mainline build 的狀態。許多團隊喜歡把一個持續工作的狀態顯示裝置連線到 build 系統來讓這個過程更加引人注目,最受歡迎的顯示裝置是燈光,綠燈閃亮表示 build 成功,紅燈表示失敗。一種常見的選擇是紅色和綠色的熔岩燈,這不僅僅指示 build 的狀態,還能指示它停留在這個狀態的時間長短,紅燈裡出現氣泡表示 build 出問題已經太長時間了。每一個團隊都會選擇他們自己的 build 感測器。如果你的選擇帶點幽默性和娛樂性效果會更好(最近我看到有人在實驗跳舞兔)。

即使你在使用手動持續整合,可見程度依然很重要。Build 計算機的顯示器可以用來顯示 mainline build 的狀態。你很可能需要一個 build 令牌放在正在做 build 那人的桌子上(橡皮雞這種看上去傻傻的東西最好,原因同上)。有時人們會想在 build 成功時弄出一點噪音來,比如搖鈴的聲音。

持續整合伺服器軟體的網頁可以承載更多資訊。Cruise 不僅顯示誰在做 build,還能指出他們都改了什麼。Cruise 還提供了一個歷史修改記錄,以便團隊成員能夠對最近專案裡的情況有所瞭解。我知道 team leader喜歡用這個功能瞭解大家手頭的工作和追蹤系統的更改。

使用網站的另一大優點是便於那些遠端工作的人瞭解專案的狀態。一般來說,我傾向於讓專案中發揮作用的成員都坐在一起工作,但通常也會有一些外圍人員想要了解專案的動態。如果組織想要把多個專案的 build情況聚合起來以提供自動更新的簡單狀態時,這也會很有用。

好的資訊展示方式不僅僅依賴於電腦顯示器。我最喜歡的方式出現於一箇中途轉入持續整合的專案。很長時間它都無法拿出一個穩定的 build。我們在牆上貼了一整年的日曆,每一天都是一個小方塊。每一天如果 QA 團隊收到了一個能透過提交測試的穩定 build,他們都會貼一張綠色的貼紙,否則就是紅色的貼紙。日積月累,從日曆上能看出 build 過程在穩定地進步。直到綠色的小方塊已經佔據了大部分的空間時,日曆被撤掉了,因為它的使命已經完成了。

 

自動化部署

自動化整合需要多個環境,一個執行提交測試,一個或多個執行次級測試。每天在這些環境之間頻繁複製 可執行檔案可不輕鬆,自動化是一個更好的方案。為實現自動化,你必須有幾個幫你將應用輕鬆部署到各個環境中的指令碼。有了指令碼之後,自然而然的結果是你也要 用類似的方式部署到生產環境中。你可能不需要每天都部署到生產環境(儘管我見過這麼做的專案),但自動化能夠加快速度並減少錯誤。它的代價也很低,因為它 基本上和你部署到測試環境是一回事。

如果你部署到生產環境,你需要多考慮一件事情:自動化回滾。壞事情隨時可 能發生,如果情況不妙,最好的辦法是儘快回到上一個已知的正常狀態。能夠自動回滾也會減輕部署的壓力,從而鼓勵人們更頻繁地部署,使得新功能更快釋出給用 戶。(Ruby on Rails 社群開發了一個名為 Capistrano 的工具,是這類工具很好的代表。)

我還在服 務器叢集環境中見過滾動部署的方法,新軟體每次被部署到一個節點上,在幾小時時間內逐步替換掉原有的軟體。

在 web 應用開發中,我碰到的一個有趣的想法是把一個試驗性的 build 部署到使用者的一個子集。團隊可以觀察這個試驗 build 被使用的情況,以決定是否將它部署到全體使用者。你可以在做出最終決定之前試驗新的功能和新的 UI。自動化部署加上良好的持續整合的紀律是這項工作的基礎。

(以上內容來自Marin Fowler的持續整合)

 

寫在最後

“我的腦海中還是會浮現出第一段描述的早期軟體專案。他們已經到了一個漫長項 目的末期(至少他們期望如此),但還是不知道距離真正的結束有多遠。”這是來自Martin Fowler曾經歷過的感受。

而文中的第二段的那些開發們的“怒氣”是筆者從十年前做開發的時候,所經歷過的幾個團隊所發生過的。

在整合的過程中,總會有種種無法預測的事情發生,不論是人還是事,你根本無法預測其進展從而很容易進入到迷茫地帶,每一個處在迷茫地帶的人都很難去做到輕鬆應對,久而久之開發人員會產生疲於奔命之感,導致團隊無法凝聚成“拳頭”打出強有力的一拳。基於這種情況,筆者認為這正是我們引入持續整合的原因所在。

筆者認為Martin Fowler關於持續整合的落地實踐部分已經比較詳盡完全可以用來大家公共參考學習,所以沒有在關公面前耍大刀,故引用於此文章。但是在我們持續整合實際的過程中一定會遇到很多問題,比如提升build效率的分段build策略或者其他實際的需求等,這都需要我們在日常工作中,透過迭代不斷的來研究和完善其實踐的方法,並在回顧的過程中加以討論、分析總結,最終提升團隊的研發效率。也可以使用一些大廠如作為持續整合的工具,其提供專業的一站式解決方案,方便了中小企業的DevOps落地。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1747/viewspace-2796418/,如需轉載,請註明出處,否則將追究法律責任。

相關文章