Go風格指南 Go風格指南

Go風格指南

精讀:

以下是一些額外的原則,以及重申重要問題。

何時panic?

Effective Go提到不要panic。申請方的代碼允許panic,但必須嚴格依照如下準則:

  • panic可以發生在程序初始化時間。

  • 初始化完成以後,panic不能讓服務器崩潰。可以在HTTP Handler內的邏輯panic。任何這些panic將會以500返回結果。

  • panic只能是由於兩種事情發生:

  1. 錯誤的原因是軟件工程師的判斷錯誤,比如程序員相信某個struct可以marshal成json,不可能出錯,那麼萬一出錯,可以panic。

  2. 錯誤是由於核心服務出錯,比如從couchdb或者elasticsearch查詢結果,服務器返回異常結果,這個時候可以panic。

  • panic不能由於以下原因:
  1. 數據錄入不正確。

  2. 最終用戶提供的參數不正確。

  3. 任何其他不是程序員直接引發的錯誤。

  • 總體而言,導致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) { ... } 

不要喫掉錯誤。

如果一個函數返回有錯誤,你必須選擇其中一種:

  1. 把錯誤作爲結果從函數返回。

  2. panic,見上文討論何時panic。

  3. 如果是數據錄入錯誤,可以考慮調用sentry.DataError。

  4. 如果是後臺go rountine,可以調用sentry.Error。

  5. 在handler中, return lu.Error(err)

  6. 如果錯誤是預計得到的,比如預計到某個條碼可能在數據庫不存在,正確檢查以後,進行相應處理。

以下處理方法就是喫掉錯誤:

  1. 完全不檢查錯誤。

  2. fmt.Print(err),然後不管。

  3. glog.Error(err),然後不管。

  4. tr.Print,然後不管。

  5. 其他這裏沒提到的,同時上面“你必須選擇其中一種”也沒提到的。

如何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會有如下問題:

  1. 如果沒有recover,該go routine內的panic會導致整個服務器退出。

  2. 很容易忘記go routine外的trace不能傳入go routine內,導致錯誤。

concurrent.Go幫助正確的進行recover,以及建立trace。

如何使用context.Context

從07/16/2017開始trace.T被棄用。取而代之的是context.Context。trace的功能還有保留,但是在base/trace這個包內部被實現, 外部調用全部使用context.Context。

更變如下:

  1. 之前tr trace.T,現在ctx context.Context。

  2. 之前當作參數傳入的tr,現在傳入ctx。

  3. 之前tr.Printf(msg string, args...), 現在trace.Printf(ctx, msg string, args...)

  4. 之前tr.SetError(),現在trace.MarkFailed(ctx)

  5. 之前trace.EventLog被棄用,現在使用通用trace。

需要注意的:

  1. 命名: context.Context 需要被命名爲ctx。

  2. 參數: ctx context.Context 需要是函數的第一個參數。(如果不是,lint也會提醒你改成第一個)

  3. 和之前的tr一樣ctx不能在goroutine之間傳遞。單獨的goroutine必須建立單獨的context,具體如何創建參見"如何建立context"

  4. 除少數特例之外,context需要作爲參數在函數間傳遞,不要儲存在struct內部。

如何得到context:

  1. 如果在lu handler裏面,直接添加ctx context.Context參數。

  2. 如果在graphql resolve裏面,調用p.(*gql.Params).Ctx或者p.(graphql.ResolveParams).Context。之前的buddy.Tr被棄用。

如何建立context:

  1. 使用concurrent.Go,它會建立帶trace的context。直接調用它建立的context.

  2. 建立不帶trace的context,使用context.Background()。這個相當於trace.Noop

  3. 手動建立帶trace的context, 使用trace.WithTrace(context.Background(), name string)。name參數的格式是family/method,如果不用/分割,family爲name, method爲"run"。 這個相當於之前的trace.New(family, method string)。

  4. 如果caller函數需要context, 而call site還沒有context傳入,可以用context.TODO()。這個相當於之前的trace.TODO。記得之後重構函數將context正確傳入。

何時使用jsonutil.Merge() (可能後期加在go基礎庫中)

需要注意的: 大多數情況不推薦使用merge方法,當你使用merge方式時,對相應字段並沒有驗證,這樣會導致數據的一系列問題。

  1. 當graphql傳入的struct字段較少時,可不使用merge,最優做法應爲將model各字段值取出覈對後將數據賦值保存

  2. 當字段較多時,應先覈對在使用merge方法,或者前端傳入完整的struct,覈對後保存

  3. 在dynamicconfig/spec.go中有使用Validate相關的代碼,可對Schema進行驗證後進行merge

使用ES搜索時須知

  1. 創建indexer時應對dynamic設置相應值,如下: { "mappings": { "my_type": { "dynamic": "false", "properties": { "title": { "type": "string"}, } } } }

  2. 當 Elasticsearch 遇到文檔中以前未遇到的字段,它用 dynamic mapping 來確定字段的數據類型並自動把新的字段添加到類型映射, 有時這是想要的行爲有時又不希望這樣。所以可對dynamic進行配置: true 動態添加新的字段--缺省 false 忽略新的字段 strict 如果遇到新字段拋出異常

  3. 可接受的選項爲以上字段,當沒有特殊要求時應將其設置成false

  4. 同上也應對all進行設置 _all字段是把所有其它字段中的值,以空格爲分隔符組成一個大字符串,然後被分析和索引,但是不存儲。 也就是說它能被查詢,但不能被取回顯示。all字段在查詢時佔用更多的CPU和佔用更多的磁盤空間, 如果確實不需要它可以完全的關閉它或者基於每字段定製。 例如: { "mappings": { "type_1": { "properties": {...} }, "type_2": { "_all": { "enabled": false }, "properties": {...} } } }

  5. type_1中的all字段是enabled,type_2中的all字段是disabled,所以一般不特別使用時,將enabled設置成false

  6. 參考文檔: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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章