前言

我很喜歡 Java 大師 Joshua Bloch 在《編程的頂尖對話》所說的:

即使是想把一個很小的程式寫對也是非常難的。

認為自己程式沒有 bug 就是在愚弄自己。程式肯定有 bug,(只是)多數情況下,程式裡的 bug 夠少,足以使其完成任務。

是的,100% 正確的程式或許很難達成,或許連定義起來都很困難,但我們還是得努力趨近它,讓我們工作更有成就感,更有信心能夠交付價值,也更少因不慎流出去的瑕疵而疲於奔命。

Joshua Bloch 緊接著又說:

既然寫正確的程式那麼困難,我們就應該盡力取得幫助。所以,能減少 bug 的所有東西都是好的。這就是我是靜態型別和靜態分析的信徒的原因。

是的,我們需要動員許多力量,取得許多幫助,才能夠趨近於正確的程式。可動員的許多力量當中,無疑的 code review 是重要的基礎,可惜的是,許多團隊連這基礎都還有諸多可改善之處,這也限縮了團隊能進一步發揮的價值。

Code review 乍看之下是小事,但若認真探討起來還真是不小。在我多年輔導各種成熟度團隊的經驗中,有六個基於 code review 以及衍生出來的議題可深入探討:

  1. 基本信念:品質來自正確地做事
  2. 掌握基本功與領域知識
  3. 凝聚團隊公約的共識
  4. 嘗試更好的協同理解技巧
  5. 讓進步看得見
  6. 支持長久研發的措施

如果你所在的團隊已經啟動 Scrum 或 Kanban 研發流程,恭喜你,你們已經有很好的流程基礎可在這六大議題上面精進,只需要正確實施(玩真的!)你們所採用的敏捷流程。文中我會大量連結到 Scrum 及 Kanban 的要素。請確實「守」你們該守的流程,不要驟然「破」或「離」。

全文很長,如果你是現役的程式設計師,可先讀①②以瞭解 engineering disciplines;如果你是 team lead,可先讀③④以瞭解團隊動力;如果你是管理層,可先讀⑤⑥,尤其是⑤關於管理面的施力點。

① 基本信念:品質來自正確地做事

Code review,甚至任何一種 review,都源自一個單純的理由:多一個人來確保品質。

品質是計畫、設計和內建而來的,不是靠檢驗出來的。換言之,品質來自正確地做事。

怎樣才叫做「正確地做事」?

談「正確地做事」之前,先確定我們擁有共同的基本信念。

Collective ownership

Odd-e 三位專家在 2023-08-31 晚上有一場很精彩的 webinar【高效 Scrum 團隊所需的能力】。在 1h:03m:36s 聊到「工程能力」時,有一句語重心長的話:「所有跟 eXtreme Programming 相關的工程能力都很重要。」

eXtreme Programming (XP) 第一版與第二版提到許多工程實踐,此刻我想特別點出 “collective ownership” 這一條:

Collective Ownership encourages everyone to contribute new ideas to all segments of the project. No one person becomes a bottleneck for changes.

In practice collective ownership is actually more reliable than putting a single person in charge of watching specific classes. Especially since a person may leave the project at any time.

積極面來說,collective ownership 可善用集體智慧提升設計品質,可促成知識共享,消極面則可降低公車指數 (bus factor)。 1

萬一連 “collective ownership” 都不是團隊想要追求的境界,或許這個團隊從一開始的組成階段 (team forming) 就有問題了;我建議,解決了之後,再談以下的議題吧。否則,連「共同持有」的心態都沒有,還有什麼動機要去 review 別人的 code 呢?

Four-eyes principle

Code review 的出發點很單純:多一個人來確保品質,也就是說「不要只靠自己的兩隻眼睛,要再加上別人的兩隻眼睛,總共要用四隻眼睛來確保品質。」

Four-eyes principle 雖然既古老又原始,但管用,因此就連聯合國歐盟這麼先進的國際性組織都仍在使用。

再引述一次 Joshua Block 的話:「既然寫正確的程式那麼困難,我們就應該盡力取得幫助。」

就算我們不想時時刻刻都像 XP 主張的 pair programming(甚至更激進的 mob programming),但至少在關鍵時刻,尤其是在 design review 及 code review 的時刻,就該好好善用這種原始方式取得幫助。

每當有「現在可不可以暫時跳過 four eyes?」的衝動,或許該換個角度思考「現在有沒有方法讓 four eyes 更有效率且不犧牲品質?」

Working backwards

「逆向工作法」或「以終為始」是很有威力的心法。用在敏捷開發上,就是熟知的 user story “INVEST” 原則當中的 V (Valuable) 及 T (Testable):

  • Valuable - 品質的起頭:這個 user story 是為了交付什麼價值?是為了誰?

  • Testable - 品質的末端:該怎麼確定這個 user story 已經做完了 (done)?該怎麼驗收?

雖然前面曾提到「品質不是靠檢驗出來的」,但若能在一開頭就設想末端的檢驗標準,甚至設計出可讓將來更容易確實檢驗的結構,就是「內建品質」的精神——這就是敏捷實踐者常講的 “acceptance criteria” 及 “test-driven/behavior-driven design” 的用意。

Code review 就是讓 four eyes 共同檢視 Valuable 及 Testable 的好時機,喔,不,甚至早在 Sprint Planning 或 Refinement 時就應該來 review 了——現在不是流行凡事都要 “shift left” 嗎?

品質不是白吃的午餐

品質是計畫、設計和內建而來的。

計畫與設計出內建品質,是需要時間、精力、技能的。孕育出 collective ownership、four eyes、working backwards 的意識及紀律,也是需要時間、精力、技能的。

畢竟品質不是白吃的午餐。

在推銷這些進步理念時,讓團隊想像藉由「追求高品質」將會帶來的效益,或是以具體事證呈現因為「漠視低品質」而正在且將會持續深陷的痛苦泥淖,讓團隊做出選擇,願意投資時間、精力、技能在品質改進上。

你能舉出「不好好進行 code review」最終會引發的災難嗎?這樣的災難是大家願意承擔的嗎?用這個來鞏固大家願意投資在品質上面的動機吧。

② 掌握基本功與領域知識

不可諱言,code review 要做得好,不是大聲嚷嚷精神喊話就會自動美夢成真,是需要自我提升能力的,尤其是研發基本功與領域知識。

不過,以下提到的這些能力,就算不談 code review,也是工程師本來就該自我提升的。

所以,結論是:請把它們當成是必修課吧,這會讓你成為更稱職的軟體研發工程師。

Clean code

Turing Award 得主 C.A.R. Hoare 有一段經典名言:

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.

單純到「顯然沒有缺陷」 vs. 複雜到「沒有明顯的缺陷」,你願意選擇哪一種?

寫出 clean code,可說是程式設計師的職業道德。坊間早有 clean code 及 refactoring 相關的經典書籍,網路上也有一大堆相關文章,請花點時間自我充實吧。

Guideline

除了 clean code 技能之外,我們還需要一些讓程式變得更好的 code review 指導方針。

讓我們向 Google 取經吧 2。Google 在 “What to look for in a code review” 一文建議了一些 review 的重點:

  • Design
  • Functionality
  • Complexity
  • Tests
  • Naming
  • Comments
  • Style
  • Consistency
  • Documentation
  • Every line
  • Context
  • Good things

軟體測試實務:業界成功案例與高效實踐(第一冊)》第四章也建議了一些 code review 的重點:

  • 是否有明顯的邏輯錯誤?
  • 有無遵循既有的程式碼編寫規範?
  • 邏輯的測試保護足夠嗎?
  • 有完整實現需求嗎?

如果是在 DevOps 合一的團隊,甚至還會 review 一些 design for observability 的要點。以上都很值得放在團隊的 Definition of Done 或 PR/MR template。

注意事項洋洋灑灑,每當有「現在可不可以暫時跳過?」的衝動時,或許該換個角度自問:「此刻若不好好 review 這些地方,我能承擔抄捷徑的後果嗎?」

Small batch

敏捷陣營推崇小批量開發:小批量的東西容易估算,容易寫,容易理解,容易測試,容易部署,容易取得回饋,容易歸因。

DevOps 陣營也推崇小批量。Google 在 “DevOps process: Working in small batches” 一文說道:

Working in small batches is one of a set of capabilities that drive higher software delivery and organizational performance. These capabilities were discovered by the DORA State of DevOps research program, an independent, academically rigorous investigation into the practices and capabilities that drive high performance.

如果不是因為技術問題無法小批量提交 PR/MR,而是因為上游的 backlog item 就是如此大,或許團隊可以嘗試在 Sprint Planning 或 Refinement 時套用 SPIDR 法來拆解過大的 backlog item。

Domain knowledge

如果身處教育資源不那麼完備的團隊,想要養成領域知識,得自助加上人助:

  • 自助:盡量回到學生時期的好學心態,並盡量掌握有效率的學習方法。尤其是近年來流行的卡片盒筆記法,很值得一試。

  • 人助:尋求協助,並設法將學到的東西反饋到團隊的公共資產:文件、簡報、錄影、自動化測試,造福你的左右鄰兵,也造福後人。

Context

即使有了領域知識,但論及理解需求,又是另外一回事了。

理解需求,除了老生常談的敏感度、傾聽與提問技巧之外,敏捷圈發展出來的 Specification by Example 技術,很值得大家嘗試。

而且,既然都已經口頭問出來這些實例了,何妨順便用驗收測試予以固化?

Config

現代軟體不能只顧好核心業務邏輯,還得處理全球化、客製化、混合雲、多租戶等外圍需求,要能透過 feature flag 動態即時切換,甚至還要能讓用戶自助服務。

組態管理比以往重要許多,複雜度不亞於程式碼管理,有時甚至超過。

程式碼的管理,有 git flow、GitHub flow、GitLab flow 等流行的作法,那麼,組態管理呢?

對於 legacy 系統,盤點既有組態,給予合適的現狀描述,至少是第一步。考古之餘,別忘了反饋到團隊的公共資產上。

③ 凝聚團隊公約的共識

Code review 是兩個人之間的活動,一個是 reviewer,另一個是 reviewee。喔,其實更常見的情況是,reviewer 要寫成複數 “reviewers”。

Code review 不只是兩個人之間的活動,更是團隊的活動。

Marcus Buckingham 在〈隱藏版團隊力量大 (Power of Hidden Teams)〉一文提到團隊的力量 3

大多數工作實際上都是團隊進行的工作。團隊是你工作經驗的現實。你的責任似乎連結到其他人的責任;你的長處似乎能與其他人的長處互補;你的左右都有人幫你留意,讓你保持自信,對你的工作給予回應,分享你對「好」的想法,在你看起來快要承受不住時伸出援手,並在你陷入困境時給予意見。這種團隊體驗的品質,正是你工作體驗的品質。

你的團隊體驗會促進很多事情:你在工作時的生產力;你在工作時是否感到快樂;你有多少創意、創新和復原力;以及你選擇留在公司多久。換句話說,在你的工作上,優秀的團隊和團隊合作不是「有了也不錯」的事物,而是必須要擁有。

現代軟體研發是一個群體的事業, 有人就有江湖 有人就有協作的議題。Code review 既然是團隊的活動,免不了也會有 江湖的議題 協作的議題。

若你的團隊已經啟動 Scrum,以下都是很值得在 retrospective 充分探討的好議題,讓團隊公約成為團隊協作的助力。

過與不及的拿捏

Code review 的尺度,該鬆還是該緊?

「太鬆」當然並非我們樂見,但「太緊」呢?

如果團隊對於鬆與緊的尺度難以達到共識,不妨聽聽看 Google 怎麼說。

Google 在 “What to look for in a code review” 一文提出他們認為最起碼的品質標準是:

Don’t accept CLs (change lists) that degrade the code health of the system.

在業界,我們常暱稱它是「童子軍規則」(The Boy Scout Rule):「離開營地前,讓營地比使用前更加乾淨。Always leave the campground cleaner than you found it.」

至於尺度太緊的問題,Google 在 “The Standard of code review” 一文提出他們的看法:

In general, reviewers should favor approving a CL once it is in a state where it definitely improves the overall code health of the system being worked on, even if the CL isn’t perfect. That is the senior principle among all of the code review guidelines.

儘管 Google 是菁英薈萃之地,但關於這一點倒是滿務實的,不會追求完美主義。

Cadence

很多人怕 code review 會打斷別人的工作節奏,也怕自己的節奏被打斷。因此我常常看到看板上面擠壓許多有待 review 的 PR/MR,我輔導過的團隊,也幾乎都會在 retrospective 提出這項熱門議題。

聽聽看 Google 怎麼說。"Speed of Code Reviews" 一文主張:

If you are not in the middle of a focused task, you should do a code review shortly after it comes in. One business day is the maximum time it should take to respond to a code review request.

可見 Google 很務實地將焦點放在 flow 上面:若無特殊理由,要以團隊目標為重。

軟體測試實務:業界成功案例與高效實踐(第一冊)》第四章則提出團隊一起 code review 的常見做法,以避免個別人士零零星星被打斷:

除了找另一個團隊成員協助 code review 外,在多數團隊成員不熟悉相關程式碼時,我們也會有 mob code review,去加強知識擴散的效果。

鑑於此種需要專心投入的特質,有些團隊會直接定時進行 mob code review(例如:每日 daily meeting 之後或是下班之前),以避免當前工作被打斷。

大家可以參考這些業界常見的做法,改善自己團隊的 code review 節奏。

Process

敏捷路線不太鼓勵過多 top-down 的流程約束,更希望團隊基於經驗主義自行湧現出合適的流程。我鼓勵團隊挑選感興趣的部份實踐看看,並在 retrospective 充分反思。

如果你是走 Scrum 路線,請善用 Definition of Done 作為品質共識。像《軟體測試實務:業界成功案例與高效實踐(第一冊)》第四章就提到:

有些團隊甚至在他們的實體或 Jira 的 Scrum board 上,將 code review 強制設為必需通過的一個階段,未 code review 過的任務就不算完成。

列入 Definition of Done 還有一個好處:code review 就不再是隱藏成本,而是正大光明的 task,需要被規劃,需要被估點,也需要認真投入。

如果你是走 Kanban 路線,可考慮新增一個名叫 “code review” 的 column,並設下 WIP 及 policy。接下來,你會驚訝地發現 code review 的 flow 是多麼順暢⋯⋯或是不順暢。請善用 Kanban 視覺化的威力。

④ 嘗試更好的協同理解技巧

在理想世界中,我們程式要實作的任務很單純,所需的背景知識大家都有,程式碼寫得清晰易懂,PR/MR 也充分交代 reviewer 需注意的事項。這種情況下,reviewer 或許只需要動動眼、動動滑鼠、動動腦,就能善盡 code review 責任。

理想世界不常發生,是吧?

真實世界中,要實作的任務不那麼單純,所需的背景知識不見得大家都有,程式碼埋藏一些神祕邏輯,如果我們繼續仰賴低頻寬的程式碼溝通方式,會讓 reviewer 只能根據眼前所見的浮面資訊進行浮面的 review,最終只能算是表面工夫的 review,輸出低品質的程式。

因此,整個團隊都需要學習更好的協同理解技巧:放大溝通頻寬,尋求深度資訊。

放大溝通頻寬

一對一的溝通頻寬太低了,有必要的話,集體一起 code review 吧。

程式碼的溝通頻寬太低了,大家可以善用嘴巴,善用白板、螢幕(如果你們坐在附近)、投影機(如果需要多人一起看)、遠距會議軟體(如果是異地辦公的團隊)。

軟體測試實務:業界成功案例與高效實踐(第一冊)》第四章就介紹過這種大絕招:

除了找另一個團隊成員協助 code review 外,在多數團隊成員不熟悉相關程式碼時,我們也會有 mob code review,去加強知識擴散的效果。

如果連這種大絕招都無效,或許團隊成員彼此落差實在太大,需要長期治療。

尋求深度資訊

避免 reviewer 只能根據浮面資訊進行浮面的 review,團隊需要提供更多深度資訊以協助 reviewer。

我從 “GitHub pull request template” 摘錄一些有助於 code review 的資訊,可做為大家的出發點:

  • Ticket number and link
  • How has this been tested?
    • Please describe the tests that you ran to verify your design. Provide instructions so we can reproduce.
    • Please also list any relevant details for your test configuration.
  • Checklist:
    • I have commented my code, particularly in hard-to-understand areas
    • I have made corresponding changes to the documentation
    • I have added tests that prove my fix is effective or that my feature works
    • New and existing unit tests pass locally with my changes
  • Screenshots (if appropriate)

我還曾經身處於有這種內規的團隊:「沒看到 unit test 的報表,就不予以 code review。」很硬!但那卻是我非常享受程式設計的一段經歷。

⑤ 讓進步看得見

Code review 的目的,是善用集體智慧提升設計品質,促成知識共享,讓我們工作更有成就感,更有信心能夠交付價值,也更少因不慎流出去的瑕疵而疲於奔命。

直接的效益是:提升品質,降低風險,間接的效益是:提升團隊的長期生產力,進而縮短上市時間,提升客戶滿意度。

這些效益都是偏向企業內部流程的構面,甚至是學習與成長的構面,偏向落後指標,且都不容易直接衡量。若以系統思考角度來說,這些效益有很明顯的滯延現象,短期內甚至還可能不利,急功近利的人,很容易因此落入「捨本逐末」(shifting the burden) 及「目標侵蝕」(eroding goals) 的陷阱。

認清根本解具有滯延現象,且選擇正面對決之後,才能接下去探討如何衡量成效。

這篇從 code review 出發的文章,一路看到這裡,大家應該很清楚,對於提升品質、降低風險、提升團隊生產力⋯⋯這些我們想要的成效,code review 只是手法之一,單獨只有這一個手法是無益的,勢必得搭配衍生的配套措施。因此,code review 的成效也不宜單獨看待,需連同其他配套措施一起衡量才比較實際。

不同的團隊,成熟度各異,管理者需要正視不同的現況,擬定不同的改進措施、衡量方式及目標狀態,並且要分階段。

譬如說,以領先指標來看:

  • 推動 collective ownership 的同時,可嘗試衡量 Scrum 五大活動參與度、PR 參與度、Sprint Backlog Items 分工多元性。

  • 推動 working backwards 的同時,可嘗試衡量 Sprint Backlog Items 的 acceptance test 完備度、design for observability 完備度、test 涵蓋率。

  • 推動 clean code 的同時,可嘗試衡量原始碼品質指標,並搭載於 CI 系統。

以落後指標來看:

  • 關注 flow cadence 的同時,可嘗試衡量看板的 cumulative flow diagram。

  • 關注最終交付品質的同時,可嘗試衡量 change failure rate——這正好是 DORA (DevOps Research and Assessment) 四大指標其中之一。4

最後,即使我們已經盡其所能把關品質,但很不幸 defect 仍然流了出去造成影響,亡羊補牢之餘,我們也需要關注 regression test 是否落實:

  • 針對已流出去造成影響的 defect,可嘗試衡量 regression test 涵蓋率。

⑥ 支持長久研發的措施

個人具備正確的品質基本信念,掌握了基本功與領域知識,團隊擁有公約共識,也掌握了更好的協同理解技巧,接下來就是這個大哉問了:周圍的環境,是否可以促成 code review 及相關的關鍵行為?

品質不是白吃的午餐,計畫與設計出內建品質,是需要時間、精力、技能的。

如果整個團隊已經開始嘗試用更好的方法進行 code review 及相關活動,但仍然受阻於事情太多、時間太少,或許這就要回到 Product Backlog Items 的基本問題了。請團隊試著問問自己幾個問題:

  • 即使可能有複數個上游來源,但團隊是否最後只會有一份透明化的 Product Backlog?
  • 團隊是認真看待 Product Backlog 的嗎?
  • PO 是根據什麼原則管理 Product Backlog 的?
  • 團隊 Sprint Planning 及 Refinement 是如何進行的?
  • 品質、flow 相關議題在 Retrospective 裡面是如何被討論的?
  • Retrospective 是玩真的嗎?

思考過一輪,應該可以更好的區隔出:究竟是外面的問題,還是自己的問題。針對前者,再去尋求協助。

想讓 PR/MR template 所要求的種種事項具體發揮效用,需要自動化環境的支持,尤其是與 CI/CD 高度連動的環境。

這是得持續投入的基礎建設,沒有捷徑。

Code review 及相關的配套措施,需要有一定的技術能力。

大規模敏捷框架 LeSS 在探討 “technical excellence” 時,有一段語重心長的話:

There is no magic to them, most people are able to learn them. However, most people are not aware of these after they graduated from university and hence, your organization will need to invest in these, usually via coaching.

簡單的說,自立自強固然可行,但若有好的前輩或教練親自帶領,將會事半功倍。

這就是團隊裡資深同仁的責任了。我期許你們能在③④提到的種種團隊協作活動中,以身作則,起示範作用。把整個團隊的水平提升到某個地步,有團隊整體戰力當你的後盾,也才能夠發揮更大的價值。

結語

品質改善具有滯延現象,需要正確思路與堅定決心,才能避免捨本逐末與目標侵蝕的陷阱。

本文探討從 code review 出發並衍生出來的六大議題,也建議一些可供個人、團隊、組織開始嘗試推動的事項。

提升品質,不必然意味著降低研發效率。讓我們持續學習與探索如何聰明地做事。


  1. 關於「公車指數」(bus factor),可參考以下兩篇文章:〈少一個差很多:算算團隊的公車指數〉、〈你的團隊「合作無間」嗎?嘗試用「公車指數」衡量現況吧!〉。 ↩︎

  2. 建議大家讀讀 Google 公開的 Google Engineering Practices Documentation,最近也有保哥的中譯版 。 ↩︎

  3. 如果你無法閱讀全文,也可收聽這一集 podcast:〈員工投入程度來自… 這個隱藏因素,你發現了嗎?〉 ↩︎

  4. 關於 DORA 四大指標,可參考 “Are you an Elite DevOps performer? Find out with the Four Key Project” 一文,更詳細的說明,請參考《Accelerate:精益軟體與 DevOps 背後的科學》一書。 ↩︎