Go的高效開發(fā)套路
作者:EdwardQ
來源:SegmentFault 思否社區(qū)
背景
當(dāng)前在公司進(jìn)行Go服務(wù)端研發(fā)工作時(shí),發(fā)現(xiàn)缺少Go開發(fā)的最佳實(shí)踐,而導(dǎo)致以下現(xiàn)象
用Go開發(fā)時(shí)會比較迷茫,不知如何下手,怎么開展工作比較高效。
重復(fù)造輪子比較嚴(yán)重。
項(xiàng)目的代碼質(zhì)量參差不齊,導(dǎo)致交付的產(chǎn)品質(zhì)量參差不齊。
產(chǎn)品運(yùn)行黑盒,可觀測性差,能跑就行。
代碼實(shí)現(xiàn)考驗(yàn)研發(fā)人員水平,但頂尖的畢竟是少數(shù),往往比較差,而且頂尖也說不準(zhǔn)會犯錯(cuò)。
一個(gè)人負(fù)責(zé)整個(gè)功能開發(fā),一旦人員離職,代碼維護(hù)就會難上艱難。
而我在進(jìn)行產(chǎn)品研發(fā)工作時(shí),意識到后續(xù)產(chǎn)品的功能會愈加復(fù)雜,不注重這些問題的話,后續(xù)恐怕出現(xiàn)代碼愈加難以維護(hù)、產(chǎn)品功能難以改動、用戶需求無法快速滿足的多米諾效應(yīng)。
為了盡早制止該現(xiàn)象的發(fā)生,探索一套可以在Go領(lǐng)域進(jìn)行最佳實(shí)踐的范式,從而達(dá)到以下效果:
代碼易維護(hù),起碼有兩個(gè)人熟知代碼實(shí)現(xiàn)目標(biāo),也容易進(jìn)行代碼迭代更新。
代碼設(shè)計(jì)合理,避免過于奇葩、過度以及不全面的實(shí)現(xiàn)。
產(chǎn)品質(zhì)量保證,交付產(chǎn)品可觀測性強(qiáng)、Bug率低。
降低重復(fù)造輪子現(xiàn)象,專注于業(yè)務(wù)邏輯開發(fā),提升研發(fā)效率。
故此從研發(fā)效能方面研究解法,探索從個(gè)人開發(fā)行為到團(tuán)隊(duì)協(xié)作上的提升,最終在研發(fā)代碼質(zhì)控以及研發(fā)框架兩個(gè)方面去實(shí)踐各種方法,最終總結(jié)出當(dāng)前的高效開發(fā)的模式,并持續(xù)優(yōu)化沉淀中,希望通過實(shí)踐驗(yàn)證該模式的效果,推廣至所有使用Go進(jìn)行服務(wù)端開發(fā)的場景實(shí)現(xiàn),提高整個(gè)團(tuán)隊(duì)研發(fā)效能。
模式簡介
本模式有三個(gè)部分組成,分別為標(biāo)準(zhǔn)化研發(fā)規(guī)范、契約協(xié)作以及統(tǒng)一的開發(fā)框架。
標(biāo)準(zhǔn)化研發(fā)規(guī)范旨在研發(fā)人員在進(jìn)行代碼開發(fā)時(shí)能夠從代碼編程風(fēng)格、Git提交規(guī)范、Review機(jī)制能達(dá)成共識契約并嚴(yán)格遵守,為產(chǎn)品的質(zhì)量負(fù)責(zé)。
契約協(xié)作旨在研發(fā)人員與外部需要調(diào)用其服務(wù)的合作伙伴,形成以契約為準(zhǔn),雙方工作開展不互相強(qiáng)依賴具體實(shí)現(xiàn)的協(xié)作模式,降低雙方溝通成本。
統(tǒng)一的開發(fā)框架旨在約束研發(fā)人員使用統(tǒng)一的框架開發(fā),沉淀各種場景實(shí)現(xiàn)用于優(yōu)化并演進(jìn)框架,達(dá)到開發(fā)時(shí)可快速復(fù)用場景、持續(xù)迭代優(yōu)化各場景實(shí)現(xiàn)以及為未來更多場景做堅(jiān)實(shí)的積累。
模式實(shí)踐-Go服務(wù)端開發(fā)
當(dāng)前在實(shí)際開發(fā)業(yè)務(wù)中都有相應(yīng)的實(shí)踐
標(biāo)準(zhǔn)化研發(fā)規(guī)范
代碼編程規(guī)范
在接手一些組件的開發(fā)時(shí),看到代碼的實(shí)現(xiàn)上存在風(fēng)格迥異,閱讀理解上比較困難,所以定下約束風(fēng)格的編程規(guī)范,可以提升代碼的可閱讀性,代碼的易維護(hù)性。
在探索Go代碼編程規(guī)范中,業(yè)界有EffectiveGo和UberGoGuide等比較知名的編程規(guī)范。
EffectiveGo作為Go官方出品的編程規(guī)范,幾乎所有使用Go語言開發(fā)的程序員都有看過并從中獲益,普適性高。
UberGoGuide是業(yè)界中使用Go語言開發(fā)的佼佼者,其開源了很多高質(zhì)量的開源軟件,比如Zap;它所開源的編程規(guī)范也是在EffectiveGo以及其他編程規(guī)范作為基礎(chǔ)上去擴(kuò)展。
從綜合考慮上,Uber有中文翻譯版上手速度快,且其公司按照規(guī)范開源的軟件質(zhì)量實(shí)現(xiàn)也側(cè)面證明了該規(guī)范的優(yōu)越性,所以選擇UberGoGuide作為指導(dǎo)編程規(guī)范,但并不作為唯一參考標(biāo)準(zhǔn),也考慮可以根據(jù)實(shí)際運(yùn)行的最佳實(shí)踐作為相應(yīng)變動。
Git提交規(guī)范
在團(tuán)隊(duì)協(xié)作開發(fā)組件時(shí),查看到Git的提交信息很隨意,僅僅簡單描述幾句,但完全不知道實(shí)際做了什么。在回溯代碼問題時(shí),不知道這個(gè)提交做了什么,對代碼維護(hù)上造成一定阻礙。估計(jì)參考業(yè)界優(yōu)秀統(tǒng)一規(guī)范Conventional Commit 1.0進(jìn)行約束。
Conventional Commit 1.0,是一種規(guī)范提交信息的輕量級規(guī)約,它提供了一些易于理解的規(guī)則用于指導(dǎo)我們?nèi)绾尉帉慶ommit信息,因此這也很容易被機(jī)器進(jìn)行解讀,結(jié)合SemVer版本規(guī)范,可以很容易通過工具既能自動化管理版本號,也進(jìn)一步約束開發(fā)人員對提交信息的嚴(yán)謹(jǐn)性,畢竟如果亂打commit信息會直接影響到版本的制定。
因此在實(shí)踐中上,嚴(yán)格遵守Conventional Commit 1.0來進(jìn)行提交信息的規(guī)定,對于提交信息不明確者也可以拒絕其代碼提交。
Review機(jī)制
在原先的團(tuán)隊(duì)合作模式中,大部分是各自開發(fā)相應(yīng)的模塊并直接提交到Master分支,然后就發(fā)布了,這樣的做法雖然速度很快但交付質(zhì)量卻很堪憂,畢竟它很考驗(yàn)研發(fā)人員的綜合水平,但畢竟不是人人都是神,所以它無法避免到缺陷的數(shù)量增長,不僅對整體代碼質(zhì)量堪憂,甚至對交付產(chǎn)品的質(zhì)量也無法保證,更甚會影響到整個(gè)團(tuán)隊(duì)的口碑。為了解決這些問題,引入CodeReview機(jī)制來管制代碼合并到代碼交付的過程,從而管控整個(gè)代碼質(zhì)量。
CodeView機(jī)制參與的人員角色有代碼提交者(Commiter)以及代碼審核者(Reviewer),代碼提交者每次提交代碼后,均需要由代碼審核者進(jìn)行代碼,審核通過后才能合并代碼進(jìn)主分支,從而達(dá)到可發(fā)布的可能性。
這樣的機(jī)制實(shí)行下,需要可以做到以下方面效果:
使研發(fā)人員在每次提交中,有其他視角去驗(yàn)證代碼實(shí)現(xiàn)方案、解決思路,保證一定的客觀性。
擔(dān)任Reviewr角色的人員,需要在過程做到識別bugs、是否有邏輯問題還有是否有覆蓋全邊緣場景,這樣可以保證實(shí)現(xiàn)的思路足夠全面。
其好處列舉如下:
知識共享:團(tuán)隊(duì)內(nèi)部在進(jìn)行Review就能知道代碼的實(shí)現(xiàn)、使用的設(shè)計(jì)等進(jìn)行共享,有利于團(tuán)隊(duì)代碼水平的提高。
可以更早的發(fā)現(xiàn)bug:避免在功能推出后才發(fā)現(xiàn)Bug,做到及時(shí)止損。
確保合規(guī)性:程序員的背景不同導(dǎo)致各自的代碼風(fēng)格不同,但通過規(guī)范標(biāo)準(zhǔn)約束項(xiàng)目代碼的合規(guī)性。
增強(qiáng)安全性:當(dāng)有安全技術(shù)背景的專業(yè)人員參加到review中時(shí),安全性的等級是非常高的。
增強(qiáng)團(tuán)隊(duì)合作意識:當(dāng)團(tuán)隊(duì)成員一起去解決一個(gè)問題,這樣有利于提升他們各自的OwnerShip思維以及團(tuán)隊(duì)的歸屬感。
當(dāng)前也需要做到以下約定:
Reviewr和Commiter在一次提交上,嚴(yán)格意義上不能為同一個(gè)人。
Comment時(shí)需要是善意的就事論事,雙方均需要以案例和嚴(yán)謹(jǐn)?shù)倪壿嬐魄萌ミM(jìn)行討論,而且只有Reviewr同意后才能終止Comment。
提交代碼前需要自身已經(jīng)經(jīng)過編譯以及測試。
一次提交盡量做到核心代碼不超過400行的。
一次Review盡量低于1小時(shí)。
需要知會到Reviewr清晰知道這次提交的實(shí)現(xiàn)目的,最終期望是什么。
如果害怕提交的代碼寫的不好,那就將代碼實(shí)現(xiàn)達(dá)到自身認(rèn)可的程度先。
單次提交如果代碼量過大或邏輯復(fù)雜可以設(shè)置兩個(gè)以上的Reviewer進(jìn)行Review。
Reviewer和Commiter存在連帶責(zé)任,Commiter提交的代碼在生產(chǎn)出現(xiàn)問題并出現(xiàn)價(jià)值損失時(shí),Reviewer也需要承擔(dān)次要責(zé)任。
Review機(jī)制很容易會被忽視并繞過,但其后果有時(shí)候是很慘痛的,所以需要團(tuán)隊(duì)人員一致認(rèn)可并遵守,才能做得好。
契約協(xié)作
PB文件既契約模式
在開發(fā)人員進(jìn)行API開發(fā)到完成后,往往都需要提供文檔給別人去調(diào)用,這樣的做法有以下缺點(diǎn):
接口文檔交付緩慢,接口需求方需要等待接口文檔給出才能進(jìn)行開發(fā),這樣對于需求方來說會更加開發(fā)風(fēng)險(xiǎn)會增加。
接口更新不及時(shí),當(dāng)開發(fā)人員在更改API時(shí),90%的概率會忘記更新文檔,導(dǎo)致第三方在調(diào)用時(shí)出現(xiàn)很多狀況需要溝通,這樣增加雙方的溝通成本,降低了雙方的開發(fā)效率。
為了解決以上問題,推行Protobuf文件(以下簡稱PB文件)既契約的模式。
該模式遵循Google API 指南,實(shí)現(xiàn)了對應(yīng)通信協(xié)議支持,并且遵守了 gRPC API 使用 HTTP 映射功能進(jìn)行 JSON/HTTP 的支持。因此可以做到通過定義PB文件即可定義出REST和RPC API,通過類似GoogleAPI的倉庫方式進(jìn)行API Schema的管理。
再者結(jié)合豐富的Protoc插件可以自動化Swagger文檔或生成不同語言類型的代碼,如下:
Go HTTP API:通過protoc-gen-go-http插件進(jìn)行生成Go版的Http相關(guān)代碼
Go gRPC API:通過protoc-gen-go-grpc插件生成Go版的gPRC相關(guān)代碼
Swagger文檔:通過protoc-gen-openapiv2插件生成
因此,研發(fā)人員通過PB文件即可清晰知道API的定義,參數(shù)、返回內(nèi)容等bin并能依據(jù)PB文件生成相應(yīng)的代碼(Server和Client)就可進(jìn)行各自的開發(fā),也可以直接生成Swagger文檔來查看使用,極大的降低了溝通的成本。
該模式也需要遵守以下規(guī)則:
新契約變更需要通知到對方
已運(yùn)行一段時(shí)間的舊契約遇到BreakChange需求,應(yīng)考慮新起契約,不應(yīng)在原有基礎(chǔ)改動。
新契約定制時(shí)需先通過需求方Review后,再繼續(xù)開發(fā),避免返工,也保證需求實(shí)現(xiàn)準(zhǔn)確性。
契約盡量以清晰簡潔明了的結(jié)構(gòu)體定義為優(yōu)先,對于特殊需求再使用Map、PBValue等。
統(tǒng)一的開發(fā)框架
對于框架的入門使用有另外一篇文章進(jìn)行指導(dǎo)
開發(fā)框架選型
在過往的編程經(jīng)驗(yàn)中,對一個(gè)框架的積累沉淀有助于提升整體的開發(fā)效能,畢竟前人栽樹后人乘涼,前人已經(jīng)摸清楚框架的使用,就有足夠的信心指導(dǎo)后人以此進(jìn)行開發(fā)、優(yōu)化框架代碼以及建立起整套的周邊生態(tài),可以有余力應(yīng)付未來的復(fù)雜業(yè)務(wù)場景。
因當(dāng)前團(tuán)隊(duì)人力有限僅4人,即使有心也無力去做到自研框架這種需要大量腦力心力且需要積累沉淀的工作,于是放棄自研框架,選擇借力開源產(chǎn)品。
在從框架選型上,從當(dāng)前團(tuán)隊(duì)需求場景考量,團(tuán)隊(duì)需要的是一套可以約束實(shí)現(xiàn)規(guī)范、生態(tài)完善、用戶自主性強(qiáng)、后臺性能要求不高以及支持微服務(wù)化的框架。
在Go這邊選型中預(yù)選了GVA、Gin和Kratos作為比對并進(jìn)行實(shí)踐,總結(jié)GVA、Gin以及Kratos三者比對如下
Gin, 為一個(gè)基礎(chǔ)輕量級高性能的Web框架,實(shí)現(xiàn)場景案例較多,但代碼實(shí)現(xiàn)無約束規(guī)范需要靠開發(fā)者自己摸索,普適性較強(qiáng)。
GVA,是一個(gè)前后端配套的框架,主要解決是快速開發(fā)web應(yīng)用,但框架代碼實(shí)現(xiàn)上并沒有很好的理論指導(dǎo)以及規(guī)范約束,整體使用后的代碼質(zhì)量較差難維護(hù),應(yīng)對復(fù)雜業(yè)務(wù)需求能力差,最讓人詬病的是濫用的公共變量。
Kratos,bilibli業(yè)務(wù)驗(yàn)證出品,配套健全的微服務(wù)框架,設(shè)計(jì)上遵循整潔架構(gòu)以及DDD思想,實(shí)現(xiàn)的模塊耦合度低,用戶自主性強(qiáng),應(yīng)對復(fù)雜的業(yè)務(wù)需求能力強(qiáng),但總體設(shè)計(jì)理念上傾向于標(biāo)準(zhǔn)規(guī)范定制,整體性能方面中上,需自己擴(kuò)展模塊實(shí)現(xiàn)更高性能。

最終采用Kratos作為統(tǒng)一的開發(fā)框架,具體決策依據(jù)如下
多協(xié)議:默認(rèn)支持HTTP/gRPC,可自主按照規(guī)范定制擴(kuò)展,支持采用gin來實(shí)現(xiàn)transport核心。
API契約理念:支持PB文件定義,可生成服務(wù)端、客戶端代碼以及Swagger文檔,并提供在線實(shí)時(shí)文檔功能。
DDD思想分層開發(fā):指導(dǎo)開發(fā)人員進(jìn)行開發(fā)時(shí),能按照領(lǐng)域驅(qū)動的方式進(jìn)行開發(fā),讓功能更加聚焦,實(shí)現(xiàn)更加精煉,且耦合度低,比如將ClickHouse替換為SLS時(shí)只需要改動基礎(chǔ)設(shè)施層實(shí)現(xiàn)即可。
框架結(jié)構(gòu)模塊化清晰:幾乎涵蓋微服務(wù)框架開發(fā)中所涉及的模塊,比如認(rèn)證、注冊、自監(jiān)控等,并這些模塊均可自定義實(shí)現(xiàn),適配性強(qiáng)。
高擴(kuò)展性的中間件設(shè)計(jì):極強(qiáng)擴(kuò)展性的中間件設(shè)計(jì),靈活擴(kuò)展各種業(yè)務(wù)所需中間件模塊。
框架代碼質(zhì)量:總體代碼質(zhì)量上層,模塊依賴解耦分明,用戶閱讀上手容易,自主定制優(yōu)化可行。
性能方面:現(xiàn)在業(yè)務(wù)暫時(shí)或?qū)?~2年均以Web后端應(yīng)用以及微服務(wù)為主,所以性能要求方面不苛刻,且如需高性能時(shí)也可自主實(shí)現(xiàn)擴(kuò)展模塊實(shí)現(xiàn)。
開發(fā)框架沉淀
當(dāng)前組內(nèi)已在Kratos框架有一定的積累,為后續(xù)提高后端開發(fā)提高很好的基礎(chǔ),具體如下:
Layout快速開發(fā):依靠Kratos的Layout能力,定制符合自身的Layout模板,在后續(xù)開發(fā)中可以快速使用,當(dāng)前Layout實(shí)現(xiàn)上已包含各層的寫法模板、Prom指標(biāo)集成、Opentelemetry全鏈路集成、本地緩存實(shí)現(xiàn)等。
自定義API文檔信息的寫法指導(dǎo):已充分踩坑如何豐富API文檔,對于任何自定義信息需求均能滿足。
快速使用Kratos開發(fā)的指南:配合Layout,助力快速上手框架,快速進(jìn)行業(yè)務(wù)開發(fā)。
通過在線API文檔生成前端SDK:通過Swagger-gen快速生成前端SDK,前端無需手寫SDK,可維護(hù)強(qiáng)。
之后也會沉淀更多的場景到框架和文檔中,豐富框架的使用場景,以助力開發(fā)提效。
實(shí)踐效果
當(dāng)前將該模式在內(nèi)部項(xiàng)目中實(shí)踐,有以下效果提升
Bug情況:在經(jīng)過次實(shí)踐后代碼級別的bug數(shù)量從項(xiàng)目開始到現(xiàn)在低于10個(gè),需求級別Bug數(shù)量低于15個(gè)。
前后端協(xié)作效率:前后端當(dāng)前溝通協(xié)商基本只在需求協(xié)商、協(xié)議定義和實(shí)際聯(lián)調(diào)階段進(jìn)行協(xié)商,整體交付功能速度均低于2個(gè)星期。
特性完成速度:后端服務(wù)交付實(shí)現(xiàn)特性,均低于5天一周期,項(xiàng)目進(jìn)展卡點(diǎn)不在后端實(shí)現(xiàn)上。
團(tuán)隊(duì)合作氛圍:review機(jī)制的加入,讓團(tuán)隊(duì)成員更加富有凝聚力以及更愿意做知識分享,提升了團(tuán)隊(duì)人員整體能力。
交付服務(wù)自監(jiān)控完善:落地全鏈路監(jiān)控,提升前后端全鏈路可觀測性,定位問題低于5分鐘內(nèi)。
總體的效果是符合預(yù)期設(shè)計(jì)的,但還有許多優(yōu)化的空間比如API管理、Error規(guī)范、CICD標(biāo)準(zhǔn)化,將繼續(xù)沉淀優(yōu)化該模式,提升后續(xù)特性以及未來新項(xiàng)目的交付速率,提升開發(fā)人員幸福感,降低開發(fā)成本。
Reference
https://github.com/uber-go/guide/blob/master/style.md
https://go.dev/doc/effective_go
https://www.conventionalcommits.org/en/v1.0.0/
https://semver.org/
https://www.bookstack.cn/read/API-design-guide/API-design-guide-08.md
https://google.aip.dev/
https://developers.google.com/protocol-buffers/docs/style
https://developers.google.com/protocol-buffers/docs/proto3
https://colobu.com/2017/03/16/Protobuf3-language-guide/
https://go-kratos.dev/docs/
https://about.gitlab.com/topics/version-control/what-is-code-review/
https://www.perforce.com/blog/qac/9-best-practices-for-code-review

