Quora 是如何做到高質量的快速開發的?

夏殤是隻溫柔的鮟鱇鵝發表於2015-08-03

高質量的程式碼庫可以促進開發速度,對產品迭代、開發協作和維護都大有裨益。不過維護程式碼質量也同時會帶來許多額外開支,並拖慢開發程式。所以很多人認為,要麼忽略程式碼質量快速開發,要麼在維護程式碼質量的同時接受緩慢的開發速度。本文讓大家看看 Quora 是如何做到兩全其美的實踐。


從長遠來說,高質量的程式碼庫會促進開發速度,因為它會使軟體的迭代、協作和維護都更為簡單。在Quora,我們認真而嚴謹地對待程式碼質量。

但在這些益處之外,維護程式碼質量也同時會帶來許多額外開支,並拖慢開發程式。很多人都覺得在這兩者之間抉擇非常困難,因為他們都認為這是個非此即彼的選擇:要麼忽略程式碼質量快速開發,要麼在維護程式碼質量的同時接受緩慢的開發速度。由於快速的產品迭代對於初創企業最為重要,因此很多人都認為初創者一定只能生產低質量程式碼。

然而,我們利用設計優化開發工具和開發流程的方法,解決了這個兩難的問題。這些優化使我們能進行高質量的快速開發。在這篇文章裡,我們將會介紹一些讓我們達到兩全其美的,維持程式碼質量的方法和實踐。

維護程式碼質量的目的

The main benefit of maintaining high code quality is the long-term boost to development speed, so this is what we optimize for and focus on. We just need to trade-off those long-term gains with the short-term costs of writing cleaner code upfront.

維護程式碼質量的最大好處就是它會大大增進長期開發的速度。這是我們進行所有優化的目標和重點。我們只是要提前處理好這些長期效用,和它所帶來的短期增加的工作量的關係。

明確了目標之後,這是我們認為最有用的,關於程式碼質量的四項原則:

Quora 的程式碼質量四項原則

1. 閱讀和理解程式碼應該是簡單的 —— 開發者讀程式碼的時間遠遠大於寫程式碼的時間。因此我們應該儘量使程式碼簡潔易懂,儘管寫出那樣的程式碼可能會需要比原來更長的時間。

2. 不同部分的程式碼應該有不同的質量要求 —— 不同部分的程式碼對於長期開發帶來的影響是不一樣的,因為它們有不同的生命週期、影響範圍、被破壞的可能性、破壞後帶來的後果大小,以及debug的難度。總體來說,不同的程式碼對於產品迭代的速度的影響是不同的,因此對所有程式碼都一視同仁顯然不是最佳選擇。

3. 維護程式碼質量的成本是可以減少的 —— 開發自動化,更易用的開發工具,更好的流程,以及更好的開發者都能夠減少維護程式碼質量的成本。

4. 整個程式碼庫應有一致性 —— 整個程式碼庫的一致性是很重要的,儘管這意味著某些區域性程式碼可能並沒有用最佳的方法去寫。一個缺乏一致性的程式碼庫會使閱讀,理解(參加第1點),後續功能新增,和使用自動化工具來改進程式碼都更加困難。

接下來,我將介紹一些在開發過程中 Quora 遵循著四項原則的具體方法。

程式碼提交後的互相審查(Code Review)

如果程式碼庫中有程式碼變動,我們會從 6 方面來同行審查 —— 正確性(correctness)、程式碼封裝(privacy)、 效能(performance)、架構(architecture)、可重用性(reusability)、程式碼風格(style)。閱讀程式碼是程式碼審查中不可或缺的一環,因此實施程式碼審查制度對於增加程式碼可讀性也無疑是有利的。

但不幸的是,程式碼審查也會拖慢開發程式。例如程式碼提交前的互相審查這個業界常規,程式碼在被提交到程式碼庫之前必須由同伴審閱並由作者完成改進。每輪審查可能會持續兩天,然後常常有兩到三輪審閱,這意味著程式碼作者會經常將浪費大半個星期在程式碼審閱的工作上。

在 Quora,我們並不進行程式碼提交前的審查。即,程式碼會先上線,然後才由某些同事來進行審閱。為了讓你對我們的工作規模有個概念,昨天我們有48個開發者總共進行了 187 次程式碼提交(commit)。我們認為程式碼後審閱是一項很好的舉措,因為它讓開發者們從不必要的程式碼審閱的牢籠中解放出來,可以先去完成其它的工作。這樣,審閱者們也可以挑他們方便的時候來閱讀這些程式碼,以免被別人催促著完成這項工作。我們的流程希望我們在一週內完成程式碼審閱工作就可以了,但我們大多數都會在  1  至  2  天內進行審閱。這個“一週”的長度是在仔細討論後定下的 —— 它既長到足夠審閱者們自由安排審閱時間,也短到可以及時阻止低質量程式碼可能帶來的,被其他人閱讀並使用的後果。實際操作中,我們也考慮到了許多開發者會有一個以“星期”為週期而安排的工作時間表。

我們能實施程式碼提交後審閱這項舉措,也是因為我們對 Quora 的每個開發者都加以信任,畢竟我們只僱傭最好的開發者,也給予了他們最好的工具和最用心的培訓。這也促使我們寫下一些很好的測試來達到很高的程式碼覆蓋率,這讓我們在任何程式碼審閱之前都可以自己檢閱程式碼的正確性。除此以外,我們使用 Phabricator 這個非常棒,配置自由度也相當高的程式碼審查工具。我們對 Phabricator 這個工具做了一些修改,讓它能更好的為我們提交後審查的流程服務。例如,我們寫了一個命令列工具來幫助大家上線程式碼並要求審閱。這個工具會讓開發者在不對之前的提交進行任何修改的同時讓 Phabricator 的 diff 能夠正確執行。

說了這麼多,我們對不同種類的改動有著不同的審閱要求。如果新程式碼有可能會造成嚴重的後果,並且修復起來很難的話,我們會要求對它進行提交前審查,而不是常規的提交後審查。比如:

  • 1. 涉及與使用者隱私和匿名相關的程式碼;
  • 2. 涉及了與一個核心抽象類有關的程式碼,因為很多其它程式碼可能基於它;
  • 3. 涉及了可能會造成當機的底層程式碼;

提交前還是提交後要求審查,這也跟開發者的謹慎程度有關。如果任何開發者想要在提交程式碼前要求程式碼審查,以此來獲得一些建議或意見的話,他們完全有這麼做的自由 —— 儘管這很少發生。

把程式碼審閱發給正確的人

為了使程式碼審查進行得順利,新程式碼應該由對於這個改變有著充分的認知的人來審查。如果程式碼是由將會維護這些程式碼的人負責那就更好了,顯然他們將會有很充分的理由和動機,來使程式碼長期的可用性達到最佳。

我們寫了一個簡單的系統,讓開發者們可以簡單地表名模組/目錄級別的程式碼歸屬,它們只需要在檔案的開頭加一個元標籤就可以了。例如:

__reviewer__ = ‘vanessa, kornel’

如果有個提交(commit)會改變某個檔案的話,這個系統會讀取這個標籤,這個標籤裡標明的開發者會被自動新增到這個 commit 的審閱者名單裡。除此以外,我們也有其它的關於程式碼審閱者的規則,例如對於所有設定了 A/B 測試的 commit,都會有一個資料分析師——如果原來沒有的話——被自動加到審閱名單裡。事實上,我們的工具也可以很簡單地新增其它的關於程式碼審閱人的規則。例如,如果我們想的話,可以把新僱員的所有 commit 都傳送給他們的導師。

有一個智慧的審閱分發系統,把程式碼審閱傳送給正確的人,減少了開發者們尋找程式碼審閱者的煩惱,並確保能找到最適合的人來審閱每一份程式碼。

測試

測試是開發流程中很重要的一環。我們寫了很多單元測試、功能測試、UI 測試來達到高程式碼覆蓋率。有一個完整的單元測試,可以讓開發者快速平行地完成新程式碼,而不必擔心破壞掉已有的功能。我們花了很多時間來製作我們的測試框架(基於 nosetests ),目標是簡單、快速、易用,使寫測試的工作量儘可能低。

我們也開發了許多自動化測試的工具。正如之前一篇探討我們持續部署系統的文章所講的一樣,我們所有的程式碼在上線之前都在一箇中心伺服器上完成測試。這個測試伺服器有著高並行的特性,因此即使跑完我們所有的測試也只需要不到5分鐘。這麼快速的系統就是為了鼓勵開發者們儘可能多地寫測試和進行測試。我們有一個叫“本地測試(test-local)”的工具,來自動找到和執行和新增程式碼有關的測試。為了更好地使用這個工具,我們的測試必須要模組化(這也在一個測試失敗了的情況下,幫助開發者們快速找到bug並修復)。為了確保這個目的和一些其它的關於測試程式碼的重要性質,我們有一份描述測試程式碼書寫規範的共享檔案。這些原則在程式碼審閱時被嚴格執行。

和程式碼審閱類似,我們對於不同種類的改動有不同的測試標準。如果新改動有可能帶來嚴重後果和修復成本的話,我們會要求更高的程式碼覆蓋率。

所有的這些加在一起,使我們寫測試的意義最大化,以此減少長期的開發成本。

程式碼質量指導

我們非常熱衷於共享程式碼質量指導,這幫助我們:

  • 1. 更好地培訓新來的開發者;
  • 2. 更好地在整個團隊裡共享我們的經驗和智慧;
  • 3. 設立共同的標準來提高程式碼庫的一致性;
  • 4. 減少開發和審閱環節的工作量。例如,在每個審閱中都討論一下每行程式碼是80個還是100個字元是沒意義的。我們可以就討論這個問題一次,然後在所有今後的程式碼中都使用這個標準。

除了每種語言自身的語句規範之外,我們也有更抽象的一些程式碼規範,例如關於如何寫出好的測試或是如何架構程式碼模組,來幫助減少程式碼的閱讀時間。這些規範並不是一成不變的。在我們逐漸對各種權衡有了更深的理解的過程中,我們也會改變這些規範來達到利益最大化。我們也有大型程式碼重構的工具(部分是諸如例如 codemod 之類的開源工具,其它的是我們自己開發的),以在我們改變了某項規範之後回過頭去重構所有的舊程式碼。

整頓舊程式碼

一個快速前進的團隊會嘗試很多新事物,自然而然,它們之中某些很好,而某些則不盡如人意。因此,一個快速前進的公司的程式碼庫裡肯定會有很多沉澱下來的糟粕,即那些實際上大家不再使用,卻留在那裡使許多事情變得更復雜的程式碼。清楚這些糟粕,保持程式碼庫的簡潔,也會提高開發速度。

我們定期組織“整頓周(Cleanup weeks)”來清除這些糟粕。在這些整頓周裡,一些指定團隊——有時也會是整個公司——把所有的時間都花在清楚那些不用的舊程式碼上。這些定期活動減少了大家在“常規工作”和“整頓工作”中切換所需花費的時間和精力,也讓整頓舊程式碼變得更為有趣,帶來更多的社交價值。

部分程式碼比其它的更加容易整頓,當然也有部分程式碼,整頓起來會對開發速度有極大的影響。為了最好地利用大家整頓舊程式碼的時間,我們會基於清除它們所需耗費的時間,和清除後它們對開發速度帶來的影響,對各個程式碼模組的整頓優先順序進行排序。

程式碼查錯與優化(Linting)

人們很容易低估偶爾不遵循程式碼語言規範(例如程式碼註釋或每行程式碼的長度)的後果,但這些後果是會疊加起來的。確實,時時記得並遵循很多規範是很惱人的,特別是規範越來越多的時候。因此,我們並不驚訝,很多開發者並不準備遵循這些規範。

我們開發了一個公司內部程式碼查錯和優化的工具,叫做 qlint,來減少達成這一目標的工作量。Qlint 是基於 flake8 pylint開發的智慧小工具,能識別文字結構和抽象語法樹(AST)。這個工具讓我們未來往裡面新增新的程式碼規則變得很容易。例如,我們規定在Python裡所有private的變數都必須在變數名前加下劃線,因而我們在 qlint 里加了這一條規則來查詢所有不符合這一規範的程式碼。

我們把 qlint 整合進了許多其它開發工具,因此開發者們不必特別來關注 qlint 指出的各項問題。對於剛剛起步的開發者們,我們把 qlint 整合進了最流行的一些編輯器,例如 Vim、Emacs 和 Sublime,並在開發者違反了程式碼規範的時候提供視覺化的提示。Qlint 也整合在了提交程式碼的流程裡,並在任何人想要提交程式碼的時候以一種互動化的方式執行。事實上,取決於 commit 具體違反了哪條規則,這個工具甚至可能阻止程式碼部署。我們也把我們的程式碼規範文件整合在了這個工具裡,因此開發者每違反一條規則,qlint 都會給出一個連結指向文件裡的那個規則條目。我們的 Phabricator 也被配置成使用qlint。這樣,由於所有的錯誤都由 qlint 以視覺化的方式指出了,程式碼審閱變得更為簡單。

所有這些改進都提高了我們程式碼庫的一致性,並使我們能夠以最小的成本提高程式碼質量。

總結

就像這篇文章中指出的那樣,Quora 十分嚴謹認真地對待程式碼質量。我們對待這個問題很腳踏實地。我們設計並開發了各種工具、系統和流程,來保持並增強我們長期的開發效率。在我們此時達到良好平衡的同時,我們的團隊也在不斷地擴大和成長,因此我們自信地認為,未來還將開發出更多地工具和系統。

如果你想要幫我們來開發這些工具,或者只是想成為這個超級棒的,認真而腳踏實地地對待程式碼質量的團隊中的一員,請加入我們吧

相關文章