国产粉嫩无码不卡在线观看,酒店大战丝袜高跟鞋人妻,特级精品毛片免费观看,欧美亚洲日本国产综合在线

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

綜述

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

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

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

知名架構(gòu)原則

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

細(xì)節(jié)即是架構(gòu)

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

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

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

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

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

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

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

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

讓目錄結(jié)構(gòu)自解釋

ETC 價(jià)值觀(easy to change)

ETC 是一種價(jià)值觀念,不是一條原則。價(jià)值觀念是幫助你做決定的: 我應(yīng)該做這個(gè),還是做那個(gè)?當(dāng)你在軟件領(lǐng)域思考時(shí),ETC 是個(gè)向?qū)?,它能幫助你在不同的路線中選出一條。就像其他一些價(jià)值觀念一樣,你應(yīng)該讓它漂浮在意識(shí)思維之下,讓它微妙地將你推向正確的方向。

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

DRY 原則(don not repeat yourself)

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

'正交性'是幾何學(xué)中的術(shù)語(yǔ)。我們的代碼應(yīng)該消除不相關(guān)事物之間的影響。這是一給簡(jiǎn)單的道理。我們寫代碼要'高內(nèi)聚、低耦合',這是大家都在提的。

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

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

全局 map case

單例就是全局變量

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

可逆性原則

可逆性原則是很少被提及的一個(gè)原則??赡嫘裕褪悄阕龀龅呐袛?,最好都是可以被逆轉(zhuǎn)的。再換一個(gè)容易懂的說(shuō)法,你最好盡量少認(rèn)為什么東西是一定的、不變的。比如,你認(rèn)為你的系統(tǒng)永遠(yuǎn)服務(wù)于,用 32 位無(wú)符號(hào)整數(shù)(比如 QQ 號(hào))作為用戶標(biāo)識(shí)的系統(tǒng)。你認(rèn)為,你的持久化存儲(chǔ),就選型 SQL 存儲(chǔ)了。當(dāng)這些一開始你認(rèn)為一定的東西,被推翻的時(shí)候,你的代碼卻很難去 change,那么,你的代碼就是可逆性做得很差。書里有一個(gè)例證,我覺得很好,直接引用過(guò)來(lái)。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

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

依賴倒置原則(DIP)

DIP 原則太重要了,我這里單獨(dú)列一節(jié)來(lái)講解。我這里只是簡(jiǎn)單的講解,講解它最原始和簡(jiǎn)單的形態(tài)。依賴倒置原則,全稱是 Dependence Inversion Principle,簡(jiǎn)稱 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 不再關(guān)心 Botton interface 里定義的 TurnOn、TurnOff 的實(shí)現(xiàn)細(xì)節(jié)。實(shí)現(xiàn)了解耦。這里,我們能看到 struct UI 需要被注入(inject)一個(gè) Botton interface 才能邏輯完整。所以,DIP 經(jīng)常換一個(gè)名字出現(xiàn),叫做依賴注入(Dependency Injection)。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

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

將知識(shí)用純文本來(lái)保存

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

契約式設(shè)計(jì)

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

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

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

盡早崩潰

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

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

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

解耦代碼讓改變?nèi)菀?/strong>

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

  • 不相關(guān)的 pkg 之間古怪的依賴關(guān)系
  • 對(duì)一個(gè)模塊進(jìn)行的'簡(jiǎn)單'修改,會(huì)傳播到系統(tǒng)中不相關(guān)的模塊里,或是破壞了系統(tǒng)中的其他部分
  • 開發(fā)人員害怕修改代碼,因?yàn)樗麄儾淮_定會(huì)造成什么影響
  • 會(huì)議要求每個(gè)人都必須參加,因?yàn)闆]有人能確定誰(shuí)會(huì)受到變化的影響

只管命令不要詢問(wèn)

看看如下三段代碼:

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)}

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

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

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

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

不要鏈?zhǔn)秸{(diào)用方法

看下面的例子:

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

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

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

繼承稅(多用組合)

繼承就是耦合。不僅子類耦合到父類,以及父類的父類等,而且使用子類的代碼也耦合到所有祖先類。 有些人認(rèn)為繼承是定義新類型的一種方式。他們喜歡設(shè)計(jì)圖表,會(huì)展示出類的層次結(jié)構(gòu)。他們看待問(wèn)題的方式,與維多利亞時(shí)代的紳士科學(xué)家們看待自然的方式是一樣的,即將自然視為須分解到不同類別的綜合體。 不幸的是,這些圖表很快就會(huì)為了表示類之間的細(xì)微差別而逐層添加,最終可怕地爬滿墻壁。由此增加的復(fù)雜性,可能使應(yīng)用程序更加脆弱,因?yàn)樽兏赡茉谠S多層次之間上下波動(dòng)。 因?yàn)橐恍┲档蒙倘兜脑~義消歧方面的原因,C 在20世紀(jì)90年代玷污了多重繼承的名聲。結(jié)果,許多當(dāng)下的OO語(yǔ)言都沒有提供這種功能。

因此,即使你很喜歡復(fù)雜的類型樹,也完全無(wú)法為你的領(lǐng)域準(zhǔn)確地建模。

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

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

面向過(guò)程,面向?qū)ο螅瘮?shù)式編程。三種編程結(jié)構(gòu)的核心區(qū)別,是在不同的方向限制程序員,來(lái)做到好的代碼結(jié)構(gòu)(引自《架構(gòu)整潔之道》):

  • 結(jié)構(gòu)化編程是對(duì)程序控制權(quán)的直接轉(zhuǎn)移的限制。
  • 面向?qū)ο笫菍?duì)程序控制權(quán)的間接轉(zhuǎn)移的限制。
  • 函數(shù)式編程是對(duì)程序中賦值操作的限制。

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

golang 的 interface 是 duck interface,把 DIP 原則更進(jìn)一步,不需要顯式 implement/extend interface,就能做到 DIP。golang 使用結(jié)構(gòu)化編程范式,卻有面向?qū)ο缶幊谭妒降暮诵膬?yōu)點(diǎn),甚至簡(jiǎn)化了。這是一個(gè)基于高度抽象理解的極度精巧的設(shè)計(jì)。google 把 abstraction 這個(gè)設(shè)計(jì)理念發(fā)揮到了極致。曾經(jīng),J2EE 的 container(EJB, Java Bean)設(shè)計(jì)是國(guó)內(nèi) Java 程序員引以為傲'架構(gòu)設(shè)計(jì)'、'厲害的設(shè)計(jì)'。

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

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

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

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

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

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

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

緘默原則

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

  • 不要在正常 case 下打印日志。
  • 不要在單元測(cè)試?yán)锸褂?fmt 標(biāo)準(zhǔn)輸出,至少不要提交到 master。
  • 不打不必要的日志。當(dāng)錯(cuò)誤出現(xiàn)的時(shí)候,會(huì)非常明顯,我們能第一時(shí)間反應(yīng)過(guò)來(lái)并處理。
  • 讓調(diào)試的日志停留在調(diào)試階段,或者使用較低的日志級(jí)別,你的調(diào)試信息,對(duì)其他人根本沒有價(jià)值。
  • 即使低級(jí)別日志,也不能泛濫。不然,日志打開與否都沒有差別,日志變得毫無(wú)價(jià)值。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

緘默

錯(cuò)誤傳遞原則

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

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,理由包含如下內(nèi)容:

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 和微軟的文章中,我們不難總結(jié)出以下幾點(diǎn)衍生的結(jié)論:

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

我們看到了異常的優(yōu)缺點(diǎn)。上面第二點(diǎn)提到的信息傳遞,是很有價(jià)值的一點(diǎn)。golang 在 1.13 版本中拓展了標(biāo)準(zhǔn)庫(kù),支持了Error Wrapping也是承認(rèn)了 error 傳遞的價(jià)值。

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

額外說(shuō)一點(diǎn),注意不要把整個(gè)鏈路的錯(cuò)誤信息帶到公司外,帶到用戶的瀏覽器、native 客戶端。至少不能直接展示給用戶看到。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

錯(cuò)誤鏈

SOLID

SOLID 原則,是由以下幾個(gè)原則的集合體:

  • SRP: 單一職責(zé)原則
  • OCP: 開閉原則
  • LSP: 里氏替換原則
  • ISP: 接口隔離原則
  • DIP: 依賴反轉(zhuǎn)原則

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

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

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

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

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

// 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中拿到關(guān)鍵的登陸態(tài)等有效信息 var session common.BaseSession common.GetBaseSessionFromCookie(ctx, &session) // 校驗(yàn)登陸態(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}

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

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

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

Unix 哲學(xué)基礎(chǔ)

《Code Review 我都 CR 些什么》講解了很多 Unix 的設(shè)計(jì)哲學(xué)。這里不再贅述,僅僅列舉一下。大家自行閱讀和參悟,并且運(yùn)用到編碼、review 活動(dòng)中。

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

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

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

偏執(zhí)

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

控制軟件的熵是軟件工程的重要任務(wù)之一

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

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

為測(cè)試做設(shè)計(jì)

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

為方法寫一個(gè)測(cè)試的考慮過(guò)程,使我們得以從外部看待這個(gè)方法,這讓我們看起來(lái)是代碼的客戶,而不是代碼的作者。很多同學(xué),就感覺很難受。對(duì),這是必然的。因?yàn)槟愕拇a設(shè)計(jì)的時(shí)候,并沒有把'容易測(cè)試'考慮進(jìn)去,可測(cè)試性不強(qiáng)。如果工程師在開發(fā)邏輯的過(guò)程中,就同時(shí)思考著這段代碼怎樣才能輕松地被測(cè)試。那么,這段寫就的代碼,同時(shí)可讀性、簡(jiǎn)單性都會(huì)得到保障,經(jīng)過(guò)了良好的設(shè)計(jì),而不僅僅是'能工作'。

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

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

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

以及,如果沒有強(qiáng)大的外部驅(qū)動(dòng)力,"以后再測(cè)"實(shí)際上意味著"永遠(yuǎn)不測(cè)"。大家,務(wù)實(shí)一點(diǎn),在編碼時(shí)就考慮怎么測(cè)試。不然,你永遠(yuǎn)沒有機(jī)會(huì)考慮了。當(dāng)面對(duì)著測(cè)試性低的代碼,需要編寫自動(dòng)化測(cè)試的時(shí)候,你會(huì)感覺很難受。

盡早測(cè)試, 經(jīng)常測(cè)試, 自動(dòng)測(cè)試

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

事實(shí)上,好項(xiàng)目的測(cè)試代碼可能會(huì)比產(chǎn)品代碼更多。生成這些測(cè)試代碼所花費(fèi)的時(shí)間是值得的。從長(zhǎng)遠(yuǎn)來(lái)看,最終的成本會(huì)低得多,而且你實(shí)際上有機(jī)會(huì)生產(chǎn)出幾乎沒有缺陷的產(chǎn)品。

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

項(xiàng)目中使用統(tǒng)一的術(shù)語(yǔ)

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

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

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

不要面向需求編程

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

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

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

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

寫代碼要有對(duì)于'美'的追求

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

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

換行

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

換行

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

換行

應(yīng)用程序框架是實(shí)現(xiàn)細(xì)節(jié)

以下是《整潔架構(gòu)之道》的原文摘抄:

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

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

trpc-go 對(duì)于插件化這事兒,做得還不錯(cuò),大家會(huì)積極地使用它。trpc-cpp 離插件化非常遠(yuǎn),它自己根本就成不了一個(gè)插件,而是有一種要強(qiáng)暴你的感覺,你能憑直覺明顯地感覺到不愿意和它訂終身。例如,trpc-cpp 甚至強(qiáng)暴了你構(gòu)建、編譯項(xiàng)目的方式。當(dāng)然,這很多時(shí)候是 c 語(yǔ)言本身的問(wèn)題。

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

一切都應(yīng)該是代碼(通過(guò)代碼去顯式組合)

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

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

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

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

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

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

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

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

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

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

  • 按層封裝
  • 按功能封裝
  • 按領(lǐng)域封裝
  • 按組件封裝

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

所以,所有細(xì)節(jié)都是關(guān)鍵的。每一塊磚頭都被精心設(shè)計(jì),才能構(gòu)建一個(gè)漂亮的項(xiàng)目!

所有細(xì)節(jié)都應(yīng)該被顯式處理

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

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

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

和'所有細(xì)節(jié)都應(yīng)該被顯式處理'一脈相承。所有他人可能要花較多時(shí)間猜測(cè)原因的細(xì)節(jié),都應(yīng)該在代碼里提前清楚地講明白。請(qǐng)慷慨一點(diǎn)。也可能,三個(gè)月后的將來(lái),是你回來(lái) eat your own dog food。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

習(xí)慣留下 TODO

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

不要丟棄錯(cuò)誤信息

即'錯(cuò)誤傳遞原則'。這里給它換個(gè)名字–你不應(yīng)該主動(dòng)把很多有用的信息給丟棄了。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

自動(dòng)化測(cè)試要快

在 google,自動(dòng)化測(cè)試是硬性要求在限定時(shí)間內(nèi)跑完的。這從細(xì)節(jié)上保障了自動(dòng)化測(cè)試的速度,進(jìn)而保障了自動(dòng)化測(cè)試的價(jià)值和可用性。你真的需要 sleep 這么久?應(yīng)該認(rèn)真考量??剂壳宄税言?qū)懴聛?lái)。當(dāng)大家發(fā)現(xiàn)總時(shí)長(zhǎng)太長(zhǎng)的時(shí)候,可以選擇其中最不必要的部分做優(yōu)化。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

歷史有問(wèn)題的代碼, 發(fā)現(xiàn)了問(wèn)題要及時(shí) push 相關(guān)人主動(dòng)解決

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

less is more

less is more. 《Code Review 我都 CR 些什么》強(qiáng)調(diào)過(guò)了,這里不再?gòu)?qiáng)調(diào)。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

less is more

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

日志

注釋要把問(wèn)題講清楚, 講不清楚的日志等于沒有

是個(gè)簡(jiǎn)單的道理,和'所有細(xì)節(jié)都應(yīng)該被顯式處理'一脈相承。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

日志

MR 要自己先 review, 不要浪費(fèi) reviewer 的時(shí)間

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

時(shí)間

要尋找合適的定語(yǔ)

這個(gè)顯而易見。但是,同學(xué)們就是愛放縱自己?

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

定語(yǔ)

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

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

寫死

使用定語(yǔ), 不要 1、2、3、4

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

數(shù)字

有必要才使用 init

這,也顯而易見。init 很方便,但是,它也會(huì)帶來(lái)心智負(fù)擔(dān)。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

init

要關(guān)注 shadow write

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

shadow

能不耦合接收器就別耦合

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

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

接收器

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

這個(gè)和'所有細(xì)節(jié)都應(yīng)該被顯式處理'一脈相承。這個(gè)理念,我見過(guò)無(wú)數(shù)種形式表現(xiàn)出來(lái)。這里就是其中一種。列舉這個(gè) case,讓你印象再深刻一點(diǎn)。

程序員進(jìn)階指南:文檔團(tuán)隊(duì)Golang最佳實(shí)踐和CR案例集分享(程序員文檔工具)

空實(shí)現(xiàn)

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

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

展望

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

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

拾遺

如果你深入了解 DDD,就會(huì)了解到'六邊形架構(gòu)'、'CQRS(Command Query Responsibility Segregation,查詢職責(zé)分離)架構(gòu)'、'事件驅(qū)動(dòng)架構(gòu)'等關(guān)鍵詞。這是 DDD 構(gòu)建自己體系的基石,這些架構(gòu)及是細(xì)節(jié)又是頂層設(shè)計(jì),也值得了解一下。

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