曹大帶我學 Go(7)—— 如何優雅地指定配置項

你好,我是小 X。

曹大最近開 Go 課程了,小 X 正在和曹大學 Go。

這個系列會講一些從課程中學到的讓人醍醐灌頂的東西,撥雲見日,帶你重新認識 Go。

最近一個年久失修的庫導致了線上事故,不得不去做一些改進。

這個陳年庫的作用是調用第三方的 RPC 拿一些比較重要的配置,業務代碼中有段邏輯會根據讀到的配置調用不同端的下游。如果沒拿到配置,就會默認地調一個兜底下游。恰好這個兜底下游最近新上了一些邏輯,不兼容這種跨端調用,直接把它打掛了。

先拋開這個下游不健壯不談,假設它是健壯的。

陳年庫的問題在於:進程啓動時它會去調一個下游拿數據,之後會定時更新。但如果啓動時調用失敗就直接 panic 了,所以之後也不會定時更新。理論上這個也沒什麼問題,服務在初始化時如果檢測到了庫的 panic,進程退出,重啓就好了。

但是阻塞啓動是比較危險的,所以有些服務就會吞掉 panic。於是,整個進程生命週期內這個配置就一直是缺失的狀態。

因爲阻塞服務的啓動風險太高,所以當前的狀態是把 panic recover 住了,但是之後這個配置也就一直沒有更新的機會了。而陳年庫其實是可以在後臺靜默更新數據的。

因此我要對陳年庫要做一點改進:如果初始化時拉取配置失敗,不 panic,後臺靜默修復。這個設置要在調用 Init 函數時設置,因爲庫就暴露了 Init 和 Get 函數。

但因爲這個庫有很多使用方,所以不可能更改函數簽名和現在的行爲,否則影響其他人使用。萬一有業務都對這個是強依賴,就是要感知 panic,初始化失敗就進程退出,你改了不就 gg 了。

我們知道,Go 語言裏面有可變參數,調用它的時候可以不傳實參,或者傳多個實參。向陳年庫函數的 Init 函數簽名後加一個可變參數:

func Init(a int)

變成:

func Init(a int, opts ...optionFunc)

這樣就不影響已有的用戶了,並且我可以增加更多的設置項。這裏的關鍵是 optionFunc 的實現原理是什麼?

它其實是一個函數類型,它接受 options 結構體指針:

type optionFunc func(*options)

再定義一個 options 結構體用於放 bool 型變量 PanicWhenInitFail,表示 Init 失敗後是否 panic:

type options struct {
 PanicWhenInitFail bool
}

再來定義一個導出的函數,用戶傳入 bool 型變量就可以設置 options,而不用定義 options 對象。這種方法美妙的地方就在這裏,要多次回味才能感受到:

func WithPanicWhenInitFail() optionFunc {
 return func(o *options) {
  o.PanicWhenInitFail = true
 }
}

初始時,Init 函數的實現如下:

func Init(a int) {
 fmt.Println(a)
}

修改後:

func Init(a int, opts ...optionFunc) {
 fmt.Println(a)

 var gOpt = &options{PanicWhenInitFail: false}

 for _, opt := range opts {
  opt(gOpt)
 }

 fmt.Println(gOpt)

}

這樣,main 函數就可以非常優雅地設置 PanicWhenInitFail 了:

func main() {
 Init(8)
 Init(8, WithPanicWhenInitFail())
}

不管加不加後面的配置,兩種調用方式都可以編譯成功,不會影響現有的用戶,完美。

爲什麼這篇文章和曹大扯上關係,因爲在曹大寫的 mosn/homels[1] 這個庫裏也有類似的代碼。當然,本文這種形式很常見,可以算作標配了。不過,有一點點不同之處,曹大定義了一個 interface,不過看起來感覺有點更難懂了。😇

// Option holmes option type.
type Option interface {
 apply(*options) error
}

type optionFunc func(*options) error

func (f optionFunc) apply(opts *options) error {
 return f(opts)
}

去 Google 上一查,其實這種形式,叫 Functional Options Pattern,早在 2014 年 Rob Pike 就寫過一篇博文 [2] 來說這個事,沒幾行代碼,但是真的很優雅。

總結一下,當我們要修改已有的函數時,爲了不破壞原有的簽名和行爲,可以使用 Functional Options Pattern 的形式增加可變參數,即可以增加設置項,又能兼容已有的代碼。

好了,這就是今天全部的內容了~ 我是小 X,我們下期再見~


歡迎關注曹大的 TechPaper 以及碼農桃花源~

參考資料

[1]

mosn/homels: https://github.com/mosn/holmes

[2]

博文: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/qwzhLKnSAYlRT7oeE93CPg