Go風格指南
精讀:
以下是一些額外的原則,以及重申重要問題。
何時panic?
Effective Go提到不要panic。申請方的代碼允許panic,但必須嚴格依照如下準則:
panic可以發生在程序初始化時間。
初始化完成以後,panic不能讓服務器崩潰。可以在HTTP Handler內的邏輯panic。任何這些panic將會以500返回結果。
panic只能是由於兩種事情發生:
錯誤的原因是軟件工程師的判斷錯誤,比如程序員相信某個struct可以marshal成json,不可能出錯,那麼萬一出錯,可以panic。
錯誤是由於核心服務出錯,比如從couchdb或者elasticsearch查詢結果,服務器返回異常結果,這個時候可以panic。
- panic不能由於以下原因:
數據錄入不正確。
最終用戶提供的參數不正確。
任何其他不是程序員直接引發的錯誤。
總體而言,導致panic的錯誤必須是軟件工程師或者運維工程師可以修復的錯誤。
如果你不確定,不要panic。
空slice聲明
當你要創建一個slice,並不知道它的長度的話使用如下風格:
var someSlice []MyStruct
for ... {
someSlice = append(someSlice, ...)
}
不要使用someSlice := []MyStruct{}或者someSlice := make(...)(除非你能預判大小)。
這樣的好處是如果這個slice最後是0長度(沒有任何append),我們省了一次內存分配。
函數參數太多的時候,考慮寫Params struct。
例如
func GetMajorList(province, types, atProvince, fieldOfStudyKey, batch, slug string, year, offset, limit, minimun, maximum int) { ... }
這個函數接受很多參數,而且類型都一樣。這樣調用的時候,參數的位置很容易搞錯,也不容易讀懂。 可以改成
type GetMajorListParam struct {
Province string
Types string
AtProvince string
FieldOfStudyKey string,
Batch string
Slug string
Year int
Offset int
... }
func GetMajorList(p GetMajorListParam) { ... }
不要喫掉錯誤。
如果一個函數返回有錯誤,你必須選擇其中一種:
把錯誤作爲結果從函數返回。
panic,見上文討論何時panic。
如果是數據錄入錯誤,可以考慮調用sentry.DataError。
如果是後臺go rountine,可以調用sentry.Error。
在handler中, return lu.Error(err)
如果錯誤是預計得到的,比如預計到某個條碼可能在數據庫不存在,正確檢查以後,進行相應處理。
以下處理方法就是喫掉錯誤:
完全不檢查錯誤。
fmt.Print(err),然後不管。
glog.Error(err),然後不管。
tr.Print,然後不管。
其他這裏沒提到的,同時上面“你必須選擇其中一種”也沒提到的。
如何log
glog.Info, glog.Error, glog.Warning,log.Print, fmt.Print: 調試的時候可以隨便用,但是提交代碼審查前,要把代碼刪掉(不要註釋掉)。 如果一個程序啓動期間只會執行少數幾次,可以用glog記錄一些事件。不要用glog 在handler裏面記錄東西。如果要記錄,可以用trace記錄。 glov.V(1):如果經常需要調試的,又想提交到代碼裏,這個可以用。開發環境會顯示V(1)記錄的信息。 另外,可以使用trace.Debugf來log一些調試信息,這會記錄到trace,同時記錄到V(1),這樣可以在開發環境看log,生產環境看trace。
啓動go routine(可能後期加在go基礎庫中)
不要直接用go xxx啓動goroutine。而是使用concurrent.Go. 原因是直接用go xxx會有如下問題:
如果沒有recover,該go routine內的panic會導致整個服務器退出。
很容易忘記go routine外的trace不能傳入go routine內,導致錯誤。
concurrent.Go幫助正確的進行recover,以及建立trace。
如何使用context.Context
從07/16/2017開始trace.T被棄用。取而代之的是context.Context。trace的功能還有保留,但是在base/trace這個包內部被實現, 外部調用全部使用context.Context。
更變如下:
之前tr trace.T,現在ctx context.Context。
之前當作參數傳入的tr,現在傳入ctx。
之前tr.Printf(msg string, args...), 現在trace.Printf(ctx, msg string, args...)
之前tr.SetError(),現在trace.MarkFailed(ctx)
之前trace.EventLog被棄用,現在使用通用trace。
需要注意的:
命名: context.Context 需要被命名爲ctx。
參數: ctx context.Context 需要是函數的第一個參數。(如果不是,lint也會提醒你改成第一個)
和之前的tr一樣ctx不能在goroutine之間傳遞。單獨的goroutine必須建立單獨的context,具體如何創建參見"如何建立context"
除少數特例之外,context需要作爲參數在函數間傳遞,不要儲存在struct內部。
如何得到context:
如果在lu handler裏面,直接添加ctx context.Context參數。
如果在graphql resolve裏面,調用p.(*gql.Params).Ctx或者p.(graphql.ResolveParams).Context。之前的buddy.Tr被棄用。
如何建立context:
使用concurrent.Go,它會建立帶trace的context。直接調用它建立的context.
建立不帶trace的context,使用context.Background()。這個相當於trace.Noop
手動建立帶trace的context, 使用trace.WithTrace(context.Background(), name string)。name參數的格式是family/method,如果不用/分割,family爲name, method爲"run"。 這個相當於之前的trace.New(family, method string)。
如果caller函數需要context, 而call site還沒有context傳入,可以用context.TODO()。這個相當於之前的trace.TODO。記得之後重構函數將context正確傳入。
何時使用jsonutil.Merge() (可能後期加在go基礎庫中)
需要注意的: 大多數情況不推薦使用merge方法,當你使用merge方式時,對相應字段並沒有驗證,這樣會導致數據的一系列問題。
當graphql傳入的struct字段較少時,可不使用merge,最優做法應爲將model各字段值取出覈對後將數據賦值保存
當字段較多時,應先覈對在使用merge方法,或者前端傳入完整的struct,覈對後保存
在dynamicconfig/spec.go中有使用Validate相關的代碼,可對Schema進行驗證後進行merge
使用ES搜索時須知
創建indexer時應對dynamic設置相應值,如下: { "mappings": { "my_type": { "dynamic": "false", "properties": { "title": { "type": "string"}, } } } }
當 Elasticsearch 遇到文檔中以前未遇到的字段,它用 dynamic mapping 來確定字段的數據類型並自動把新的字段添加到類型映射, 有時這是想要的行爲有時又不希望這樣。所以可對dynamic進行配置: true 動態添加新的字段--缺省 false 忽略新的字段 strict 如果遇到新字段拋出異常
可接受的選項爲以上字段,當沒有特殊要求時應將其設置成false
同上也應對all進行設置 _all字段是把所有其它字段中的值,以空格爲分隔符組成一個大字符串,然後被分析和索引,但是不存儲。 也就是說它能被查詢,但不能被取回顯示。all字段在查詢時佔用更多的CPU和佔用更多的磁盤空間, 如果確實不需要它可以完全的關閉它或者基於每字段定製。 例如: { "mappings": { "type_1": { "properties": {...} }, "type_2": { "_all": { "enabled": false }, "properties": {...} } } }
type_1中的all字段是enabled,type_2中的all字段是disabled,所以一般不特別使用時,將enabled設置成false
參考文檔:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html
篩選slice中元素
for _, term := range terms {
if ... {
continue
}
if ...{
continue
}
validTerms = append(validTerms, term)
}
關於正則表達式的使用
儘量不使用正則表達式,如果使用的話,可以聲明全局變量var reg = regexp.MustCompile(具體內容)(相當於一個常量),提高代碼運行速度
關於字符串的拼接
如果出現大量字符串的拼接,應使用bytes.Buffer 例如a=a+b 可以寫成 buf:= bytes.NewBufferString(a) buf.WriteString(b) a=buf.String()
創建Map
創建一個map時 當多個不同的key會對應相同的value時,建議value的類型使用指針(例如:entity—>*entity)。 這樣好處是,相同的Entity只要有個一個,不同的key通過指針指過去,節省內存。
交流q裙:1007576722