国产精品久久国产精麻豆99网站,激烈18禁高潮视频免费,老师含紧一点h边做边走视频动漫,双乳被一左一右的吸着

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

作者:cheaterlin,騰訊 PCG 后臺開發(fā)工程師

綜述

我寫過一篇《Code Review 我都 CR 些什么》,講解了 Code Review 對團隊有什么價值,我認為 CR 最重要的原則有哪些。最近我在團隊工作中還發(fā)現(xiàn)了:

  • 原則不清晰。對于代碼架構的原則,編碼的追求,我的骨干員工對它的認識也不是很全面。當前還是在 review 過程中我對他們口口相傳,總有遺漏。
  • 從知道到會做需要時間。我需要反復跟他們補充 review 他們漏掉的點,他們才能完成吸收、內化,在后續(xù)的 review 過程中,能自己提出這些 review 的點。

過度文檔化是有害的,當過多的內容需要被閱讀,工程師們最終就會選擇不去讀,讀了也僅僅能吸收很少一部分。在 google,對于代碼細節(jié)的理解,更多還是口口相傳,在實踐中去感受和理解。但是,適當?shù)奈臋n、文字宣傳,是必要的。特此,我就又輸出了這一篇文章,嘗試從'知名架構原則'、'工程師的自我修養(yǎng)'、'不能上升到原則的幾個常見案例'三大模塊,把我個人的經(jīng)驗系統(tǒng)地輸出,供其他團隊參考。

知名架構原則

后面原則主要受《程序員修煉之道: 通向務實的最高境界》、《架構整潔之道》、《Unix 編程藝術》啟發(fā)。我不是第一個發(fā)明這些原則的人,甚至不是第一個總結出來的人,別人都已經(jīng)寫成書了!務實的程序員對于方法的總結,總是殊途同歸。

細節(jié)即是架構

(下面是原文摘錄, 我有類似觀點, 但是原文就寫得很好, 直接摘錄)

一直以來,設計(Design)和架構(Architecture)這兩個概念讓大多數(shù)人十分迷惑–什么是設計?什么是架構?二者究竟有什么區(qū)別?二者沒有區(qū)別。一丁點區(qū)別都沒有!"架構"這個詞往往適用于"高層級"的討論中,這類討論一般都把"底層"的實現(xiàn)細節(jié)排除在外。而"設計"一詞,往往用來指代具體的系統(tǒng)底層組織結構和實現(xiàn)的細節(jié)。但是,從一個真正的系統(tǒng)架構師的日常工作來看,這些區(qū)分是根本不成立的。以給我設計新房子的建筑設計師要做的事情為例。新房子當然是存在著既定架構的,但這個架構具體包含哪些內容呢?首先,它應該包括房屋的形狀、外觀設計、垂直高度、房間的布局,等等。

但是,如果查看建筑設計師使用的圖紙,會發(fā)現(xiàn)其中也充斥著大量的設計細節(jié)。譬如,我們可以看到每個插座、開關以及每個電燈具體的安裝位置,同時也可以看到某個開關與所控制的電燈的具體連接信息;我們也能看到壁爐的具體位置,熱水器的大小和位置信息,甚至是污水泵的位置;同時也可以看到關于墻體、屋頂和地基所有非常詳細的建造說明??偟膩碚f,架構圖里實際上包含了所有的底層設計細節(jié),這些細節(jié)信息共同支撐了頂層的架構設計,底層設計信息和頂層架構設計共同組成了整個房屋的架構文檔。

軟件設計也是如此。底層設計細節(jié)和高層架構信息是不可分割的。他們組合在一起,共同定義了整個軟件系統(tǒng),缺一不可。所謂的底層和高層本身就是一系列決策組成的連續(xù)體,并沒有清晰的分界線。

我們編寫、review 細節(jié)代碼,就是在做架構設計的一部分。我們編寫的細節(jié)代碼構成了整個系統(tǒng)。我們就應該在細節(jié) review 中,總是帶著所有架構原則去審視。你會發(fā)現(xiàn),你已經(jīng)寫下了無數(shù)讓整體變得丑陋的細節(jié),它們背后,都有前人總結過的架構原則。

把代碼和文檔綁在一起(自解釋原則)

寫文檔是個好習慣。但是寫一個別人需要咨詢老開發(fā)者才能找到的文檔,是個壞習慣。這個壞習慣甚至會給工程師們帶來傷害。比如,當初始開發(fā)者寫的文檔在一個犄角旮旯(在 wiki 里,但是閱讀代碼的時候沒有在明顯的位置看到鏈接),后續(xù)代碼被修改了,文檔已經(jīng)過時,有人再找出文檔來獲取到過時、錯誤的知識的時候,閱讀文檔這個同學的開發(fā)效率必然受到傷害。所以,如同 golang 的 godoc 工具能把代碼里'按規(guī)范來'的注釋自動生成一個文檔頁面一樣,我們應該:

  • 按照 godoc 的要求好好寫代碼的注釋。
  • 代碼首先要自解釋,當解釋不了的時候,需要就近、合理地寫注釋。
  • 當小段的注釋不能解釋清楚的時候,應該有 doc.go 來解釋,或者,在同級目錄的 ReadMe.md 里注釋講解。
  • 文檔需要強大的富文本編輯能力,Down 無法滿足,可以寫到 wiki 里,同時必須把 wiki 的簡單描述和鏈接放在代碼里合適的位置。讓閱讀和維護代碼的同學一眼就看到,能做到及時的維護。

以上,總結起來就是,解釋信息必須離被解釋的東西,越近越好。代碼能做到自解釋,是最棒的。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

讓目錄結構自解釋

ETC 價值觀(easy to change)

ETC 是一種價值觀念,不是一條原則。價值觀念是幫助你做決定的: 我應該做這個,還是做那個?當你在軟件領域思考時,ETC 是個向導,它能幫助你在不同的路線中選出一條。就像其他一些價值觀念一樣,你應該讓它漂浮在意識思維之下,讓它微妙地將你推向正確的方向。

敏捷軟件工程,所謂敏捷,就是要能快速變更,并且在變更中保持代碼的質量。所以,持有 ETC 價值觀看待代碼細節(jié)、技術方案,我們將能更好地編寫出適合敏捷項目的代碼。這是一個大的價值觀,不是一個基礎微觀的原則,所以沒有例子。本文提到的所有原則,或者接,或間接,都要為 ETC 服務。

DRY 原則(don not repeat yourself)

在《Code Review 我都 CR 些什么》里面,我已經(jīng)就 DRY 原則做了深入闡述,這里不再贅述。我認為 DRY 原則是編碼原則中最重要的編碼原則,沒有之一(ETC 是個觀念)。不要重復!不要重復!不要重復!

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

正交性原則(全局變量的危害)

'正交性'是幾何學中的術語。我們的代碼應該消除不相關事物之間的影響。這是一給簡單的道理。我們寫代碼要'高內聚、低耦合',這是大家都在提的。

但是,你有為了使用某個 class 一堆能力中的某個能力而去派生它么?你有寫過一個 helper 工具,它什么都做么?在騰訊,我相信你是做過的。你自己說,你這是不是為了復用一點點代碼,而讓兩大塊甚至多塊代碼耦合在一起,不再正交了?大家可能并不是不明白正交性的價值,只是不知道怎么去正交。手段有很多,但是首先我就要批判一下 OOP。它的核心是多態(tài),多態(tài)需要通過派生/繼承來實現(xiàn)。繼承樹一旦寫出來,就變得很難 change,你不得不為了使用一小段代碼而去做繼承,讓代碼耦合。

你應該多使用組合,而不是繼承。以及,應該多使用 DIP(Dependence Inversion Principle),依賴倒置原則。換個說法,就是面向 interface 編程,面向契約編程,面向切面編程,他們都是 DIP 的一種衍生。寫 golang 的同學就更不陌生了,我們要把一個 struct 作為一個 interface 來使用,不需要顯式 implement/extend,僅僅需要持有對應 interface 定義了的函數(shù)。這種 duck interface 的做法,讓 DIP 來得更簡單。AB 兩個模塊可以獨立編碼,他們僅僅需要一個依賴一個 interface 簽名,一個剛好實現(xiàn)該 interface 簽名。并不需要顯式知道對方 interface 簽名的兩個模塊就可以在需要的模塊、場景下被組合起來使用。代碼在需要被組合使用的時候才產(chǎn)生了一點關系,同時,它們依然保持著獨立。

說個正交性的典型案例。全局變量是不正交的!沒有充分的理由,禁止使用全局變量。全局變量讓依賴了該全局變量的代碼段互相耦合,不再正交。特別是一個 pkg 提供一個全局變量給其他模塊修改,這個做法會讓 pkg 之間的耦合變得復雜、隱秘、難以定位。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

全局 map case

單例就是全局變量

這個不需要我解釋,大家自己品一品。后面有'共享狀態(tài)就是不正確的狀態(tài)'原則,會進一步講到。我先給出解決方案,可以通過管道、消息機制來替代共享狀態(tài)/使用全局變量/使用單例。僅僅能獲取此刻最新的狀態(tài),通過消息變更狀態(tài)。要拿到最新的狀態(tài),需要重新獲取。在必要的時候,引入鎖機制。

可逆性原則

可逆性原則是很少被提及的一個原則??赡嫘?,就是你做出的判斷,最好都是可以被逆轉的。再換一個容易懂的說法,你最好盡量少認為什么東西是一定的、不變的。比如,你認為你的系統(tǒng)永遠服務于,用 32 位無符號整數(shù)(比如 QQ 號)作為用戶標識的系統(tǒng)。你認為,你的持久化存儲,就選型 SQL 存儲了。當這些一開始你認為一定的東西,被推翻的時候,你的代碼卻很難去 change,那么,你的代碼就是可逆性做得很差。書里有一個例證,我覺得很好,直接引用過來。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

與其認為決定是被刻在石頭上的,還不如把它們想像成寫在沙灘的沙子上。一個大浪隨時都可能襲來,卷走一切。騰訊也確實在 20 年內經(jīng)歷了'大鐵塊'到'云虛擬機換成容器'的幾個階段。幾次變化都是傷筋動骨,浪費大量的時間。甚至總會有一些上一個時代殘留的服務。就機器數(shù)量而論,還不小。一到裁撤季,就很難受。就最近,我看到某個 trpc 插件,直接從環(huán)境變量里讀取本機 IP,僅僅因為 STKE(Tencent Kubernetes Engine)提供了這個能力。這個細節(jié)設計就是不可逆的,將來會有人為它買單,可能價格還不便宜。

我今天才想起一個事兒。當年 SNG 的很多部門對于 metrics 監(jiān)控的使用。就潛意識地認為,我們將一直使用'模塊間調用監(jiān)控'組件。使用它的 API 是直接把上報通道 DCLog 的 API 裸露在業(yè)務代碼里的。今天(2020.12.01),該組件應該已經(jīng)完全沒有人維護、完全下線了,這些核心業(yè)務代碼要怎么辦?有人能對它做出修改么?那,這些部門現(xiàn)在還有 metrics 監(jiān)控么?答案,可能是悲觀的。有人已經(jīng)已經(jīng)嘗到了可逆性之痛。

依賴倒置原則(DIP)

DIP 原則太重要了,我這里單獨列一節(jié)來講解。我這里只是簡單的講解,講解它最原始和簡單的形態(tài)。依賴倒置原則,全稱是 Dependence Inversion Principle,簡稱 DIP??紤]下面這幾段代碼:

package dippackage diptype Botton interface {    TurnOn()    TurnOff()}type UI struct {    botton Botton}func NewUI(b Botton) *UI {    return &UI{botton: b}}func (u *UI) Poll() {    u.botton.TurnOn()    u.botton.TurnOff()    u.botton.TurnOn()}

package Javaimplimport "fmt"type Lamp struct {}func NewLamp() *Lamp {    return &Lamp{}}func (*Lamp) TurnOn() {    fmt.Println("turn on java lamp")}func (*Lamp) TurnOff() {    fmt.Println("turn off java lamp")}

package pythonimplimport "fmt"type Lamp struct {}func NewLamp() *Lamp {    return &Lamp{}}func (*Lamp) TurnOn() {    fmt.Println("turn on python lamp")}func (*Lamp) TurnOff() {    fmt.Println("turn off python lamp")}

package mainimport (    "javaimpl"    "pythonimpl"    "dip")func runPoll(b dip.Botton) {    ui := NewUI(b)    ui.Poll()}func main() {    runPoll(pythonimpl.NewLamp())    runPoll(javaimpl.NewLamp())}

看代碼,main pkg 里的 runPoll 函數(shù)僅僅面向 Botton interface 編碼,main pkg 不再關心 Botton interface 里定義的 TurnOn、TurnOff 的實現(xiàn)細節(jié)。實現(xiàn)了解耦。這里,我們能看到 struct UI 需要被注入(inject)一個 Botton interface 才能邏輯完整。所以,DIP 經(jīng)常換一個名字出現(xiàn),叫做依賴注入(Dependency Injection)。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

從這個依賴圖觀察。我們發(fā)現(xiàn),一般來說,UI struct 的實現(xiàn)是要應該依賴于具體的 pythonLamp、javaLamp、其他各種 Lamp,才能讓自己的邏輯完整。那就是 UI struct 依賴于各種 Lamp 的實現(xiàn),才能邏輯完整。但是,我們看上面的代碼,卻是反過來了。pythonLamp、javaLamp、其他各種 Lamp 是依賴 Botton interface 的定義,才能用來和 UI struct 組合起來拼接成完整的業(yè)務邏輯。變成了,Lamp 的實現(xiàn)細節(jié),依賴于 UI struct 對于 Botton interface 的定義。這個時候,你發(fā)現(xiàn),這種依賴關系被倒置了!依賴倒置原則里的'倒置',就是這么來的。在 golang 里,'pythonLamp、javaLamp、其他各種 Lamp 是依賴 Botton interface 的定義',這個依賴是隱性的,沒有顯式的 implement 和 extend 關鍵字。代碼層面,pkg dip 和 pkg pythonimpl、javaimpl 沒有任何依賴關系。他們僅僅需要被你在 main pkg 里組合起來使用。

在 J2EE 里,用戶的業(yè)務邏輯不再依賴低具體低層的各種存儲細節(jié),而僅僅依賴一套配置化的 Java Bean 接口。Object 落地存儲的具體細節(jié),被做成了 Java Bean 配置,注入到框架里。這就是 J2EE 的核心科技,并不復雜,其實也沒有多么'高不可攀'。反而,在'動態(tài)代碼'優(yōu)于'配置'的今天,這種通過配置實現(xiàn)的依賴注入,反而有點過時了。

將知識用純文本來保存

這也是一個生僻的原則。指代碼操作的數(shù)據(jù)和方案設計文稿,如果沒有充分的必要使用特定的方案,就應該使用人類可讀的文本來保存、交互。對于方案設計文稿,你能不使用 office 格式,就不使用(office 能極大提升效率,才用),最好是原始 text。這是《Unix 編程藝術》也提到了的 Unix 系產(chǎn)生的設計信條。簡而言之一句話,當需要確保有一個所有各方都能使用的公共標準,才能實現(xiàn)交互溝通時,純文本就是這個標準。它是一個接受度最高的通行標準。如果沒有必要的理由,我們就應該使用純文本。

契約式設計

如果你對契約式設計(Design by Contract, DBC)還很陌生,我相信,你和其他端的同學(web、client、后端)聯(lián)調需求應該是一件很花費時間的事情。你自己編寫接口自動化,也會是一件很耗費精力的事情。你先看看它的wiki 解釋吧。grpc grpc-gateway swagger 是個很香的東西。

代碼是否不多不少剛好完成它宣稱要做的事情,可以使用契約加以校驗和文檔化。TDD 就是全程在不斷調整和履行著契約。TDD(Test-Driven Development)是自底向上地編碼過程,其實會耗費大量的精力,并且對于一個良好的層級架構沒有幫助。TDD 不是強推的規(guī)范,但是同學們可以用一用,感受一下。TDD 方法論實現(xiàn)的接口、函數(shù),自我解釋能力一般來說比較強,因為它就是一個實現(xiàn)契約的過程。

拋開 TDD 不談。我們的函數(shù)、api,你能快速抓住它描述的核心契約么?它的契約簡單么?如果不能、不簡單,那你應該要求被 review 的代碼做出調整。如果你在指導一個后輩,你應該幫他思考一下,給出至少一個可能的簡化、拆解方向。

盡早崩潰

Erlang 和 Elixir 語言信奉這種哲學。喬-阿姆斯特朗,Erlang 的發(fā)明者,《Erlang 程序設計》的作者,有一句反復被引用的話: "防御式編程是在浪費時間,讓它崩潰"。

盡早崩潰不是說不容錯,而是程序應該被設計成允許出故障,有適當?shù)墓收媳O(jiān)管程序和代碼,及時告警,告知工程師,哪里出問題了,而不是嘗試掩蓋問題,不讓程序員知道。當最后程序員知道程序出故障的時候,已經(jīng)找不到問題出現(xiàn)在哪里了。

特別是一些 recover 之后什么都不做的代碼,這種代碼簡直是毒瘤!當然,崩潰,可以是早一些向上傳遞 error,不一定就是 panic。同時,我要求大家不要在沒有充分的必要性的時候 panic,應該更多地使用向上傳遞 error,做好 metrics 監(jiān)控。合格的 golang 程序員,都不會在沒有必要的時候無視 error,會妥善地做好 error 處理、向上傳遞、監(jiān)控。一個死掉的程序,通常比一個癱瘓的程序,造成的損害要小得多。

崩潰但是不告警,或者沒有補救的辦法,不可取.盡早崩潰的題外話是,要在問題出現(xiàn)的時候做合理的告警,有預案,不能掩蓋,不能沒有預案:

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

解耦代碼讓改變容易

這個原則,顯而易見,大家自己也常常提,其他原則或多或少都和它有關系。但是我也再提一提。我主要是描述一下它的癥狀,讓同學們更好地警示自己'我這兩塊代碼是不是耦合太重,需要額外引入解耦的設計了'。癥狀如下:

  • 不相關的 pkg 之間古怪的依賴關系
  • 對一個模塊進行的'簡單'修改,會傳播到系統(tǒng)中不相關的模塊里,或是破壞了系統(tǒng)中的其他部分
  • 開發(fā)人員害怕修改代碼,因為他們不確定會造成什么影響
  • 會議要求每個人都必須參加,因為沒有人能確定誰會受到變化的影響

只管命令不要詢問

看看如下三段代碼:

func applyDiscount(customer Customer, orderID string, discount float32) { customer.  Orders.  Find(orderID).  GetTotals().  ApplyDiscount(discount)}

func applyDiscount(customer Customer, orderID string, discount float32) { customer.  FindOrder(orderID).  GetTotals().  ApplyDiscount(discount)}

func applyDiscount(customer Customer, orderID string, discount float32) { customer.  FindOrder(orderID).  ApplyDiscount(discount)}

明顯,最后一段代碼最簡潔。不關心 Orders 成員、總價的存在,直接命令 customer 找到 Order 并對其進行打折。當我們調整 Orders 成員、GetTotals()方法的時候,這段代碼不用修改。還有一種更嚇人的寫法:

func applyDiscount(customer Customer, orderID string, discount float32) { total := customer.  FindOrder(orderID).  GetTotals() customer.  FindOrder(orderID).  SetTotal(total*discount)}

它做了更多的查詢,關心了更多的細節(jié),變得更加 hard to change 了。我相信,大家寫過類似的代碼也不少。特別是客戶端同學。

最好的那一段代碼,就是只管給每個 struct 發(fā)送命令,要求大家做事兒。怎么做,就內聚在和 struct 關聯(lián)的方法里,其他人不要去操心。一旦其他人操心了,當需要做修改的時候,就要操心了這個細節(jié)的人都一起參與進修改過程。

不要鏈式調用方法

看下面的例子:

func amount(customer Customer) float32 { return customer.Orders.Last().Totals().Amount}

func amount(totals Totals) float32 { return totals.Amount}

第二個例子明顯優(yōu)于第一個,它變得更簡單、通用、ETC。我們應該給函數(shù)傳入它關心的最小集合作為參數(shù)。而不是,我有一個 struct,當某個函數(shù)需要這個 struct 的成員的時候,我們把整個 struct 都作為參數(shù)傳遞進去。應該僅僅傳遞函數(shù)關心的最小集合。傳進去的一整條調用鏈對函數(shù)來說,都是無關的耦合,只會讓代碼更 hard to change,讓工程師懼怕去修改。這一條原則,和上一條關系很緊密,問題常常同時出現(xiàn)。還是,特別是在客戶端代碼里。

繼承稅(多用組合)

繼承就是耦合。不僅子類耦合到父類,以及父類的父類等,而且使用子類的代碼也耦合到所有祖先類。 有些人認為繼承是定義新類型的一種方式。他們喜歡設計圖表,會展示出類的層次結構。他們看待問題的方式,與維多利亞時代的紳士科學家們看待自然的方式是一樣的,即將自然視為須分解到不同類別的綜合體。 不幸的是,這些圖表很快就會為了表示類之間的細微差別而逐層添加,最終可怕地爬滿墻壁。由此增加的復雜性,可能使應用程序更加脆弱,因為變更可能在許多層次之間上下波動。 因為一些值得商榷的詞義消歧方面的原因,C 在20世紀90年代玷污了多重繼承的名聲。結果,許多當下的OO語言都沒有提供這種功能。

因此,即使你很喜歡復雜的類型樹,也完全無法為你的領域準確地建模。

Java 下一切都是類。C 里不使用類還不如使用 C。寫 Python、PHP,我們也肯定要時髦地寫一些類。寫類可以,當你要去繼承,你就得考慮清楚了。繼承樹一旦形成,就是非常 hard to change 的,在敏捷項目里,你要想清楚'代價是什么',有必要么?這個設計'可逆'么?對于邊界清晰的 UI 框架、游戲引擎,使用復雜的繼承樹,挺好的。對于 UI 邏輯、后臺邏輯,可能,你僅僅需要組合、DIP(依賴反轉)技術、契約式編程(接口與協(xié)議)就夠了。寫出繼承樹不是'就應該這么做',它是成本,繼承是要收稅的!

在 golang 下,繼承稅的煩惱被減輕了,golang 從來說自己不是 OO 的語言,但是你 OO 的事情,我都能輕松地做到。更進一步,OO 和過程式編程的區(qū)別到底是什么?

面向過程,面向對象,函數(shù)式編程。三種編程結構的核心區(qū)別,是在不同的方向限制程序員,來做到好的代碼結構(引自《架構整潔之道》):

  • 結構化編程是對程序控制權的直接轉移的限制。
  • 面向對象是對程序控制權的間接轉移的限制。
  • 函數(shù)式編程是對程序中賦值操作的限制。

SOLID 原則(單一功能、開閉原則、里氏替換、接口隔離、依賴反轉,后面會講到)是 OOP 編程的最經(jīng)典的原則。其中 D 是指依賴倒置原則(Dependence Inversion Principle),我認為,是 SOLID 里最重要的原則。J2EE 的 container 就是圍繞 DIP 原則設計的。DIP 能用于避免構建復雜的繼承樹,DIP 就是'限制控制權的間接轉移'能繼續(xù)發(fā)揮積極作用的最大保障。合理使用 DIP 的 OOP 代碼才可能是高質量的代碼。

golang 的 interface 是 duck interface,把 DIP 原則更進一步,不需要顯式 implement/extend interface,就能做到 DIP。golang 使用結構化編程范式,卻有面向對象編程范式的核心優(yōu)點,甚至簡化了。這是一個基于高度抽象理解的極度精巧的設計。google 把 abstraction 這個設計理念發(fā)揮到了極致。曾經(jīng),J2EE 的 container(EJB, Java Bean)設計是國內 Java 程序員引以為傲'架構設計'、'厲害的設計'。

在 golang 里,它被分析、解構,以更簡單、靈活、統(tǒng)一、易懂的方式呈現(xiàn)出來。寫了多年垃圾 C 代碼的騰訊后端工程師們,是你們再次審視 OOP 的時候了。我大學一年級的時候看的 C 教材,終歸給我描述了一個美好卻無法抵達的世界。目標我沒有放棄,但我不再用 OOP,而是更多地使用組合(Mixin)。寫 golang 的同學,應該對 DIP 和組合都不陌生,這里我不再贅述。如果有人自傲地說他在 golang 下搞起了繼承,我只能說,'同志,你現(xiàn)在站在了廣大 gopher 的對立面'。現(xiàn)在,你站在哲學的云端,鳥瞰了 Structured Programming 和 OOP。你還愿意再繼續(xù)支付繼承稅么?

共享狀態(tài)是不正確的狀態(tài)

你坐在最喜歡的餐廳。吃完主菜,問男服務員還有沒有蘋果派。他回頭一看-陳列柜里還有一個,就告訴你"還有"。點到了蘋果派,你心滿意足地長出了一口氣。與此同時,在餐廳的另一邊,還有一個顧客也問了女服務員同樣的問題。她也看了看,確認有一個,讓顧客點了單??傆幸粋€顧客會失望的。

問題出在共享狀態(tài)。餐廳里的每一個服務員都查看了陳列柜,卻沒有考慮到其他服務員。你們可以通過加互斥鎖來解決正確性的問題,但是,兩個顧客有一個會失望或者很久都得不到答案,這是肯定的。

所謂共享狀態(tài),換個說法,就是: 由多個人查看和修改狀態(tài)。這么一說,更好的解決方案就浮出水面了: 將狀態(tài)改為集中控制。預定蘋果派,不再是先查詢,再下單。而是有一個餐廳經(jīng)理負責和服務員溝通,服務員只管發(fā)送下單的命令/消息,經(jīng)理看情況能不能滿足服務員的命令。

這種解決方案,換一個說法,也可以說成"用角色實現(xiàn)并發(fā)性時不必共享狀態(tài)"。對,上面,我們引入了餐廳經(jīng)理這個角色,賦予了他職責。當然,我們僅僅應該給這個角色發(fā)送命令,不應該去詢問他。前面講過了,'只管命令不要詢問',你還記得么。

同時,這個原則就是 golang 里大家耳熟能詳?shù)闹V語: "不要通過共享內存來通信,而應該通過通信來共享內存"。作為并發(fā)性問題的根源,內存的共享備受關注。但實際上,在應用程序代碼共享可變資源(文件、數(shù)據(jù)庫、外部服務)的任何地方,問題都有可能冒出來。當代碼的兩個或多個實例可以同時訪問某些資源時,就會出現(xiàn)潛在的問題。

緘默原則

如果一個程序沒什么好說,就保持沉默。過多的正常日志,會掩蓋錯誤信息。過多的信息,會讓人根本不再關注新出現(xiàn)的信息,'更多信息'變成了'沒有信息'。每人添加一點信息,就變成了輸出很多信息,最后等于沒有任何信息。

  • 不要在正常 case 下打印日志。
  • 不要在單元測試里使用 fmt 標準輸出,至少不要提交到 master。
  • 不打不必要的日志。當錯誤出現(xiàn)的時候,會非常明顯,我們能第一時間反應過來并處理。
  • 讓調試的日志停留在調試階段,或者使用較低的日志級別,你的調試信息,對其他人根本沒有價值。
  • 即使低級別日志,也不能泛濫。不然,日志打開與否都沒有差別,日志變得毫無價值。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

緘默

錯誤傳遞原則

我不喜歡 Java 和 C 的 exception 特性,它容易被濫用,它具有傳染性(如果代碼 throw 了 excepttion, 你就得 handle 它,不 handle 它,你就崩潰了??赡苣悴幌M罎ⅲ銉H僅希望報警)。但是 exception(在 golang 下是 panic)是有價值的,參考微軟的文章:

Exceptions are preferred in modern C  for the following reasons:* An exception forces calling code to recognize an error condition and handle it. Unhandled exceptions stop program execution.* An exception jumps to the point in the call stack that can handle the error. Intermediate functions can let the exception propagate. They don't have to coordinate with other layers.* The exception stack-unwinding mechanism destroys all objects in scope after an exception is thrown, according to well-defined rules.* An exception enables a clean separation between the code that detects the error and the code that handles the error.

Google 的 C 規(guī)范在常規(guī)情況禁用 exception,理由包含如下內容:

Because most existing C  code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.

從 google 和微軟的文章中,我們不難總結出以下幾點衍生的結論:

  • 在必要的時候拋出 exception。使用者必須具備'必要性'的判斷能力。
  • exception 能一路把底層的異常往上傳遞到高函數(shù)層級,信息被向上傳遞,并且在上級被妥善處理??梢宰尞惓:完P心具體異常的處理函數(shù)在高層級和低層級遙相呼應,中間層級什么都不需要做,僅僅向上傳遞。
  • exception 傳染性很強。當代碼由多人協(xié)作,使用 A 模塊的代碼都必須要了解它可能拋出的異常,做出合理的處理。不然,就都寫一個丑陋的 catch,catch 所有異常,然后做一個沒有針對性的處理。每次 catch 都需要加深一個代碼層級,代碼常常寫得很丑。

我們看到了異常的優(yōu)缺點。上面第二點提到的信息傳遞,是很有價值的一點。golang 在 1.13 版本中拓展了標準庫,支持了Error Wrapping也是承認了 error 傳遞的價值。

所以,我們認為錯誤處理,應該具備跨層級的錯誤信息傳遞能力,中間層級如果不關心,就把 error 加上本層的信息向上透傳(有時候可以直接透傳),應該使用 Error Wrapping。exception/panic 具有傳染性。大量使用,會讓代碼變得丑陋,同時容易滋生可讀性問題。我們應該多使用 Error Wrapping,在必要的時候,才使用 exception/panic。每一次使用 exception/panic,都應該被認真審核。需要 panic 的地方,不去 panic,也是有問題的。參考本文的'盡早崩潰'。

額外說一點,注意不要把整個鏈路的錯誤信息帶到公司外,帶到用戶的瀏覽器、native 客戶端。至少不能直接展示給用戶看到。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

錯誤鏈

SOLID

SOLID 原則,是由以下幾個原則的集合體:

  • SRP: 單一職責原則
  • OCP: 開閉原則
  • LSP: 里氏替換原則
  • ISP: 接口隔離原則
  • DIP: 依賴反轉原則

這些年來,這幾個設計原則在很多不同的出版物里都有過詳細描述。它們太出名了,我這里就不更多地做詳解了。我這里想說的是,這 5 個原則環(huán)環(huán)相扣,前 4 個原則,要么就是同時做到,要么就是都沒做到,很少有說,做到其中一點其他三點都不滿足。ISP 就是做到 LSP 的常用手段。ISP 也是做到 DIP 的基礎。只是,它剛被提出來的時候,是主要針對'設計繼承樹'這個目的的。現(xiàn)在,它們已經(jīng)被更廣泛地使用在模塊、領域、組件這種更大的概念上。

SOLI 都顯而易見,DIP 原則是最值得注意的一點,我在其他原則里也多次提到了它。如果你還不清楚什么是 DIP,一定去看明白。這是工程師最基礎、必備的知識點之一了。

要做到 OCP 開閉原則,其實,就是要大家要通過后面講到的'不要面向需求編程'才能做好。如果你還是面向需求、面向 UI、交互編程,你永遠做不到開閉,并且不知道如何才能做到開閉。

如果你對這些原則確實不了解,建議讀一讀《架構整潔之道》。該書的作者 Bob 大叔,就是第一個提出 SOLID 這個集合體的人(20 世紀 80 年代末,在 USENET 新聞組)。

一個函數(shù)不要出現(xiàn)多個層級的代碼

// IrisFriends 拉取好友func IrisFriends(ctx iris.Context, app *app.App) { var rsp sdc.FriendsRsp defer func() {  var buf bytes.Buffer  _ = (&jsonpb.Marshaler{EmitDefaults: true}).Marshal(&buf, &rsp)  _, _ = ctx.Write(buf.Bytes()) }() common.AdjustCookie(ctx) if !checkCookie(ctx) {  return } // 從cookie中拿到關鍵的登陸態(tài)等有效信息 var session common.BaseSession common.GetBaseSessionFromCookie(ctx, &session) // 校驗登陸態(tài) err := common.CheckLoginSig(session, app.ConfigStore.Get().OIDBCmdSetting.PTLogin) if err != nil {  _ = common.ErrorResponse(ctx, errors.PTSigErr, 0, "check login sig error")  return } if err = getRelationship(ctx, app.ConfigStore.Get().OIDBCmdSetting, NewAPI(), &rsp); err != nil {  // TODO:日志 } return}

上面這一段代碼,是我隨意找的一段代碼。邏輯非常清晰,因為除了最上面 defer 寫回包的代碼,其他部分都是頂層函數(shù)組合出來的。閱讀代碼,我們不會掉到細節(jié)里出不來,反而忽略了整個業(yè)務流程。同時,我們能明顯發(fā)現(xiàn)它沒寫完,以及 common.ErrorResponse 和 defer func 兩個地方都寫了回包,可能出現(xiàn)發(fā)起兩次 http 回包。TODO 也會非常顯眼。

想象一下,我們沒有把細節(jié)收歸進 checkCookie()、getRelationship()等函數(shù),而是展開在這里,但是總函數(shù)行數(shù)沒有到 80 行,表面上符合規(guī)范。但是實際上,閱讀代碼的同學不再能輕松掌握業(yè)務邏輯,而是同時在閱讀功能細節(jié)和業(yè)務流程。閱讀代碼變成了每個時刻心智負擔都很重的事情。

顯而易見,單個函數(shù)里應該只保留某一個層級(layer)的代碼,更細化的細節(jié)應該被抽象到下一個 layer 去,成為子函數(shù)。

Unix 哲學基礎

《Code Review 我都 CR 些什么》講解了很多 Unix 的設計哲學。這里不再贅述,僅僅列舉一下。大家自行閱讀和參悟,并且運用到編碼、review 活動中。

  • 模塊原則: 使用簡潔的接口拼合簡單的部件
  • 清晰原則: 清晰勝于技巧
  • 組合原則: 設計時考慮拼接組合
  • 分離原則: 策略同機制分離,接口同引擎分離
  • 簡潔原則: 設計要簡潔,復雜度能低則低
  • 吝嗇原則: 除非確無它法,不要編寫龐大的程序
  • 透明性原則: 設計要可見,以便審查和調試
  • 健壯原則: 健壯源于透明與簡潔
  • 表示原則: 把知識疊入數(shù)據(jù)以求邏輯質樸而健壯
  • 通俗原則: 接口設計避免標新立異
  • 緘默原則: 如果一個程序沒什么好說,就保持沉默
  • 補救原則: 出現(xiàn)異常時,馬上退出并給出足量錯誤信息
  • 經(jīng)濟原則: 寧花機器一分,不花程序員一秒
  • 生成原則: 避免手工 hack,盡量編寫程序去生成程序
  • 優(yōu)化原則: 雕琢前先得有原型,跑之前先學會走
  • 多樣原則: 絕不相信所謂"不二法門"的斷言
  • 擴展原則: 設計著眼未來,未來總比預想快

工程師的自我修養(yǎng)

下面,是一些在 review 細節(jié)中不能直接使用的原則。更像是一種信念和自我約束。帶著這些信念去編寫、review 代碼,把這些信念在實踐中傳遞下去,將是極有價值的。

偏執(zhí)

對代碼細節(jié)偏執(zhí)的觀念,是我自己提出的新觀點。在當下研發(fā)質量不高的騰訊,是很有必要普遍存在的一個觀念。在一個系統(tǒng)不完善、時間安排荒謬、工具可笑、需求不可能實現(xiàn)的世界里,讓我們安全行事吧。就像伍迪-艾倫說的:"當所有人都真的在給你找麻煩的時候,偏執(zhí)就是一個好主意。"

對于一個方案,一個實現(xiàn),請不要說出"好像這樣也可以"。你一定要選出一個更好的做法,并且一直堅持這個做法,并且要求別人也這樣做。既然他來讓你 review 了,你就要有自己的偏執(zhí),你一定要他按照你覺得合適的方式去做。當然,你得有說服得了自己,也說服得了他人的理由。即使,只有一點點。偏執(zhí)會讓你的世界變得簡單,你的團隊的協(xié)作變得簡單。特別當你身處一個編碼質量低下的團隊的時候。你至少能說,我是一個務實的程序員。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

控制軟件的熵是軟件工程的重要任務之一

熵是個物理學概念,大家可能看過諾蘭的電影《信條》。簡單來說,熵可以理解為'混亂程度'。我們的項目,在剛開始的幾千行代碼,是很簡潔的。但是,為什么到了 100w 行,我們常常就感覺'太復雜了'?比如 QQ 客戶端,最近終于在做大面積重構,但是發(fā)現(xiàn)無數(shù) crash。其中一個重要原因,就是'混亂程度'太高了。'混亂程度',理解起來還是比較抽象,它有很多其他名字。'hard code 很多'、'特殊邏輯很多'、'定制化邏輯很多'。再換另一個抽象的說法,'我們面對一類問題,采取了過多的范式和特殊邏輯細節(jié)去實現(xiàn)它'。

熵,是一點點堆疊起來的,在一個需求的 2000 行代碼更改中,你可能就引入了一個不同的范式,打破了之前的通用范式。在微觀來看,你覺得你的代碼是'整潔干凈'的。就像一個已經(jīng)穿著好看的紅色風衣的人,你隔一天讓他接著穿上一條綠色的褲子,這還干凈整潔么?熵,在不斷增加,我們需要做到以下幾點,不然你的團隊將在希望通過重構來降低項目的熵的時候嘗到惡果,甚至放棄重構,讓熵不斷增長下去。

  • 如果沒有充分的理由,始終使用項目規(guī)范的范式對每一類問題做出解決方案。
  • 如果業(yè)務發(fā)展發(fā)現(xiàn)老的解決方案不再優(yōu)秀,做整體重構。
  • 項目級主干開發(fā),對重構很友好,讓重構變得可行。(客戶端很容易實現(xiàn)主干開發(fā))。
  • 務實地講,重構已經(jīng)不可能了。那么,你們可以謹慎地提出新的一整套范式。重建它。
  • 禁止 hardcode,特殊邏輯。如果你發(fā)現(xiàn)特殊邏輯容易實現(xiàn)需求,否則很難。那么,你的架構已經(jīng)出現(xiàn)問題了,你和你的團隊應該深入思考這個問題,而不是輕易加上一個特殊邏輯。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

為測試做設計

現(xiàn)在我們在做'測試左移',讓工程師編寫自動化測試來保證質量。測試工程師的工作更多的是類似 google SET(Software Engineer In Test, 參考《google 軟件測試之道》)的工作。工作重心在于測試編碼規(guī)范、測試編碼流程、測試編碼工具、測試平臺的思考和建設。測試代碼,還是得工程師來做。

為方法寫一個測試的考慮過程,使我們得以從外部看待這個方法,這讓我們看起來是代碼的客戶,而不是代碼的作者。很多同學,就感覺很難受。對,這是必然的。因為你的代碼設計的時候,并沒有把'容易測試'考慮進去,可測試性不強。如果工程師在開發(fā)邏輯的過程中,就同時思考著這段代碼怎樣才能輕松地被測試。那么,這段寫就的代碼,同時可讀性、簡單性都會得到保障,經(jīng)過了良好的設計,而不僅僅是'能工作'。

我覺得,測試獲得的主要好處發(fā)生在你考慮測試及編寫測試的時候,而不是在運行測試的時候!在編碼的時候同時讓思考怎么測試的思維存在,會讓編寫高質量的代碼變得簡單,在編碼時就更多地考慮邊界條件、異常條件,并且妥善處理。僅僅是抱有這個思維,不去真地編寫自動化測試,就能讓代碼的質量上升,代碼架構的能力得到提升。

硬件工程出 bug 很難查,bug 造成的成本很高,每次都要重新做一套模具、做模具的工具。所以硬件工程往往有層層測試,極早發(fā)現(xiàn)問題,盡量保證簡單且質量高。我們可以在軟件上做同樣的事情。與硬件工程師一樣,從一開始就在軟件中構建可測試性,并且嘗試將每個部分連接在一起之前,對他們進行徹底的測試。

這個時候,有人就說,TDD 就是這樣,讓你同時思考編碼架構和測試架構。我對 TDD 的態(tài)度是: 它不一定就是最好的。測試對開發(fā)的驅動,絕對有幫助。但是,就像每次驅動汽車一樣,除非心里有一個目的地,否則就可能會兜圈子。TDD 是一種自底向上的編程方法。但是,適當?shù)臅r候使用自頂向下設計,才能獲得一個最好的整體架構。很多人處理不好自頂向下和自底向上的關系,結果在使用 TDD 的時候發(fā)現(xiàn)舉步維艱、收效甚微。

以及,如果沒有強大的外部驅動力,"以后再測"實際上意味著"永遠不測"。大家,務實一點,在編碼時就考慮怎么測試。不然,你永遠沒有機會考慮了。當面對著測試性低的代碼,需要編寫自動化測試的時候,你會感覺很難受。

盡早測試, 經(jīng)常測試, 自動測試

一旦代碼寫出來,就要盡早開始測試。這些小魚的惡心之處在于,它們很快就會變成巨大的食人鯊,而捕捉鯊魚則相當困難。所以我們要寫單元測試,寫很多單元測試。

事實上,好項目的測試代碼可能會比產(chǎn)品代碼更多。生成這些測試代碼所花費的時間是值得的。從長遠來看,最終的成本會低得多,而且你實際上有機會生產(chǎn)出幾乎沒有缺陷的產(chǎn)品。

另外,知道通過了測試,可以讓你對代碼已經(jīng)"完成"產(chǎn)生高度信心。

項目中使用統(tǒng)一的術語

如果用戶和開發(fā)者使用不同的名稱來稱呼相同的事物,或者更糟糕的是,使用相同的名稱來代指不同的事物,那么項目就很難取得成功。

DDD(Domain-Driven Design)把'項目中使用統(tǒng)一的術語'做到了極致,要求項目把目標系統(tǒng)分解為不同的領域(也可以稱作上下文)。在不同的上下文中,同一個術語名字意義可能不同,但是要項目內統(tǒng)一認識。比如證券這個詞,是個多種經(jīng)濟權益憑證的統(tǒng)稱,在股票、債券、權證市場,意義和規(guī)則是完全不同的。當你第一次聽說'渦輪(港股特有金融衍生品,是一種股權)'的時候,是不是瞬間蒙圈,搞不清它和證券的關系了。買'渦輪'是在買什么鬼證劵?

在軟件領域是一樣的。你需要對股票、債券、權證市場建模,你就得有不同的領域,在每個領域里有一套詞匯表(實體、值對象),在不同的領域之間,同一個概念可能會換一個名字,需要映射。如果你們既不區(qū)分領域,甚至在同一個領域還對同一個實體給出不同的名字。那,你們怎么確保自己溝通到位了?寫成代碼,別人如何知道你現(xiàn)在寫的'證券'這個 struct 具體是指的什么?

不要面向需求編程

需求不是架構;需求無關設計,也非用戶界面;需求就是需要的東西。需要的東西是經(jīng)常變化的,是不斷被探索,不斷被加深認識的。產(chǎn)品經(jīng)理的說辭是經(jīng)常變化的。當你面向需求編程,你就是在依賴一個認識每一秒都在改變的女/男朋友。你將身心俱疲。

我們應該面向業(yè)務模型編程。我在《Code Review 我都 CR 些什么》里也提到了這一點,但是我當時并沒有給出應該怎么去設計業(yè)務模型的指導。我的潛臺詞就是,你還是僅僅能憑借自己的智力和經(jīng)驗,沒有很多方法論工具。

現(xiàn)在,我給你推薦一個工具,DDD(Domain-Driven Design),面向領域驅動設計。它能讓你對業(yè)務更好地建模,讓對業(yè)務建模變成一個可拆解的執(zhí)行步驟,僅僅需要少得多的智力和經(jīng)驗。區(qū)分好領域上下文,思考明白它們之間的關系,找到領域下的實體和值對象,找到和模型貼合的架構方案。這些任務,讓業(yè)務建模變得簡單。

當我們面向業(yè)務模型編程,變更的需求就變成了–提供給用戶他所需要的業(yè)務模型的不同部分。我們不再是在不斷地 change 代碼,而是在不斷地 extend 代碼,逐漸做完一個業(yè)務模型的填空題。

寫代碼要有對于'美'的追求

google 的很多同學說(至少 hankzheng 這么說),軟件工程=科學 藝術。當前騰訊,很多人,不講科學。工程學,計算機科學,都不管。就喜歡搞'巧合式編程'。剛好能工作了,打完收工,交付需求。絕大多數(shù)人,根本不追求編碼、設計的藝術。對細節(jié)的好看,毫無感覺。對于一個空格、空行的使用,毫無邏輯,毫無美感。用代碼和其他人溝通,連基本的整潔、合理都不講。根本沒想過,別人會看我的代碼,我要給代碼'梳妝打扮'一下,整潔大方,美麗動人,還極有內涵。'窈窕淑女,君子好逑',我們應該對別人慷慨一點,你總是得閱讀別人的代碼的。大家都對美有一點追求,就是互相都慷慨一些。

很無奈,我把對美的追求說得這么'卑微'。必須要由'務實的需要'來構建必要性。而不是每個工程師發(fā)自內心的,像對待漂亮的異性、好的音樂、好的電影一樣的發(fā)自內心的需要它。認為代碼也是取悅別人、取悅自己的東西。

如果我們想做一個有尊嚴、有格調的工程師,我們就應該把自己的代碼、勞動的產(chǎn)物,當成一件藝術品去雕琢。務實地追求效率,同時也追求美感。效率產(chǎn)出價值,美感取悅自己。不僅僅是為了一口飯,同時也把工程師的工作當成自己一個快樂的源頭。工作不再是 overhead,而是 happiness。此刻,你做不到,但是應該有這樣的追求。當我們都有了這樣的追求,有一天,我們會能像 google 一樣做到的 。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

換行

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

換行

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

換行

應用程序框架是實現(xiàn)細節(jié)

以下是《整潔架構之道》的原文摘抄:

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

對,DIP 大發(fā)神威。我覺得核心做法就是:

  • 核心代碼應該通過 DIP 來讓它不要和具體框架綁定!它應該使用 DIP(比如代理類),抽象出一個防腐層,讓自己的核心代碼免于腐壞。
  • 選擇一個框架,你不去做防腐層(主要通過 DIP),你就是單方面領了結婚證,你只有義務,沒有權利。同學們要想明白。同學們應該對框架本身是否優(yōu)秀,是否足夠組件化,它本身能否在項目里做到可插拔,做出思考和設計。

trpc-go 對于插件化這事兒,做得還不錯,大家會積極地使用它。trpc-cpp 離插件化非常遠,它自己根本就成不了一個插件,而是有一種要強暴你的感覺,你能憑直覺明顯地感覺到不愿意和它訂終身。例如,trpc-cpp 甚至強暴了你構建、編譯項目的方式。當然,這很多時候是 c 語言本身的問題。

‘解耦’、'插件化’就是 golang 語言的關鍵詞。大家開玩笑說,c 已經(jīng)被委員會玩壞了,加入了太多特性。less is more, more means nothing。c 從來都是讓別的工具來解決自己的問題,trpc-cpp 可能把自己松綁定到 bazel 等優(yōu)秀的構建方案。尋求優(yōu)秀的組件去軟綁定,提供解決方案,是可行的出路。我個人喜歡 rust。但是大家還是熟悉 cpp,我們確實需要一個投入更多人力做得更好的 trpc-cpp。

一切都應該是代碼(通過代碼去顯式組合)

Unix 編程哲學告訴我們: 如果有一些參數(shù)是可變的,我們應該使用配置,而不是把參數(shù)寫死在代碼里。在騰訊,這一點做得很好。但是,大人,現(xiàn)在時代又變了。

J2EE 框架讓我們看到,組件也可以是通過配置 Java Bean 的形式注入到框架里的。J2EE 實現(xiàn)了把組件也配置化的壯舉。但是,時代變了!你下載一個 golang 編譯器,你進入你下載的文件里去看,會發(fā)現(xiàn)你找不到任何配置文件。這是為什么?兩個簡單,但是很多年都被人們忽略的道理:

  • 配置即隱性耦合。配置只有和使用配置的代碼組合使用,它才能完成它的工作。它是通過把'一個事情分開兩個步驟'來換取動態(tài)性。換句話說,它讓兩個相隔十萬八千里的地方產(chǎn)生了耦合!作為工程師,你一開始就要理解雙倍的復雜度。配置如何使用、配置的處理程序會如何解讀配置。
  • 代碼能夠有很強的自解釋能力,工程師們更愿意閱讀可讀性強的代碼,而不是編寫得很爛的配置文檔。配置只能通過厚重的配置說明書去解釋。當你缺乏完備的配置說明書,配置變成了地獄。

golang 的編譯器是怎么做的呢?它會在代碼里給你設定一個通用性較強的默認配置項。同時,配置項都是集中管理的,就像管理配置文件一樣。你可以通過額外配置一個配置文件或者命令行參數(shù),來改變編譯器的行為。這就變成了,代碼解釋了每一個配置項是用來做什么的。只有當你需要的時候,你會先看懂代碼,然后,當你有需求的時候,通過額外的配置去改變一個你有預期的行為。

邏輯變成了。一開始,所有事情都是解耦的。一件事情都只看一塊代碼就能明白。代碼有較好的自解釋性和注解,不再需要費勁地編寫撇腳的文檔。當你明白之后,你需要不一樣的行為,就通過額外的配置來實現(xiàn)。關于怎么配置,代碼里也講明白了。

對于 trpc-go 框架,以及一眾插件,優(yōu)先考慮配置,然后才是代碼去指定,部分功能還只能通過配置去指定,我就很難受。我接受它,就得把一個事情放在兩個地方去完成:

  • 需要在代碼里 import 插件包。
  • 需要在配置文件里配置插件參數(shù)。

既然不能消滅第一步,為什么不能是顯式 import,同時通過代碼 其他自定義配置管理方案去完成插件的配置?當然,插件,直接不需要任何配置,提供代碼 Option 去改變插件的行為,是最香的。這個時候,我就真的能把 trpc 框架本身也當成一個插件來使用了。

封裝不一定是好的組織形式

封裝(Encapsulation),是我上學時剛接觸 OOP,驚為天人的思想方法。但是,我工作了一些年頭了,看過了不知道多少腐爛的代碼。其中一部分還需要我來維護。我看到了很多莫名其妙的封裝,讓我難受至極。封裝,經(jīng)常被濫用。封裝的時候,我們一定要讓自己的代碼,自己就能解釋自己是按照下面的哪一種套路在做封裝:

  • 按層封裝
  • 按功能封裝
  • 按領域封裝
  • 按組件封裝

或者,其他能被命名到某種有價值的類型的封裝。你要能說出為什么你的封裝是必要的,有價值的。必要的時候,你必須要封裝。比如,當你的 golang 函數(shù)達到了 80 行,你就應該對邏輯分組,或者把一塊過于細節(jié)化卻功能單一的較長的代碼獨立到一個函數(shù)。同時,你又不能胡亂封裝,或者過度封裝。是否過度,取決于大家的共識,要 reviwer 能認可你這個封裝是有價值的。當然,你也會成為 reviewer,別人也需要獲得你的認可。缺乏意圖設計的封裝,是破壞性的。這會使其他人在面對這段代碼時,畏首畏尾,不敢修改它。形成一個腐爛的肉塊,并且,這種腐爛會逐漸蔓延開來。

所以,所有細節(jié)都是關鍵的。每一塊磚頭都被精心設計,才能構建一個漂亮的項目!

所有細節(jié)都應該被顯式處理

這是一個顯而易見的道理。但是很多同學卻毫無知覺。我為需要深入閱讀他們編寫的代碼的同學默哀一秒。當有一個函數(shù) func F() error,我僅僅是用 F(),沒有用變量接收它的返回值。你閱讀代碼的時候,你就會想,第一開發(fā)者是忘記了 error handling 了,還是他思考過了,他決定不關注這個返回值?他是設計如此,還是這里是個 bug?他人即地獄,維護代碼的苦難又多了一分。

我們對于自己的代碼可能會給別人帶來困擾的地方,都應該顯式地去處理。就像寫了一篇不會有歧義的文章。如果就是想要忽略錯誤,'_ = F()'搞定。我將來再處理錯誤邏輯,'_ = F() // TODO 這里需要更好地處理錯誤'。在代碼里,把事情講明白,所有人都能快速理解他人的代碼,就能快速做出修改的決策。'猜測他人代碼的邏輯用意'是很難受且困難的,他人的代碼也會在這種場景下,產(chǎn)生被誤讀。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

不能上升到原則的一些常見案例

合理注釋一些并不'通俗'的邏輯和數(shù)值

和'所有細節(jié)都應該被顯式處理'一脈相承。所有他人可能要花較多時間猜測原因的細節(jié),都應該在代碼里提前清楚地講明白。請慷慨一點。也可能,三個月后的將來,是你回來 eat your own dog food。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

習慣留下 TODO

要這么做的道理很簡單。便于所有人能接著你開發(fā)。極有可能就是你自己接著自己開發(fā)。如果沒有標注 TODO 把沒有做完的事情標示出來??赡?,你自己都會搞忘自己有事兒沒做完了。留下 TODO 是很簡單的事情,我們?yōu)槭裁床蛔瞿兀?/span>

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

不要丟棄錯誤信息

即'錯誤傳遞原則'。這里給它換個名字–你不應該主動把很多有用的信息給丟棄了。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

自動化測試要快

在 google,自動化測試是硬性要求在限定時間內跑完的。這從細節(jié)上保障了自動化測試的速度,進而保障了自動化測試的價值和可用性。你真的需要 sleep 這么久?應該認真考量??剂壳宄税言驅懴聛怼.敶蠹野l(fā)現(xiàn)總時長太長的時候,可以選擇其中最不必要的部分做優(yōu)化。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

歷史有問題的代碼, 發(fā)現(xiàn)了問題要及時 push 相關人主動解決

這是'控制軟件的熵是軟件工程的重要任務之一'的表現(xiàn)之一。我們是團隊作戰(zhàn),不是無組織無記錄的部隊。發(fā)現(xiàn)了問題,就及時拋出和解決。讓傷痛更少,跑得更快。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

less is more

less is more. 《Code Review 我都 CR 些什么》強調過了,這里不再強調。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

less is more

如果打了錯誤日志, 有效信息必須充足, 且不過多

和'less is more'一脈相承。同時,必須有的時候,就得有,不能漏。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

日志

注釋要把問題講清楚, 講不清楚的日志等于沒有

是個簡單的道理,和'所有細節(jié)都應該被顯式處理'一脈相承。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

日志

MR 要自己先 review, 不要浪費 reviewer 的時間

你也會成為 reviewer,節(jié)省他人的時間,他人也節(jié)省你的時間。縮短交互次數(shù),提升 review 的愉悅感。讓他人提的 comment 都是'言之有物'的東西,而不是一些反反復復的最基礎的細節(jié)。會讓他人更愉悅,自己在看 comment 的時候,也更愉悅,更愿意去討論、溝通。讓 code review 成為一個技術交流的平臺。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

時間

要尋找合適的定語

這個顯而易見。但是,同學們就是愛放縱自己?

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

定語

不要出現(xiàn)特定 IP,或者把什么可變的東西寫死

這個和'ETC'一脈相承,我覺得也是顯而易見的東西。但是很多同學還是喜歡放縱自己?

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

寫死

使用定語, 不要 1、2、3、4

這個存粹就是放縱自己了。當然,也會有只能用 1、2、3、4 的時候。但是,你這里,是么?多數(shù)時候,都不會是。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

數(shù)字

有必要才使用 init

這,也顯而易見。init 很方便,但是,它也會帶來心智負擔。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

init

要關注 shadow write

這個很重要,看例子就知道了。但是大家常常忽略,特此提一下。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

shadow

能不耦合接收器就別耦合

減少耦合是我們保障代碼質量的重要手段。請把 ETC 原則放在自己的頭上漂浮著,時刻帶著它思考,不要懶惰。熟能生巧,它并不會成為心智負擔。反而常常會在你做決策的時候幫你快速找到方向,提升決策速度。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

接收器

空實現(xiàn)需要注明空實現(xiàn)就是實現(xiàn)

這個和'所有細節(jié)都應該被顯式處理'一脈相承。這個理念,我見過無數(shù)種形式表現(xiàn)出來。這里就是其中一種。列舉這個 case,讓你印象再深刻一點。

程序員進階指南:文檔團隊Golang最佳實踐和CR案例集分享(程序員文檔工具)

空實現(xiàn)

看錯題集沒多少有用, 我們需要教練和傳承

上面我列了很多例子。是我能列出來的例子中的九牛一毛。但是,我列一個非常龐大的錯題集沒有任何用。我也不再例舉更多。只有當大家信奉了敏捷工程的美。認可好的代碼架構對于業(yè)務的價值,才能真正地做到舉一反三,理解無數(shù)例子,能對更多的 case 自己做出合理的判斷。同時,把好的判斷傳播起來,做到"群體免疫",最終做好 review,做好代碼質量。

展望

希望本文能幫助到需要做好 CR、做好編碼,需要培養(yǎng)更多 reviwer 的團隊。讓你門看到很多原則,吸收這些原則和理念。去理解、相信這些理念。在 CR 中把這些理念、原則傳播出去。成為別人的臨時教練,讓大家都成為合格的 reviwer。加強對于代碼的交流,飛輪效應,讓團隊構建好的人才梯度和工程文化。

寫到最后,我發(fā)現(xiàn),我上面寫的這些東西都不那么重要了。你有想把代碼寫得更利于團隊協(xié)作的價值觀和態(tài)度,反而是最重要的事情。上面講的都僅僅是寫高質量代碼的手段和思想方法。當你認可了'應該編寫利于團隊協(xié)作的高質量代碼',并且擁有對'不利于團隊代碼質量的代碼'嫉惡如仇的態(tài)度。你總能找到高質量代碼的寫法。沒有我?guī)湍憧偨Y,你也總會掌握!

拾遺

如果你深入了解 DDD,就會了解到'六邊形架構'、'CQRS(Command Query Responsibility Segregation,查詢職責分離)架構'、'事件驅動架構'等關鍵詞。這是 DDD 構建自己體系的基石,這些架構及是細節(jié)又是頂層設計,也值得了解一下。

版權聲明:本文內容由互聯(lián)網(wǎng)用戶自發(fā)貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權/違法違規(guī)的內容, 請發(fā)送郵件至 舉報,一經(jīng)查實,本站將立刻刪除。

国产精品人人妻人色五月| 最近中文字幕在线的MV| 美女被躁到高潮嗷嗷叫视频| 亚洲国产综合久久久精品| 欧美性大战xxxxx久久久| 亚洲av中文无码乱人伦在线播放| 国产成人免费视频| 真实的和子乱拍视频| 亚洲精品无码久久一线| 丝袜美腿一区二区三区| 亚洲精品国产精品乱码不99| 为夫献身的少妇潭静| 日韩精品久久久久久免费| 免费国产裸体美女视频全黄| 国产熟女一区二区三区五月婷| 麻麻张开腿让我爽了一夜| 亚洲 欧美 自拍 动漫 另类| 高中女学生破苞视频免费| 亚洲欧美成人无码久久久| 麻豆AV无码精品一区二区| 色欲国产精品久久毛片av大全| 国产又a又黄又潮娇喘视频| 免费人成激情视频在线观看冫| 午夜视频在线观看| 国产午夜精品一区二区三区| 亚洲人成色777777精品音频| 精品综合久久久久久98| 国产韩国日本欧美品牌suv | 老熟女高潮喷了一地| 国产综合精品| 国产精品扒开腿做爽爽爽视频| 丰满少妇人妻无码| 国产高潮国产高潮久久久| 18禁白丝喷水视频www视频 | 他揉捏她两乳不停呻吟a片| 日本三级吃奶头添泬| 亚洲成av人综合在线观看| 15学生初次破初视频免费| 无码专区亚洲综合另类| 中文字幕无码他人妻味| 妺妺洗澡忍不住c了她|