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