中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何實現Golang函數執行時間統計裝飾器

發布時間:2021-08-07 09:30:53 來源:億速云 閱讀:380 作者:小新 欄目:編程語言

小編給大家分享一下如何實現Golang函數執行時間統計裝飾器,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

背景

最近在搭一個新項目的架子,在生產環境中,為了能實時的監控程序的運行狀態,少不了邏輯執行時間長度的統計。時間統計這個功能實現的期望有下面幾點:

  1. 實現細節要剝離:時間統計實現的細節不期望在顯式的寫在主邏輯中。因為主邏輯中的其他邏輯和時間統計的抽象層次不在同一個層級

  2. 用于時間統計的代碼可復用

  3. 統計出來的時間結果是可被處理的。

  4. 對并發編程友好

實現思路

統計細節的剝離

最樸素的時間統計的實現,可能是下面這個樣子:

func f() {
 startTime := time.Now()
 logicStepOne()
 logicStepTwo()
 endTime := time.Now()
 timeDiff := timeDiff(startTime, endTime)
 log.Info("time diff: %s", timeDiff)
}

《代碼整潔之道》告訴我們:一個函數里面的所有函數調用都應該處于同一個抽象層級。

在這里時間開始、結束的獲取,使用時間的求差,屬于時間統計的細節,首先他不屬于主流程必要的一步,其次他們使用的函數 time.Now() 和 logicStepOne, logicStepTwo 并不在同一個抽象層級。

因此比較好的做法應該是把時間統計放在函數 f 的上層,比如:

func doFWithTimeRecord() {
 startTime: = time.Now()
 f()
 endTime := Time.Now()
 timeDiff := timeDIff(startTime, endTime)
 log.Info("time diff: %s", timeDiff)
}

時間統計代碼可復用&統計結果可被處理&不影響原函數的使用方式

我們雖然達成了函數內抽象層級相同的目標,但是大家肯定也能感受到:這個函數并不好用。

原因在于,我們把要調用的函數 f 寫死在了 doFWithTimeRecord 函數中。這意味著,每一個要統計時間的函數,我都需要實現一個 doXXWithTimeRecord, 而這些函數里面的邏輯是相同的,這就違反了我們 DRY(Don't Repeat Yourself)原則。因此為了實現邏輯的復用,我認為裝飾器是比較好的實現方式:將要執行的函數作為參數傳入到時間統計函數中。

舉個網上看到的例子

實現一個功能,第一反應肯定是查找同行有沒有現成的輪子。不過看了下,沒有達到自己的期望,舉個例子:

type SumFunc func(int64, int64) int64

func timedSumFunc(f SumFunc) SumFunc {
 return func(start, end int64) int64 {
  defer func(t time.Time) {
   fmt.Printf("--- Time Elapsed: %v ---\n", time.Since(t))
  }(time.Now())
  
  return f(start, end)
 }
}

說說這段代碼不好的地方:

這個裝飾器入參寫死了函數的類型:

type SumFunc func(int64, int64) int64

也就是說,只要換一個函數,這個裝飾器就不能用了,這不符合我們的第2點要求

這里時間統計結果直接打印到了標準輸出,也就是說這個結果是不能被原函數的調用方去使用的:因為只有掉用方,才知道這個結果符不符合預期,是花太多時間了,還是正常現象。這不符合我們的第3點要求。

怎么解決這兩個問題呢?

這個時候,《重構,改善既有代碼的設計》告訴我們:Replace Method with Method Obejct——以函數對象取代函數。他的意思是當一個函數有比較復雜的臨時變量時,我們可以考慮將函數封裝成一個類。這樣我們的函數就統一成了 0 個參數。(當然,原本就是作為一個 struct 里面的方法的話就適當做調整就好了)

現在,我們的代碼變成了這樣:

type TimeRecorder interface {
 SetCost(time.Duration)
 TimeCost() time.Duration
}

func TimeCostDecorator(rec TimeRecorder, f func()) func() {
 return func() {
  startTime := time.Now()
  f()
  endTime := time.Now()
  timeCost := endTime.Sub(startTime)
  rec.SetCost(timeCost)
 }
}

這里入參寫成是一個 interface ,目的是允許各種函數對象入參,只需要實現了 SetCost 和 TimeCost 方法即可

對并發編程友好

最后需要考慮的一個問題,很多時候,一個類在整個程序的生命周期是一個單例,這樣在 SetCost 的時候,就需要考慮并發寫的問題。這里考慮一下幾種解決方案:

使用裝飾器配套的時間統計存儲對象,實現如下:

func NewTimeRecorder() TimeRecorder {
 return &timeRecorder{}
}

type timeRecorder struct {
 cost time.Duration
}

func (tr *timeRecorder) SetCost(cost time.Duration) {
 tr.cost = cost
}

func (tr *timeRecorder) Cost() time.Duration {
 return tr.cost
}

抽離出存粹的執行完就可以銷毀的函數對象,每次要操作的時候都 new 一下

函數對象內部對 SetCost 函數實現鎖機制

這三個方案是按推薦指數從高到低排序的,因為我個人認為:資源允許的情況下,盡量保持對象不可變;同時怎么統計、存儲使用時長其實是統計時間模塊自己的事情。

單元測試

最后補上單元測試:

func TestTimeCostDecorator(t *testing.T) {
 testFunc := func() {
  time.Sleep(time.Duration(1) * time.Second)
 }
 
 type args struct {
  rec TimeRecorder
  f func()
 }
 
 tests := []struct {
  name string
  args args
 }{
  {
   "test time cost decorator",
   args{
    NewTimeRecorder(),
    testFunc,
   },
  },
 }
 for _, tt := range tests {
  t.Run(tt.name, func(t *testing.T) {
   got := TimeCostDecorator(tt.args.rec, tt.args.f)
   got()
   if tt.args.rec.Cost().Round(time.Second) != time.Duration(1) * time.Second.Round(time.Second) {
    "Record time cost abnormal, recorded cost: %s, real cost: %s",
    tt.args.rec.Cost().String(),
    tt.Duration(1) * time.Second,
   }
  }) 
 }
}

測試通過,驗證了時間統計是沒問題的。

以上是“如何實現Golang函數執行時間統計裝飾器”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

长丰县| 鸡泽县| 三门县| 北碚区| 连南| 江油市| 扎赉特旗| 美姑县| 丽水市| 新竹县| 阳山县| 阿合奇县| 崇左市| 综艺| 蒙城县| 延津县| 沿河| 栾城县| 凭祥市| 四川省| 镇安县| 巴楚县| 波密县| 石渠县| 三都| 长宁县| 六枝特区| 贵阳市| 四会市| 闽侯县| 营口市| 股票| 林州市| 徐汇区| 延安市| 西平县| 临夏市| 德兴市| 和硕县| 永定县| 洛南县|