Google 如何寫設計文檔

作者:Malte 是 Vercel 的 CTO。在此之前,Malte 是負責谷歌搜索渲染的首席工程師,以及 Search on Laptops, Tablets, 和 Desktop 的工程總監。

譯者,許曉斌,現任阿里巴巴資深技術專家,《Maven 實戰》作者。

Google 軟件工程文化中關鍵元素之一,是使用設計文檔來定義軟件設計。這些文檔通常不是非常正式,主要是軟件系統或應用程序的作者在着手寫代碼之前編寫的。這些設計文檔記錄了 high-level 的實現策略和關鍵的設計決策,而後者重點描述了決策過程中思考的權衡。

作爲軟件工程師,我們的工作並不是生產代碼本身,而是解決問題。非結構化的文本,例如以設計文檔的形式,在項目早期或許是解決問題更適宜的工具。因爲設計文檔可能更簡明、更容易被理解,相比代碼能在更高的層面溝通問題及解決方案。

除了用作軟件設計的原始文檔記錄,設計文檔還在軟件研發生命週期中實現了下述功能:

●在修改成本還比較低的時候,儘早地識別設計缺陷。

●在組織中圍繞設計達成共識。

●確保橫切關注點(Cross-cutting concern)得到充分考慮。

●在組織中傳播資深工程師的知識。

●就設計決策形成組織記憶基礎。

●成爲軟件設計者技術資產中的一個摘要性製品。

1. 設計文檔的構成

設計文檔是非正式的文檔,因此其內容不需要遵循嚴格的準則。因此首要原則就是:在特定的項目中,用任何最合理的形式編寫。

在此原則之外,Google 也建立了一種頗有效果的設計文檔結構。

1.1 上下文和範圍(Context and scope)

這一小節給讀者展現一個有關這個系統在哪裏被構建,以及什麼會被構建的非常粗的概覽。這不是需求文檔。保持言簡意賅!這裏的目標是讓讀者快速進入狀態,可以假設讀者知道一些前置的知識,相關詳情可以給到鏈接。這一節內容應該完全關注在客觀的背景事實。

1.2 目標和非目標(Goals and non-goals)

給出一個簡單的列表,講述系統的目標是什麼。有時候更重要的是講述非目標是什麼。注意,非目標不是對目標的否定,例如 “系統不應該 crash”,而是顯示地挑選出來的不是目標的內容。一個好的例子是 “遵循 ACID”,當設計一個數據庫的時候,你必然想要知道這是目標還是非目標。進一步的,你仍然可以選擇一個方案來實現非目標,只要它不會給實現目標帶來不必要的權衡。

1.3 實際設計(The actual design)

這一部分應該以一個概述開頭,然後逐漸展開細節。

  設計是文檔是在你設計軟件的過程中,記錄設計取捨的地方。應該關注這些取捨,以產出一個具備長期價值的文檔。具體就是,在既定的上下文(事實),目標和非目標(需求)下,設計文檔應該提出解決方案,並闡明爲什麼某個特定的解決方案是滿足這些目標的最佳方案。

相較於更爲正式媒體形式,編寫文檔的意義在於可以用合適的方式靈活地表述手頭的問題集合。因此,如何描述設計並沒有顯式的指引。

話雖如此,對於大多數設計文檔來說,一些最佳實踐和重複出現的主題還是有意義的:

1.3.1 系統上下文圖(System-context-diagram)

對於很多文檔來說,系統上下文圖是很有用的。這類圖展示了當前系統是更大技術圖景的一部分,能讓讀者在一個他們已經熟悉的上下文環境中去理解新的設計。

系統上下文圖的例子

1.3.2 APIs

如果設計的系統會暴露 API,那麼草擬出 API 通常是好想法。不過,在大多數情況,我們應該剋制住把正式接口和數據定義複製粘貼到文檔中的衝動,因爲這麼做會導致文檔過於冗長,包含不必要的細節,並很快過期。相對應的,我們應當關注和設計及取捨相關的那部分 API。

1.3.3 數據存儲(Data storage)

需要存儲系統的系統應該討論數據是如何以何種形式,如何被存儲的。和前面描述 API 的建議一樣,基於相同的理由,應該避免複製粘貼完整的 schema 定義,正確的做法是關注在那些和設計取捨相關的部分。

1.3.4 代碼和僞代碼(Code and pseudo-code)

設計文檔應當很少包含代碼或僞裝代碼,除非有一些情況需要描述新的算法。合理的做法是給到設計原型實現的鏈接。

1.3.5 約束條件(Degree of constraint)

軟件設計形態(因此設計文檔)的主要影響因素是解決方案空間(solution space)中的約束條件。

一個方向的極端情況是 “綠地軟件項目(greenfield)”,在這種情況下我們知道所有的目標,解決方案只要是合理的,沒什麼限制。這樣的文檔可能就會顯得很寬泛,但是也應該快速定義一組規則,以便讓大家儘快把目光收斂到一組可控的解決方案中。

另一個方向的極端情況是,所有可能的解決方案都被定義得很清楚了,但是如何把它們結合起來以達成目標,卻毫不清晰。這往往是因爲遺留系統難以改動,或者遺留系統不是被設計用來解決當前所面臨的問題的,又或者是一個類庫的設計要求我們在它的宿主編程語言限制下工作。

在這種情況下,你或許可以遍歷所有可行的簡單方法,但更需要創新地把所有這些方法整合起來以完成目標。也許存在多種方案,每一種方案都不是特別出色的,因此文檔應該關注在如何從已經識別的各項取捨中,選擇最合適的方案。

1.4 候選設計(Alternatives considered)

這一小節列出那些同樣可以實現類似產出的可選設計。這裏關注的應該是各種方案各自的取捨,以及這些取捨的對比如何引向最終的設計 —— 文檔的核心主題。

雖然描述候選設計可以簡潔一些,但是這一小節實際上非常核心的,因爲這裏非常清晰地展示了,在給定的項目目標和所有可選方案下,爲什麼選擇了最終方案,在給定目標下權衡的判斷是如何做出的,而這正是文檔的讀者所關注的核心。

1.5 橫切關注點(Cross-cutting Concerns)

在這裏組織可以確保一些橫切關注點如安全,隱私,可觀測性,總是被考慮到。這部分內容通常相對較短,只是用來解釋設計會如何影響到橫切關注點,以及相關影響如何得到解決。團隊應該標準化在他們場景下的關注點。

例如,由於隱私非常重要,Google 的項目就必須寫一個專門的隱私設計文檔,這個文檔會別專門用來 Review 隱私和安全。雖然 Review 只要在項目啓動前完成,但通常最好是儘早讓隱私和安全團隊介入,確保設計從一開始就重視他們的意見。關於這部分內容更專門的細節,核心文檔不一定要全部包含,有時候給到這些專門文檔的引用即可。

1.6 設計文檔的長度(The length of a design doc)

設計文檔應該具備充分的細節,但同時足夠簡短以能夠被忙碌的人閱讀。對於一個大型項目來說,最佳的長度似乎是 10 到 20 頁。如果你的內容超過這個大小,更合理的做法可能是把這個問題劃分成更易管理的子問題。當然,需要注意的是,編寫 1-3 頁的 “迷你設計文檔” 完全是可能的。這類文檔對於敏捷項目中的增量改進或者子任務尤其有用 —— 但你仍然需要和編寫長文檔一樣執行一樣的步驟,區別只是讓內容更精煉,並且只關注有限的問題集合。

2. 什麼時候不要編寫設計文檔(When not to write a design doc)

編寫設計文檔是需要成本的。關於是否編寫設計文檔的決定,實際上是在做權衡。權衡的一邊是圍繞設計、文檔、高層評審等工作形成組織共識的益處,權衡的另一邊是這塊工作投入的精力成本。決策的核心在於設計問題的解決方案是否模糊 —— 這往往是因爲問題複雜度,或者解決方案的複雜度引起的(或者兩者皆有)。如果不存在這個問題,那麼走一個設計文檔編寫的流程價值就有限。

設計文檔可能沒有必要的一個明顯徵兆是:設計文檔實際上是_實現手冊_。如果文檔基本上說的是 “這是我們將如何實現之”,而沒有深入討論取捨、可選方案、沒有解釋決策(或者說解決方案太明顯了以致於沒什麼取捨可討論),那麼很有可能直接編寫真實代碼是更好的選擇。

最後,創建和評審設計文檔的投入可能與快速製作原型並迭代理念並不相容。但是大多數軟件項目都是有一組_實際上已知的問題_。擁抱敏捷方法不應該成爲不花時間去尋找已知問題正確解決方案的藉口。此外,原型本身可能就是創建設計文檔過程的一部分。“我試過了,這麼做可行” 就是選擇一種設計最好的論據之一。

3. 設計文檔的生命週期(The design doc lifecycle)

一份設計文檔的生命週期包括如下階段:

  1. 創建和快速迭代

  2. 審查(可能有多輪)

  3. 實現和迭代

  4. 維護和學習

3.1 創建和快速迭代(Creation and rapic iteration)

你編寫文檔,有時候是和幾個合作者一起編寫。

這一階段快速演進成爲快速迭代的時期,文檔被分享給一些同事,他們擁有關於問題空間(problem space)的最多的知識(通常屬於同一個團隊),通過他們不斷澄清問題並提出建議,文檔逐漸形成第一個相對穩定的版本。

你會發現很多工程師和團隊更偏向於使用版本控制和代碼審查工具來管理文檔,但是 Google 的大多數文檔是使用 Google Docs 創建的,並重度使用了協作特性。

3.2 評審**(Review)**

在評審階段,設計文檔被分享給原始作者和緊密協作者之外更廣範的一批受衆。評審可以給文檔增加很多價值,但是也有可能是危險的投入成本陷阱,因此要明智對待。

評審可以有很多形式:最輕量的版本就是簡單把文檔發給更大範圍的團隊,讓大夥有機會可以看一眼。隨着而來的討論主要就發生在文檔的評論區。而形式較重的評審,就是發起正式的設計文檔評審會議,在會議中作者面向通常是較爲資深的工程師聽衆演示文檔(通常是專門的演示)。Google 有很多團隊爲此目的排了週期性的會議,工程師可以註冊用來發起設計評審。自然的,等待此類會議來評審設計文檔會大幅降低研發速度。工程師可以通過直接向同事獲取最關鍵的反饋,同時也不阻塞更廣泛的評審,進而降低這一風險。

當 Google 是一個小一些的公司的時候,大家習慣上會把設計發到一箇中心的郵件列表中,在這裏資深的工程師會抽空進行評審。這種方式對公司來說可能就不錯。這種方式一大好處是,它在整個公司層面建立了一種相對一致的軟件設計文化。但是隨着公司逐漸增長細形成一個較大的工程師團隊,維護這種中心化的方法就不可行了。

設計評審增加的主要價值是,它形成了一個讓組織的綜合經驗可以融合到設計中的機會。如何讓設計能夠充分考慮橫切關注點如可觀測性、安全性、以及隱私,這一點就能夠非常一致地在評審階段得到保障。評審的主要價值不是問題被發現本身,而是讓問題在軟件研發階段相對早期的時候,也就是修復成本相對較低的時候被發現。

3.3 實現和迭代(Implementation and iteration)

當事情獲得了充分的進展,看起來進一步的評審不太會要求設計做重大的改變,那就是時候開始實現了。當計劃和現實衝突,不可避免的會發現設計的缺陷,未被充分考慮的需求,或者基於經驗的推測實際上錯誤的,進而發現需要對設計做修改。在這種情況下強烈推薦更新設計文檔,一般說來:如果系統還沒有上線,那麼很確定應該更新文檔。在實踐中,我們普通人在更新文檔方面都做得不好,以及因爲一些其他的實際因素,更新還通常會落到新的獨立的文檔中去。這就導致了一種近似美國憲法的最終態:有一堆修正案,而非一份一致的文檔。對於將來維護系統的可憐程序員來說,當他們像考古學家一樣翻閱歷史設計文檔,去試圖理解目標系統的時候,原始文檔中給出這些 “修正案” 的鏈接會非常有幫助。

3.4 維護和學習(Maintenance and learning)

當 Google 工程師首次接觸一個系統的時候,他們的第一個問題通常是 “設計文檔在哪裏?”。雖然說設計文檔和所有其他文檔一樣,都會隨着時間的流逝變得和現實不一致,但它們依舊是學習系統在創建之初其背後思考的最佳起步材料。

作爲作者,從爲自己着想的角度考慮,可以在一兩年後重新閱讀自己的設計文檔。你哪裏做對了?哪裏做錯了?在今天來看你會做哪些不同的決策?對於工程師來說,回答這些問題是一種絕佳的提升軟件設計能力和自我進步的方式。

4. 結論(Conclusions)

在軟件項目中,圍繞解決最難的問題,設計文檔是一種獲得清晰度以並達成共識的絕佳方法。設計文檔可以節省金錢,因爲在前期足夠的調研可以幫助避免過早就進入編碼細節卻未能完成項目目標;設計文檔又花費金錢,因爲編寫和審查文檔消耗時間。因此,在你項目中明智地做出選擇。

在考慮是否編寫設計文檔的時候,思考如下問題:

●你是否對正確軟件設計不確信?在前期消耗時間來獲取確定性是否合理?

●在設計階段引入相關的資深工程師是否有幫助?他們可能沒有時間審查所有代碼。

●軟件設計是否是模糊的,甚至是有爭議的?因此圍繞這一問題在組織層面達成共識會很有價值?

●我團隊是否有時候會在設計中忘記考慮隱私、安全、日誌或者其他橫切關注點?

●在組織中是否非常需要遺留系統設計的文檔?這樣可以讓大家在 high-level 快速瞭解系統。

如果對於上述的問題你有三個或更多 “是” 的回答,那麼在你開始下一個軟件項目的時候,設計文檔大概率是個不錯的方法。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/NJdPVNcrJ9OuyKSPKpmfWA