mysql在哪里寫代碼_[譯] 如何寫好 Go 代碼

原文:https://scene-si.org/2018/07/24/writing-great-go-code/

我寫了多年的 Go 微服務,并在寫完兩本關于 (API Foundations in Go 和 12 Factor Applications with Docker and Go) 主題的書之后,有了一些關于如何寫好 Go 代碼的想法

但首先,我想給閱讀這篇文章的讀者解釋一點。好代碼是主觀的。你可能對于好代碼這一點,有完全不同的想法,而我們可能只對其中一部分意見一致。另一方面,我們可能都沒有錯,只是我們從兩個角度出發,從而選擇了不同的方式解決工程問題,并不意味著意見不一致的不是好代碼。

包很重要,你可能會反對 - 但是如果你在用 Go 寫微服務,你可以將所有代碼放在一個包中。當然,下面也有一些反對的觀點:

  1. 將定義的類型放入單獨的包中

  2. 維護與傳輸無關的服務層

  3. 在服務層之外,維護一個數據存儲(repository)層

我們可以計算一下,一個微服務包的最小數量是 1。如果你有一個大型的微服務,它擁有 websocket 和 http 網關,你最終可能需要 5 個包(類型,數據存儲,服務,websocket 和 http 包)。

簡單的微服務實際上并不關心從數據存儲層(repository),或者從傳輸層(websocket,http)抽離業務邏輯。你可以寫簡單的代碼,轉換數據然后響應,也是可以運行的。但是,添加更多的包可以解決一些問題。例如,如果你熟悉 ?SOLID 原則,S 代表單一職責。如果我們拆分成包,這些包就可以是單一職責的。

  • types - 聲明一些結構,可能還有一些結構的別名等

  • repository - 數據存儲層,用來處理存儲和讀取結構

  • service - 服務層,包裝存儲層的具體業務邏輯實現

  • http, websocket, … - 傳輸層,用來調用服務層

當然,根據你使用的情況,還可以進一步細分,例如,可以使用types/requesttypes/response 來更好的分隔一些結構。這樣就可以擁有 request.Messageresponse.Message 而不是 MessageRequestMessageResponse。如果一開始就像這樣拆分開,可能會更有意義。

但是,為了強調最初的觀點 - 如果你只用了這些聲明包中的一部分,也沒什么影響。像 Docker 這樣的大型項目在 server 包下只使用了 types ?包,這是它真正需要的。它使用的其他包(像 errors 包),可能是第三方包。

同樣需要注意的是,在一個包中,共享正在處理的結構和函數會很容易。如果你有相互依賴的結構,將它們拆分為兩個或多個不同的包可能會導致鉆石依賴問題。解決方案也很顯然 - 將代碼放到一塊兒,或者將所有代碼放在一個包中。

到底選哪一個呢?兩種方法都行。如果我非要按規則來的話,將其拆分更多的包可能會使添加新代碼變得麻煩。因為你可能要修改這些包才能添加單個 API 調用。如果不是很清楚如何布局,那么在包之間跳轉可能會帶來一些認知上的開銷。在很多情況下,如果項目只有一兩個包,閱讀代碼會更容易。

你肯定也不想要太多的小包。

錯誤

如果是描述性的 Errors 可能是開發人員檢查生產問題的唯一工具。這就是為什么我們要優雅地處理錯誤,要么將它們一直傳遞到應有程序的某一層,如果錯誤無法處理,該層就接收錯誤并記錄下來,這一點非常重要。以下是標準庫錯誤類型缺少的一些特性:

  • 錯誤信息不含堆棧跟蹤

  • 不能堆積錯誤

  • errors 是預實例化的

但是,通過使用第三方錯誤包(我最喜歡的是pkg/Errors.))可以幫助解決這些問題。也有其他的第三方錯誤包,但是這個是 Dave Cheney (Go 語言大神)編寫的,它在錯誤處理的方式在一定程度上是一種標準。他的文章 Don’t just check errors, handle them gracefully 是推薦必讀的。

錯誤的堆棧跟蹤

pkg/errors 包在調用 errors.New 時,會將上下文(堆棧跟蹤)添加到新建的錯誤中。

users_test.go:34:?testing?error?Hello?world
????????github.com/crusttech/crust/rbac_test.TestUsers
????????????????/go/src/github.com/crusttech/crust/rbac/users_test.go:34
????????testing.tRunner
????????????????/usr/local/go/src/testing/testing.go:777
????????runtime.goexit
????????????????/usr/local/go/src/runtime/asm_amd64.s:2361

考慮到完整的錯誤信息是 "Hello world",使用 fmt.Printf 帶有%+v 的參數或者類似的方式來打印少量的上下文 - 對于查找錯誤的而言,是一件很棒的事。你可以確切知道是哪里創建了錯誤(關鍵字)。當然,當涉及到標準庫時,errors 包和本地 error 類型 - 不提供堆棧跟蹤。但是,使用 pkg/errors 可以很容易地添加一個。例如:

resp,?err?:=?u.Client.Post(fmt.Sprintf(resourcesCreate,?resourceID),?body)
if?err?!=?nil?{
????????return?errors.Wrap(err,?"request?failed")
}

在上面這個例子中,pkg/errors包將上下文添加 err 中,加的錯誤消息("request failed") 和堆棧跟蹤都會拋出來。通過調用 errors.Wrap 來添加堆棧跟蹤,所以你可以精準追蹤到此行的錯誤。

堆積錯誤

你的文件系統,數據庫,或者其他可能拋出相對不太好描述的錯誤。例如,Mysql 可能會拋出這種強制錯誤:

ERROR?1146?(42S02):?Table?'test.no_such_table'?doesn't?exist

這不是很好處理。然而,你可以使用 errors.Wrap(err,"database aseError") 在上面堆積新的錯誤。這樣,就可以更好地處理 "databaseError" 等。pkg/errors 包將在 causer 接口后面保留實際的錯誤信息。

type?causer?interface?{
???????Cause()?error
}

這樣,錯誤堆積在一起,不會丟失任何上下文。附帶說一下,mysql 錯誤是一個類型錯誤,其背后包含的不僅僅是錯誤字符串的信息。這意味著它有可能被處理的更好:

if?driverErr,?ok?:=?err.(*mysql.MySQLError);?ok?{
????if?driverErr.Number?==?mysqlerr.ER_ACCESS_DENIED_ERROR?{
????????//?Handle?the?permission-denied?error
????}
}

此例子來自于 this stackoverflow thread。

錯誤預實例化

究竟什么是錯誤(error)呢?非常簡單,錯誤需要實現下面的接口:

type?error?interface?{
????Error()?string
}

net/http 的例子中,這個包將幾種錯誤類型暴露為變量,如文檔所示。在這里添加堆棧跟蹤是不可能的(Go不允許對全局 var 聲明可執行代碼,只能進行類型聲明)。其次,如果標準庫將堆棧跟蹤添加到錯誤中 - 它不會指向返回錯誤的位置,而是指向聲明變量(全局變量)的位置。

這意味著,你仍然需要在后面的代碼中強制調用類似于 ?return errors.WithStack(ErrNotSupported) 的代碼。這也不是很痛苦,但不幸的是,你不能只導入 pkg/errors ,就讓所有現有的錯誤都帶有堆棧跟蹤。如果你還沒有使用 errors.New 來實例化你的錯誤,那么它需要一些手動調用。

日志

接下來是日志,或者更恰當的說,結構化日志。這里提供了許多軟件包,類似于 sirupsen/logrus 或我最喜歡的APEX/LOG。這些包也支持將日志發送到遠程的機器或者服務,我們可以用工具來監控這些日志。

當談到標準日志包時,我不常看到的一個選項是創建一個自定義 logger,并將 log.LShorfilelog.LUTC 等標志傳遞給它,以再次獲得一點上下文,這能讓你的工作變輕松 - 尤其在處理不同時區的服務器時。

const?(
????????Ldate?????????=?1?<iota?????//?the?date?in?the?local?time?zone:?2009/01/23
????????Ltime?????????????????????????//?the?time?in?the?local?time?zone:?01:23:23
????????Lmicroseconds?????????????????//?microsecond?resolution:?01:23:23.123123.??assumes?Ltime.
????????Llongfile?????????????????????//?full?file?name?and?line?number:?/a/b/c/d.go:23
????????Lshortfile????????????????????//?final?file?name?element?and?line?number:?d.go:23.?overrides?Llongfile
????????LUTC??????????????????????????//?if?Ldate?or?Ltime?is?set,?use?UTC?rather?than?the?local?time?zone
????????LstdFlags?????=?Ldate?|?Ltime?//?initial?values?for?the?standard?logger
)

即使你沒有創建自定義 logger,你也可以使用 SetFlags 來修改默認 logger。(playground link):

package?main

import?(
????"log"
)

func?main()?{
????log.SetFlags(log.LstdFlags?|?log.Lshortfile)
????log.Println("Hello,?playground")
}

結果如下:

2009/11/10?23:00:00?main.go:9:?Hello,?playground

你不想知道你在哪里打印了日志嗎?這會讓跟蹤代碼變得更容易。

接口

如果你正在寫接口并命名接口中的參數,請考慮以下的代碼片段:

type?Mover?interface?{
????Move(context.Context,?string,?string)?error
}

你知道這里的參數代表什么嗎?只需要在接口中使用命名參數就可以讓它很清晰。

type?Mover?interface?{
????Move(context.Context,?source?string,?destination?string)
}

我還經常看到一些使用一個具體類型作為返回值的接口。一種未得到充分利用的做法是,根據一些已知的結構體或接口參數,以某種方式聲明接口,然后在接收器中填充結果。這可能是 Go 中最強大的接口之一。

type?Filler?interface?{
????Fill(r?*http.Request)?error
}

func?(s?*YourStruct)?Fill(r?*http.Request)?error?{
????//?here?you?write?your?code...
}

更可能的是,一個或多個結構體可以實現該接口。如下:

type?RequestParser?interface?{
????Parse(r?*http.Request)?(*types.ServiceRequest,?error)
}

此接口返回具體類型(而不是接口)。通常,這樣的代碼會使你代碼庫中的接口變得雜亂無章,因為每個接口只有一個實現,并且在你的應用包結構之外會變得不可用。

小帖士

如果你希望在編譯時確保你的結構體符合并完全實現一個接口(或多個接口),你可以這么做:

var?_?io.Reader?=?&YourStruct{}
var?_?fmt.Stringer?=?&YourStruct{}

如果你缺少這些接口所需的某些函數,編譯器就會報錯。字符 _ 表示丟棄變量,所以沒有副作用,編譯器完全優化了這些代碼,會忽視這些被丟棄的行。

空接口

與上面的觀點相比,這可能是更有爭議的觀點 - 但是我覺得使用 interface{} 有時非常有效。在 HTTP API 響應的例子中,最后一步通常是 json 編碼,它接收一個接口參數:

func?(enc?*Encoder)?Encode(v?interface{})?error

因此,完全可以避免將 API 響應設置成具體類型。我并不建議對所有情況都這么處理,但是在某些情況下,可以在 API 中完全忽略響應的具體類型,或者至少說明具體類型聲明的意義。腦海中浮現的一個例子是使用匿名結構體。

body?:=?struct?{
????Username?string???`json:"username"`
????Roles????[]string?`json:"roles,omitempty"`
}{username,?roles}

首先,不使用 interface{} 的話,無法從函數里返回這種結構體。顯然,json 編碼器可以接受任何類型的內容,因此,按傳遞空接口(對我來說)是完全有意義的。雖然趨勢是聲明具體類型,但有時候你可能不需要一層中間層。對于包含某些邏輯并可能返回各種形式的匿名結構體的函數,空接口也很合適。

更正:匿名結構體不是不可能返回,只是做起來很麻煩:playground

  • 感謝 @Ikearens at Discord Gophers #golang channel

第二個用例是數據庫驅動的 API 設計,我之前寫過一些有關內容,我想指出的是,實現一個完全由數據庫驅動的 API 是非常可能的。這也意味著添加和修改字段是僅僅在數據庫中完成的,而不會以 ORM 的形式添加額外的間接層。顯然,你仍然需要聲明類型才能在數據庫中插入數據,但是從數據庫中讀取數據可以省略聲明。

//?getThread?fetches?comments?by?data,?order?by?ID
func?(api?*API)?getThread(params?*CommentListThread)?(comments?[]interface{},?err?error)?{
????//?calculate?pagination?parameters
????start?:=?params.PageNumber?*?params.PageSize
????length?:=?params.PageSize
????query?:=?fmt.Sprintf("select?*?from?comments?where?news_id=??and?self_id=??and?visible=1?and?deleted=0?order?by?id?%s?limit?%d,?%d",?params.Order,?start,?length)
????err?=?api.db.Select(&comments,?query,?params.NewsID,?params.SelfID)
????return
}

同樣,你的應用程序可能充當反向代理,或者只使用無模式(schema-less)的數據庫存儲。在這些情況下,目的只是傳遞數據。

一個大警告(這是你需要輸入結構體的地方)是,修改 Go 中的接口值并不是一件容易的事。你必須將它們強制轉換為各種內容,如 map、slice 或結構體,以便可以在訪問這些返回的數據。如果你不能保持結構體一成不變,而只是將它從 DB(或其他后端服務)傳遞到 JSON 編碼器(會涉及到斷言成具體類型),那么顯然這個模式不適合你。這種情況下不應該存在這樣的空接口代碼。也就是說,當你不想了解任何關于載荷的信息時,空接口就是你需要的。

代碼生成

盡可能使用代碼生成。如果你想生成用于測試的 mock,如果你想生成 proc/GRPC 代碼,或者你可能擁有的任何類型的代碼生成,可以直接生成代碼并提交。在發生沖突的情況下,可以隨時將其丟棄,然后重新生成。

唯一可能的例外是提交類似于 public_html 文件夾的內容,其中包含你將使用 rakyll/statik 打包的內容。如果有人想告訴我,由 gomock 生成的代碼在每次提交時都會以兆字節的數據污染 GIT 歷史記錄?不會的。

結束語

關于 Go 的最佳實踐和最差實踐的另一本值得注意的好書應該是Idiomatic Go。
如果你不熟悉的話,可以閱讀一下 - 它是與本文很好的搭配。

我想在這里引用Jeff Atwood post - The Best Code is No Code At All文章的一句話,這是一句令人難忘的結束語:

如果你真的喜歡寫代碼,你會非常喜歡盡可能少地寫代碼。

但是,一定要編寫那些單元測試。完結

6b15f9dfea4864312f05d2a30bb9ba20.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532138.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532138.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532138.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學畫畫軟件app推薦_路由器管理軟件哪個好?6款路由器管理APP推薦_軟件評測

無限網絡應用越來越廣泛&#xff0c;由此應運而生了許多可以蹭網的軟件&#xff0c;家里的網速突然變慢了&#xff0c;也許就是隔壁的小哥哥小姐姐在蹭網絡&#xff0c;那么如何避免被蹭網&#xff1f;今天小編給各位小伙伴推薦幾款路由器管理軟件&#xff0c;發現網絡變慢了&a…

408計算機考研 各科題目題號,2021考研408計算機專業基礎綜合數據結構試題特點分析...

2021考研408計算機專業基礎綜合數據結構試題特點分析2021考研初試結束后&#xff0c;新東方在線考研網為各位考研考生梳理"2021考研408計算機專業基礎綜合數據結構試題特點分析"內容&#xff0c;同時新東方在線考研各研究院院長針對2021考研各科目試題變化及難度進行…

各個圖標的意思_冬奧體育圖標設計團隊負責人林存真:每一個圖標要畫100稿以上...

林存真中央美術學院設計學院副院長、北京冬奧組委文化活動部形象景觀藝術總監、北京冬奧會體育圖標設計團隊負責人。12月31日晚&#xff0c;北京2022年冬奧會和冬殘奧會體育圖標正式發布。在體育圖標發布前夕&#xff0c;新京報記者采訪了北京冬奧會體育圖標設計團隊的負責人&a…

delphi制作上下開幕效果_2019中超聯賽揭幕戰在深圳舉行 現場開幕式亮點多多

3月2日晚&#xff0c;2019中超聯賽開幕式在深圳大運中心體育場舉行。隨著中國足協黨委書記杜兆才正式宣布2019年中超聯賽開幕&#xff0c;以“超精彩”為口號的2019新賽季中超聯賽正式打響。本賽季中超聯賽是出臺“注資帽”、“薪酬帽”、“獎金帽”、“轉會帽”之后的第一個賽…

ae正在發生崩潰_AE錯誤:正在發生崩潰的解決方法,原創問題解決方案

AE在使用過程中,可能會出現一些問題。有的問題是莫名其妙出現。例如剛剛還在正常使用AE軟件,下一次再打開的時候就會出現問題。今天給大家說的是如何去解決after effects錯誤:正在發生崩潰這個問題 。該問題所提供的解決方法為實際操作過,并成功解決。所以才寫出來給大家提…

計算機二級公共,計算機二級公共基礎知識

計算機二級公共基礎知識計算機二級考試包括計算機基礎知識。雖然分值不高但是我們還是要把握好每一分。下面百分網小編整理了相關計算機二級公共基礎知識&#xff0c;希望大家喜歡。計算機二級公共基礎知識1.1棧和隊列1、棧及其基本運算棧是限定在一端進行插入與刪除運算的線性…

echarts map 點擊地圖區域變色_繪制炫酷的地圖,不只是pyecharts.map!

導讀&#xff1a;地圖可視化是一種非常直觀的數據分析結果展現形式&#xff0c;python有很多可視化庫可以實現&#xff0c;pyecharts就是很多python愛好者喜愛的實現地圖可視化方法之一。不可否認&#xff0c;pyecharts繪制的地圖實現方便、圖形美觀而且支持交互&#xff0c;但…

金蝶kis專業版公網訪問_金蝶KIS云專業版—【賬務處理】進階操作101問

對KIS專業版【賬務處理】模塊日常操作之外的各種問題進行回答&#xff0c;幫助老師們快速進階此模塊的操作&#xff0c;提高軟件的便利性。1.專業版資產負債表如何移動表頁位置&#xff1f;【操作步驟】 1、單擊【報表與分析】-【資產負債表】&#xff1b;2、單擊左上角菜單欄【…

奧鵬東師計算機應用基礎18,免費在線作業答案奧鵬東師計算機應用基礎15秋在線作業1試卷及答案(1)...

奧鵬東師計算機應用基礎15秋在線作業1試卷及答案(1)一、單選題(共25道試題&#xff0c;共62.5分。)1.在Excel 中保存的工作簿默認的文件擴展名是()。A. XLSB. DOCC. DBFD. TXT正確答案&#xff1a;A2.中文Windows 2000的“桌面”是指()。A. 整個屏幕B. 某個窗口2015奧鵬作業答案…

es集群搭建_滴滴Elasticsearch 集群跨版本升級與平臺重構之路

前不久&#xff0c;滴滴ES團隊將維護的30多個ES集群&#xff0c;3500多個ES節點&#xff0c;8PB的數據&#xff0c;從2.3.3跨大版本無縫升級到6.6.1。在對用戶查詢寫入基本零影響和改動的前提下&#xff0c;解決了ES跨大版本協議不兼容、文件格式不兼容、mapping不兼容等難題&a…

電子工程可以報考二建_非工程類專業也能報考二建嗎?

非工程類專業也能報考二建嗎&#xff1f;2020年非工程類專業能考二級建造師的省份匯總整理&#xff01;2020年二級建造師考試報名公告陸續公布中&#xff0c;目前江西、陜西、江蘇三省公布了報名時間&#xff0c;其他省份報名時間暫時未確定。四川省已經受疫情影響推遲五月的考…

計算機考試中英文打字題,計算機信息技術(五筆及中英文打字測試試題)

計算機信息技術(五筆及中英文打字測試試題) (14頁)本資源提供全文預覽&#xff0c;點擊全文預覽即可全文預覽,如果喜歡文檔就下載吧&#xff0c;查找使用更方便哦&#xff01;14.9 積分第一章基本知識習題答案一、填空題1. 計算機信息高新技術考試劃分為五、四、三、二、_ 5個等…

pil python 安裝_20行Python代碼給微信頭像戴帽子

作者 | Leauky&#xff0c;北理工碩士在讀&#xff0c;非CS專業的Python愛好者。朋友圈里微信官方要求戴圣誕帽的活動曾經火爆一時&#xff0c;有些會玩的小伙伴都悄咪咪地用美圖秀秀一類的 app 給自己頭像 p 一頂&#xff0c;然后可高興地表示“哎呀好神奇hhhh”&#xff0c;呆…

arcgis 屬性表 匯總_Arcgis中遙感影像地理配準、矢量化與地圖制作

目的&#xff1a;將遙感圖像進行地理配準、矢量化&#xff0c;并且制作地圖。要求&#xff1a;對的遙感圖像進行地理配準&#xff1b;矢量化建筑物、綠地、道路、水體等主要地物要素&#xff1b;對各類地物要素進行符號化設置并對其名稱進行標注&#xff1b;添加指北針、比例尺…

怎么查看計算機的系統內存大小,Windows10系統怎么查看電腦內存大小

很多用戶在升級到windows10系統之后&#xff0c;因為很多界面和操作都跟之前的Windows系統不一樣&#xff0c;所以很多操作都不知道要如何下手&#xff0c;比如想要查看電腦內存大小的時候卻不知道要怎么操作&#xff0c;其實方法很簡單&#xff0c;下面給大家介紹一下Windows1…

java類初始化順序_《To Be a Better Javaer》-- Java 基礎篇 vol.2:面向對象

Java是面向對象的高級編程語言&#xff0c;面向對象的特征如下&#xff1a;面向對象具有抽象、封裝、繼承、多態等特性&#xff1b;面向對象可以將復雜的業務邏輯簡單化&#xff0c;增強代碼復用性&#xff1b;面向對象是一種常見的思想&#xff0c;比較符合人們的思考習慣。面…

計算機開機時間停在上次關機,怎么在電腦開機的時候查看上次關機前的操作

滿意答案wencai242013.09.05采納率&#xff1a;51% 等級&#xff1a;12已幫助&#xff1a;12606人1.看計算機在哪天運行過~運行了多久!(系統安裝在c盤)找到c:\windows\SchedLgU.txt文件 里面有你自這個系統產生以來曾經工作過的時間&#xff0c;包括哪天開了機 開機時間 關機…

php 遞歸實現無限極分類和排序_PHP實現選擇排序

這次說說選擇排序。 選擇排序&#xff08;Selection sort&#xff09;是一種簡單直觀的排序算法。它的工作原理如下。首先在未排序序列中找到最小&#xff08;大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然后&#xff0c;再從剩余未排序元素中繼續尋找…

idea for循環快捷鍵_IDEA騷技巧,編碼速度至少快一倍

IDEA是目前市場上最好用的IDE&#xff0c;公認的&#xff01;前幾年eclipse在市場上非常流行&#xff0c;因此大多數人都習慣了eclipse的一些快捷鍵。近年來&#xff0c;隨著IDEA的興起&#xff0c;很多人都放棄了exlipse&#xff0c;進而選擇了IDEA&#xff0c;但是有些人習慣…

從物聯網到 3D 打印:硬件相關的開源項目概覽 | 開源專題 No.52

arendst/Tasmota Stars: 20.4k License: GPL-3.0 Tasmota 是一款為 ESP8266 和 ESP32 設備提供的替代固件&#xff0c;具有易于配置的 webUI、OTA 更新、定時器或規則驅動的自動化功能以及通過 MQTT、HTTP、串口或 KNX 進行完全本地控制。該項目主要特點包括&#xff1a; 支持…