Quora是如何維持高質量程式碼的

jointforce發表於2015-08-03

一個高質量的程式碼庫可以加快長期開發的速度,因為它會使得迭代、協作和維護更加容易。在Quora,我們十分重視程式碼庫的質量。

Quora是如何維持高質量程式碼的

除了會取得收益之外,要維護高質量的程式碼,會帶來一大筆間接費用,還會犧牲實際開發週期。很多人發現,實際產生的收益很難抵消這一間接費用,這時人們會面臨兩個選擇:要麼以低質量程式碼提升開發速度,要麼維護高質量程式碼而犧牲開發速度。而對於初創公司來說,他們希望開發速度能快一些,所以就不得不使用低質量的程式碼。

我們開發了一系列工具和流程,這樣就可以在維護高質量程式碼庫的同時,提升開發的速度。在這篇文章中,我們將會介紹關於保證程式碼質量的一些方法,以及一些平衡這兩方面的具體案例。

維護高質量程式碼的目標

維護高質量程式碼主要的好處在於能夠長期推動開發速度,這也是我們所關注的重點。通過編寫清晰程式碼而產生的短期成本,就可以換來長期的收益。

記住這一點,然後下面是四點基本原則,我們發現對程式碼質量非常有幫助:

Quara的程式碼質量基本原則

1. 程式碼的閱讀和理解都要很容易——現在的情況是更多的時間用在了程式碼的閱讀上,而不是程式碼的編寫上。實際上,應該把閱讀的時間減少,即便這樣意味著需要花費更多的時間來寫程式碼。

2. 程式碼的不同部分需要有不同的質量標準——不同的程式碼行需要有不同的使用時間、範圍、被破壞的風險、被破壞後造成的成本及其修復的成本,等等。總體來講,這些對長期迭代速度的影響不同,所以執行一個統一的質量標準是不合理的。

3. 程式碼質量的間接費用是可以縮減的——維護高質量成本的間接費用一般是可以節省的,這可以通過使用自動化、更好的工具、更好的流程和培訓更優質的開發人員來實現。

4. 程式碼庫一致性很重要——保持整個程式碼庫的一致性是十分有價值的,即便這意味著有一部分程式碼不能是最優的。一個不一致的程式碼庫是很難閱讀和理解的(見第一點),也很難編寫,很難通過自動化工具進行優化。

下面,我來介紹幾個我們將這幾點原則用於實際開發的例子。

提交後code review

我們程式碼庫的程式碼變更需要從六個維度進行評估——正確性、隱私、效能、架構、複用性和風格。讀程式碼是code review的關鍵部分,所以說,code review在提升程式碼可讀性方面起著至關重要的作用。

Quora是如何維持高質量程式碼的

但是有一點不好的是,code review同樣會減緩開發速度。比如說,提交前code review在行業中目前是比較常見的,其中程式碼必須在推送之前就進行審查和修改。這樣的話,即使是每一輪審查只需要2天的時間,這樣重複2-3輪,也會耽誤開發人員一週的時間,這是嚴重的時間浪費。

在Quora,我們主要做的是提交後code review。也就是說,程式碼首先進入生產階段,然後再找人進行code review。為了讓你對這一工作的規模有個認識,舉個例子,昨天我們48個人總共推送了187次程式碼。提交後review是很好的辦法,因為它解放了開發人員,可以去做其他的工作。code review人員也可以更好的管理自己的時間,可以在他們方便的時候做程式碼審查,而不是非得匆忙的完成這一工作,以便不影響其他人的工作。過程合理的話,預計程式碼審查一週內可以完成,而實際上大部分情況下,一到兩天就可以完成。之所以說一週的時間,也是經過仔細考慮的,因為一週足夠長,可以允許審查人員靈活安排,而一週的時間又有點短,因為它需要將不好結果的影響最小化,比如說有人讀了程式碼之後將不好的程式碼在整個程式碼庫中進行傳播。實際還有這樣一層原因,很多的開發人員都是按周進行個人工作安排的。

我們之所以可以做提交後code review,是因為我們相信我們的每一位開發人員,我們選擇的都是最優秀的人才,並且我們在他們的程式碼質量培訓/工具方面投入了大量資金。這也督促我們對程式碼進行更好的測試,而好的測試能幫助檢測程式碼的準確性,甚至在任何code review工作開始之前就可以檢測。對此,我們採用的是Phabricator,一個穩健的、高配置的審查工具。我們對它做了幾點改進,這樣就能與我們的提交後code review工作流更好的配合。比如說,我們構建了一個命令列工具,可以將程式碼推向生產階段,並提出程式碼審查請求。有了該工具,Phabricator的diff就可以繼續工作,而無需去修復那些commits。

所有這些說明,對於不同型別的程式碼問題,我們有不同的程式碼審查標準。如果說我們事先發現了某一個潛在錯誤,問題比較大,可能會帶來很大影響,那麼我們會轉而執行提交前審查,而不是平常的提交後審查。這方面的幾個例子:

1. 涉及使用者隱私和匿名的程式碼。

2. 涉及核心的抽象類(abstraction),因為很多其他的程式碼都可能依賴於它。

3. 涉及基礎設施的某些部分,其有些錯誤可能會導致當機的風險。

當然這還取決於開發人員如何決策——如果他們想要對某些程式碼進行提交前審查,從而從中獲取更多想法,這也是可以的,而不用非得采用平常的提交後code review,只不過這種情況比較少見。

code review任務分派

為了做好code review,每一個變更都應該由有著相關經驗的人員來審查。如果這些code review人員能負責“維護”這些他們審查過的程式碼的話那就更好了,這樣他們可以獲得一定物質報酬,進而形成長期的交易關係。

我們之前實施了一個簡單的系統,其模組和目錄層級程式碼的“所有權”只需要在檔案開頭賦予一個元標籤(meta tag)就可以指定了。比如:

__reviewer__ = ‘vanessa, kornel’

如果某commit要對檔案做一些修改,其reviewer就會根據檔案(或結構樹【ancestor tree】),進行分析,並自動作為reviewer新增到commit中。我們針對code review任務的分派或者說路由選擇還有一些其他的規則,比如說,如果沒有reviewer的話,一個資料科學家可自動作為一個reviewer新增到啟動A/B測試的commit中。實際上我們已經搭建了一些工具,可以讓我們更容易的制定更多這類自定義的“路由”規則。舉個例子,如果我們想做,那就很容易就能增加一個規則,可以將某一個新任務所有的commits都路由指派給相應的人員。

這樣的一個智慧系統,將所有的commits都分配給合適的人員進行程式碼審查,能有效減輕開發人員的負擔,否則他們還需要四處尋找合適的審查人員,還要確保大部分的審查人員否能認真審查每條commit。

測試

測試對我們的開發流程來說是非常重要的一步,我們寫了很多的單元測試、功能測試和UI測試程式碼,覆蓋很大的測試範圍。而一個綜合性測試可以讓開發人員並行工作,工作進行的更快,而不用擔心會破壞現有功能。我們投入了大量時間來制定測試框架(建立在nosetests之上),使其容易使用、可快速應用,從而降低編寫測試程式碼的間接費用。

我們還開發了幾項工具,可以實現測試的自動化。我們之前釋出了一篇關於“持續整合系統”的文章,描述了一箇中心伺服器,可以在部署任何新程式碼之前執行所有測試。該測試伺服器可以並行執行,這樣的話執行所有的測試只需要不到5分鐘的時間。這樣的快速運轉可以激勵人們經常性的編寫和執行測試程式。我們有一個叫做“test-local”的工具,只能識別和執行程式碼工作副本的測試變更相關的測試檔案。為了能做的更好,我們的測試還需要進行模組化(這能進一步幫我們在測試失敗的時候進行快速除錯)。為了確保好的測試程式碼能得到期望的效能,我們維護了一個共享文件,描述了編寫測試程式碼相關的最佳實踐。這些指導方針在程式碼審查過程中會被嚴格執行。

Quora是如何維持高質量程式碼的

同code review一樣,我們對不同型別的變更也有不同的測試標準,對測試範圍有更高的要求,尤其是變更成本很高的情況。

所有這些系統使得我們能夠從寫測試程式碼的過程中獲取最大收益,可以以很小的間接費用節省很多的開發週期。

程式碼質量指南

我們非常熱衷於共享程式碼質量標準的指南,它可以幫我們做很多事情:

① 作為新開發人員培訓的很好的工具。

② 與整個團隊分享每個人的智慧和最佳實踐。

③ 降低開發和程式碼審查過程的間接費用。比如說,我們討論一次就可以得出一些有價值的結論的話,那麼再在每次code review時討論程式碼行應該是80個字串還是100個字串就完全沒有意義了。

Quora是如何維持高質量程式碼的

除了針對不同語言的語法風格指南,我們還有一些針對抽象事務的指導,比如如何寫一個好的測試用例,或者如何做出更好的模組架構,從而提高工作效率。這些指南不是一成不變的,是隨著我們對不同的過程有了更深的理解而不斷髮展的。我們還有很多重構的工具(一些開源工具比如codemod,還有一些內部開發的工具),以備我們改變指南的時候需要重新“修改”所有的歷史程式碼。

舊程式碼的清理

一個快速發展的團隊會嘗試很多不同的工具,當然其中不乏有一些可以用而有一些不可用。到最後,任何一家快速發展的公司,隨著時間的推移其程式碼庫都會開發很多多餘的東西,而這些可能永遠都不會再用,放在那裡只會使很多東西變得混亂。清理這些廢棄程式碼可以使得程式碼庫更健康的執行,還能提升開發的速度。

Quora是如何維持高質量程式碼的

我們會定期組織“清理周”,對這些多餘程式碼進行清理。在清理周內,某一些團隊或者有時會發動整個公司專門來清理舊程式碼。這種定期的清理模式可以降低在“正常工作”和“清理工作”之間來回轉換的成本,還能使得清理工作更加社交化,更加有樂趣。

程式碼庫有些部分會比其他部分相對更好清理一些。同樣地,清理程式碼庫的不同部分會對開發速度產生不同的影響。為了能更好的利用好舊程式碼清理的時間,我們在考慮成本的基礎上優化了相關模組,來進行清理工作,以及衡量清理工作對未來開發速度的影響。

Linting

我們很可能會低估在某一例項中不遵循上述程式碼質量指南(比如說docstring的格式或程式碼行長度)所造成的成本,但事實上成本的確是增加了。同時,要記住各種各樣的規則並應用,也是一件很煩的事情,尤其是規則還在不斷增加。最終造成的結果就是,很多人都選擇不再遵循這些指導方針。

我們開發了一個內部的linter,叫做qlint,可以相應減輕一些上述困擾。qlint是一個很智慧的linter,可以讀懂文字結構以及AST,是基於flake8和pylint的。它旨在使得未來增加自定義的lint規則變得更容易。比如說,我們其中一個做法是Python中任何一個“私有”變數都需要進行突出強調,所以我們在qlint中新增了一個規則,那就是可以檢測出這類私有變數中任何不合理的地方。

我們還將qlint與很多系統整合起來,提供無縫的開發環境,這樣人們就不必親自盯著lint錯誤。對於新手來說,qlint是一個與我們常用的文字編輯器——Vim,Emacs和Sublime——充分整合的工具,可以在規則被破壞的情況下提供可視的反饋(紅色標記)。它還與我們的推送流程整合在了一起,任何人在進行code推送的時候都可以執行qlint(以互動的方式)。而實際上,如果某項規則被commit打破,它有可能就會影響到部署工作。我們還將我們的風格指南與qlint整合起來,這樣對每一個lint錯誤,qlint都可以給出一個超連結,指向相關風格指導的相應規則。我們的Phabricator也可以用qlint。通過這種方式,所有被qlint找到的錯誤都被明顯的標記出來,這樣他們的程式碼審查工作就變得相當容易了。

Quora是如何維持高質量程式碼的

所有這些都幫我們實現了以很小的成本就能提升程式碼的一致性和質量。

結論

就像文章中所說的那樣, Quora對程式碼的重視程度是很高的。我們講求務實主義,搭建了很多好的系統、工具和流程,能幫我們增強並維護長期開發的生產效率。今天我們努力維持一個良好的平衡,我們的團隊也在不斷成長和發展,所以我們相信,未來我們還將生產更多的工具和系統。

如果你想幫我們搭建這樣的系統,或者想成為我們強大開發團隊的一份子,幫助我們以這樣一種務實的、深刻的方式提升程式碼質量,那麼歡迎加入我們。

相關文章