在 Square Register 過去 6 年的歷史中,代碼庫和公司都發(fā)生了顯著的變化,由于應(yīng)用程序已經(jīng)從一個簡單的刷卡終端成長為一個全功能銷售終端 (point-of-sale) 系統(tǒng)。公司已經(jīng)從 10 人發(fā)展到 1000 多人,而我們不得不迅速擴(kuò)張。下面是一些我們在前進(jìn)的道路上實現(xiàn)的流程和已經(jīng)學(xué)到的東西。
隨著我們的成長,我們意識到,一旦開發(fā)團(tuán)隊達(dá)到一定規(guī)模,按平臺來組織團(tuán)隊會很低效。相反,我們用“全?!眻F(tuán)隊來負(fù)責(zé)應(yīng)用程序中的特定功能集。這些團(tuán)隊包括 iOS 工程師,Android 工程師和服務(wù)器的工程師。這給了團(tuán)隊更多的自由來集中精力去創(chuàng)造一個更深入,更全面的產(chǎn)品。我們圍繞著餐廳,零售商店,國際化支持,硬件和核心部件 (僅舉幾例),組建了面向功能的團(tuán)隊。當(dāng)團(tuán)隊擁有了足夠的縱向所有權(quán),就為工程師們做出更全面的技術(shù)決定留出了可能,并讓他們擁有了對產(chǎn)品確定的歸屬感。
2014 年之前,Register 的發(fā)布還遵循瀑布模型;我們規(guī)劃一個大的功能集,然后設(shè)定一個未來的最終日期 (三至六個月),然后努力實現(xiàn)這些功能。
這個流程沒法很好地擴(kuò)展。當(dāng)我們?yōu)楫a(chǎn)品增加功能和工程師時瀑布模型變得費力和緩慢。由于發(fā)布版本的所有開發(fā)功能必須一起發(fā)布,某一個功能的延遲或有問題會耽誤整個發(fā)布。為了確保團(tuán)隊持續(xù)保持自主性,我們找到了一個不同的,更有效的方法。
為了保持高效率,我們總是希望確保我們的流程符合我們的規(guī)模。從 2014 年開始,我們引進(jìn)了一個由 “發(fā)布列車” 組成的新模式。發(fā)布列車優(yōu)化了功能團(tuán)隊的自主權(quán),同時支持持續(xù)發(fā)布。這意味著單個功能可以在它們準(zhǔn)備好的時候就被發(fā)布,而不必等待其他工作的完成。
切換到發(fā)布列車需要改變我們的工作流程:
這意味著,我們的主分支保持在一個穩(wěn)定的狀態(tài)。這就是所說的列車的一部分。
這有很多的好處:
在 2015 年年初,我們對這個流程做了進(jìn)一步改進(jìn):現(xiàn)在發(fā)布分支被按兩個星期的時間為間隔分割和發(fā)布。這意味著團(tuán)隊在今年將有 26 次發(fā)布的機(jī)會。相比 2013 年及更早的每一年只有三個或四個發(fā)布版本而言,這是一個巨大的勝利。更多的發(fā)布機(jī)會意味著交付給客戶更多的功能。
Square 的商人依靠 Register 來經(jīng)營生意。因此,它在任何時候都必須是可靠的。我們有嚴(yán)格的流程,以確保設(shè)計、實裝和測試階段的質(zhì)量。
“寫下來是讓你知道你的思維有多草率的最自然的方式” - Guindon
這是我最喜歡的一句話之一,而且它也適用于軟件構(gòu)建!如果你只是在你的頭腦里構(gòu)建軟件的話,該軟件將是有缺陷的。在你腦袋里的形象是很模糊和短暫的;它總是持續(xù)變化的,因此需要寫下來加以澄清和完善。
Square 里每一個大的變化,都要經(jīng)過工程設(shè)計審查。如果你以前從來沒有做過,這聽起來會有點嚇人,但它其實很簡單!這個過程通常需要編寫以下的設(shè)計文檔:
然后,我們會有兩到四個評審來審查文檔,提出問題,并作出最后的決定。這些評審應(yīng)該熟悉你要擴(kuò)展的系統(tǒng)。
這似乎是大量的工作,但它是值得的。最終的結(jié)果將是一個更茁壯和更易理解的設(shè)計。我們不斷地看到,當(dāng)一個變化經(jīng)過了設(shè)計審查,會使得錯誤更少,并降低了復(fù)雜性。另外,作為一個副產(chǎn)品,我們還得到了經(jīng)過評審的系統(tǒng)文檔。棒!
因為以下幾個原因,我們的代碼審查過程是很嚴(yán)謹(jǐn)?shù)模?/p>
我們的 pull requests 流程是什么呢?每次 PR 必須滿足:
同樣,評審們被要求:
在合并之前,所有的測試必須通過。單元測試和我們的自動化集成測試 (使用 KIF) 跑成功前,pull request 的合并都是被禁止的。
我們已經(jīng)開始在做下面的事情來幫助簡化和加快 Register 的開發(fā)過程。
在 Register 團(tuán)隊的成長中我們學(xué)到的有一件事是“口頭說說”是很糟糕的知識傳遞方式。如果一年只有幾個工程師加入項目的話,這不會是一個問題,但如果一個月就會有幾個工程師加入,尤其是如果他們只是針對臨時項目 (例如臨時需要一個服務(wù)器工程師幫助建立一個特別的功能),這種尺度很快會變得很費時。一個具有標(biāo)準(zhǔn)和團(tuán)隊實踐的保持更新的文檔就變得很重要。這份文檔應(yīng)該包括哪些內(nèi)容呢?
你可能會注意到這里的一個規(guī)律:凡是能在 10 分鐘以內(nèi)回答的問題都應(yīng)清楚地記錄下來。
需要幾個工程師花數(shù)分鐘的手動流程如果用更多工程師可能需要花更長的時間。任何時候你看到瑣碎的東西花費了大量的時間,都應(yīng)該盡可能讓它自動化。
我們最近的一個最大的“自動化”成功案例是我們的 Objective-C 代碼風(fēng)格指南:我們現(xiàn)在使用 clang-format 來自動格式化提交到 Register 及其子模塊的所有代碼。這消除了代碼審查里面的各種“沒有換行”或者“太多的空白”的意見,這意味著審閱者可以專注于真正提高產(chǎn)品質(zhì)量的東西。
我們每天都要合并很多的 pull requests。之前這些“挑剔風(fēng)格”的意見會在每個 pull request 額外花 10-20 分鐘 (鑒于審查者和作者)。這意味著僅是風(fēng)格指南的自動化都讓我們每天節(jié)省了兩小時或更長時間。也就是一個星期 10 個小時。增加太快了!
另一個自動化節(jié)省時間的例子是我們每天都會發(fā)送 “Pull Request 狀態(tài)”的郵件。
在這個電子郵件存在之前,每天早晨我們當(dāng)中的 10 到 15 個人會擠在一個桌前站 10 分鐘,分配 pull requests 的審查。而現(xiàn)在,我們每天早上發(fā)出一個包含了所有開放 PR 列表的電子郵件,以及誰被分配來對其進(jìn)行審查。不需要再額外開會了。這意味著我們又多了每天 2 個多小時或每周 10 小時的開發(fā)時間。
這個每天 PR 狀態(tài)電子郵件的另一個好處是,我們可以輕松地跟蹤評審發(fā)生了什么事:花了多長時間,哪個工程師貢獻(xiàn)最大,哪個工程師審查了最多。這有助于揭示那些可能拖累團(tuán)隊的時間分配問題 (比如是否一名工程師做了團(tuán)隊一半的評審?)。
如果你的 bug 被分散在多個跟蹤器上是不可能發(fā)布無缺陷的產(chǎn)品的。有一個地方可以讓我們看到一切有關(guān)當(dāng)前版本的信息是極其重要的:bug 的數(shù)量,每個工程師未解 bug 的數(shù)量 (是否有誰忙不過來了?),以及 bug 的總體趨勢 (我們修復(fù)它們速度比它們被創(chuàng)建的速度更快嗎?)。
如果只有幾個工程師在做同一個項目,可以很容易地保證質(zhì)量:因為所有的工程師都清楚了解代碼庫,他們也都有強(qiáng)烈的歸屬感。但當(dāng)一個團(tuán)隊擴(kuò)展到 5、10、20 或更多的工程師的時候,維護(hù)這樣的品質(zhì)變得更加困難。重要的是要確保每個組件和功能有明確的負(fù)責(zé)維護(hù)它質(zhì)量的所有者。
在 Register,我們最近決定應(yīng)用程序的每個邏輯組件都要有明確的所有者。這些所有者都記錄在一個列表里以便查找。什么是一個組件?它可能是一個框架,它可能是一個面向客戶的功能,它也可能是兩者的某種組合。確切的分界并不重要;最重要的是確保應(yīng)用程序的每一行代碼都是有人所有的。這些所有者要做些什么呢?
在指派出組件明確所有者后,我們得到了很好的結(jié)果:有明確所有者的組件和默認(rèn)所有人為所有者的組件相比,代碼質(zhì)量是持續(xù)增高的 (bug 率也較低)。
這是我們最近的另一項改變:我們已經(jīng)開始在主分支上嚴(yán)格執(zhí)行“不回退”的規(guī)則。這樣做的好處是什么?我們的主分支現(xiàn)在一直都很穩(wěn)定。如果有人發(fā)現(xiàn)了一個錯誤,他完全不需要去想是否需要提交這個報告。這么做也能減少 QA 的負(fù)擔(dān),因為花在搞清楚問題是否應(yīng)提交或者它們是否重復(fù)上的時間少了。如果發(fā)現(xiàn)了錯誤,就提 bug。
這一策略和發(fā)布列車模型是齊頭并進(jìn)的:幾乎在任何時候,我們都可以從主分支拉一個發(fā)布分支,并在短短幾天內(nèi)發(fā)布到 App Store。這對一個像 Register 這樣的一個大型應(yīng)用程序是非常有價值的,它可以幫助我們盡可能快的做出行動。
鑒于我們的規(guī)模,保持主分支在可發(fā)布狀態(tài),也有助于避免“破窗效應(yīng)(broken windows)”的問題;發(fā)現(xiàn)的時候就修正 bug,確保工程師讓自己保持更高的標(biāo)準(zhǔn)。
確保 Register 里的每一個組件在建造和設(shè)計的時候都保持了可測試性的初衷是非常重要的。沒有這一點,我們就需要成倍擴(kuò)大手動 QA 的工作量:兩個功能可以通過四種方式進(jìn)行交互,三個功能可以在八個方面互動,等等。顯然,這是不合理的、不可靠的,也是不可擴(kuò)展的。
當(dāng)我們在為某個功能做工程設(shè)計工作的時候,我們不斷地問自己:“這個可以測試嗎?我在讓自動化測試容易進(jìn)行嗎?“
建立可測試性也有一個額外的好處:它引入了所有 API 的二次使用 (即測試本身)。這意味著工程師們不得不花更多的時間思考一個 API 的設(shè)計,確保它在多個情況下都是工作的。其結(jié)果是,這將使得其他工程師重用 API 變得更容易,節(jié)省了未來的時間。
對我們來說,測試不是可選項,而是一個需求。如果你在 Register 提交代碼,必須包括測試。
想想看:如果一個開發(fā)團(tuán)隊有 365 個工程師,每位工程師只需要每年弄壞一次主分支,就可以讓項目在整一年都停擺了。這顯然是不能接受的,并且會極大的減慢進(jìn)度和挫敗的開發(fā)團(tuán)隊。
有什么簡單的方法來防止主分支被破壞?首先當(dāng)然是不合并錯誤的代碼!這就是 pull request CI 需要做的,每個 Register 的 pull request 都有一個 CI 在有新提交的時候被觸發(fā)。大約 15 分鐘后,工程師就可以放心的提交 PR,因為他或她不會因此引入任何導(dǎo)致回退的問題。
當(dāng)我們有新加入的工程師時,這會是非常有價值的。他們可以提交代碼,而無需擔(dān)心他們將引入讓主分支不工作的改動。
以下是在過去三年當(dāng) Register 的 iOS 團(tuán)隊不斷壯大的一些個人看法。
在一個大的應(yīng)用程序里,你會有大量的代碼。有些代碼是很老的了。但老并不一定意味著壞。只要你有良好的測試覆蓋率,舊代碼將繼續(xù)正常工作。不要把時間花在“清理”那些的履行了需求并且沒有拖累任何人的代碼上去。這種清理過程中你能做的最好的事情就是不破壞任何東西。所以還是把這些時間花在創(chuàng)建新的功能上吧。
在一個大的代碼庫里,你很容易就把所有的時間都花在其中,而無法從外界學(xué)習(xí)新東西。
你怎么解決這個問題?每周花些時間 (我每天預(yù)留一小時) 從你的代碼庫之外的資源來進(jìn)行學(xué)習(xí)。你能從哪兒學(xué)習(xí)呢?可以看看那些聽起來有趣的討論,或者閱讀你覺得有興趣的領(lǐng)域的文章。堅持這樣做,你會發(fā)現(xiàn)這些并行的知識會為你的日常工作帶來很多好處。有時,正是這些小事情會造成結(jié)果的巨大區(qū)別。
很少有可以立即解決的事情,包括技術(shù)累積。如果技術(shù)累積需要很長的時間,不要讓自己感到沮喪,尤其是在一個大的代碼庫里。
想想像體重增加一樣積累技術(shù):你并不會一夜就增加一百磅;它是逐步顯現(xiàn)的。就像減肥一樣,也需要大量的時間和精力來消化技術(shù) - 從來都不會有一個瞬時方案。在累積的同時跟蹤你的進(jìn)步,并確保它在一個合理的速度向下進(jìn)展。
如果你有任何問題,請隨時通過 k@squareup.com 聯(lián)系我。感謝您的閱讀!
(感謝 Connor Cimowsky, Charles Nicholson, Shuvo Chatterjee, Ben Adida, Laurie Voss, and Michael White 的審查。)