濮阳杆衣贸易有限公司

主頁 > 知識(shí)庫 > Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解

Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解

熱門標(biāo)簽:西部云谷一期地圖標(biāo)注 學(xué)海導(dǎo)航地圖標(biāo)注 中國地圖標(biāo)注省會(huì)高清 南通如皋申請(qǐng)開通400電話 江西轉(zhuǎn)化率高的羿智云外呼系統(tǒng) 地圖標(biāo)注的汽車標(biāo) 浙江高速公路地圖標(biāo)注 高德地圖標(biāo)注口訣 廣州呼叫中心外呼系統(tǒng)

背景

最近在搭一個(gè)新項(xiàng)目的架子,在生產(chǎn)環(huán)境中,為了能實(shí)時(shí)的監(jiān)控程序的運(yùn)行狀態(tài),少不了邏輯執(zhí)行時(shí)間長度的統(tǒng)計(jì)。時(shí)間統(tǒng)計(jì)這個(gè)功能實(shí)現(xiàn)的期望有下面幾點(diǎn):

  1. 實(shí)現(xiàn)細(xì)節(jié)要?jiǎng)冸x:時(shí)間統(tǒng)計(jì)實(shí)現(xiàn)的細(xì)節(jié)不期望在顯式的寫在主邏輯中。因?yàn)橹鬟壿嬛械钠渌壿嫼蜁r(shí)間統(tǒng)計(jì)的抽象層次不在同一個(gè)層級(jí)
  2. 用于時(shí)間統(tǒng)計(jì)的代碼可復(fù)用
  3. 統(tǒng)計(jì)出來的時(shí)間結(jié)果是可被處理的。
  4. 對(duì)并發(fā)編程友好

實(shí)現(xiàn)思路

統(tǒng)計(jì)細(xì)節(jié)的剝離

最樸素的時(shí)間統(tǒng)計(jì)的實(shí)現(xiàn),可能是下面這個(gè)樣子:

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

《代碼整潔之道》告訴我們:一個(gè)函數(shù)里面的所有函數(shù)調(diào)用都應(yīng)該處于同一個(gè)抽象層級(jí)。

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

因此比較好的做法應(yīng)該是把時(shí)間統(tǒng)計(jì)放在函數(shù) f 的上層,比如:

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

時(shí)間統(tǒng)計(jì)代碼可復(fù)用統(tǒng)計(jì)結(jié)果可被處理不影響原函數(shù)的使用方式

我們雖然達(dá)成了函數(shù)內(nèi)抽象層級(jí)相同的目標(biāo),但是大家肯定也能感受到:這個(gè)函數(shù)并不好用。

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

舉個(gè)網(wǎng)上看到的例子

實(shí)現(xiàn)一個(gè)功能,第一反應(yīng)肯定是查找同行有沒有現(xiàn)成的輪子。不過看了下,沒有達(dá)到自己的期望,舉個(gè)例子:

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)
 }
}

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

這個(gè)裝飾器入?yún)懰懒撕瘮?shù)的類型:

type SumFunc func(int64, int64) int64

也就是說,只要換一個(gè)函數(shù),這個(gè)裝飾器就不能用了,這不符合我們的第2點(diǎn)要求

這里時(shí)間統(tǒng)計(jì)結(jié)果直接打印到了標(biāo)準(zhǔn)輸出,也就是說這個(gè)結(jié)果是不能被原函數(shù)的調(diào)用方去使用的:因?yàn)橹挥械粲梅?,才知道這個(gè)結(jié)果符不符合預(yù)期,是花太多時(shí)間了,還是正?,F(xiàn)象。這不符合我們的第3點(diǎn)要求。

怎么解決這兩個(gè)問題呢?

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

現(xiàn)在,我們的代碼變成了這樣:

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)
 }
}

這里入?yún)懗墒且粋€(gè) interface ,目的是允許各種函數(shù)對(duì)象入?yún)?,只需要?shí)現(xiàn)了 SetCost 和 TimeCost 方法即可

對(duì)并發(fā)編程友好

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

使用裝飾器配套的時(shí)間統(tǒng)計(jì)存儲(chǔ)對(duì)象,實(shí)現(xiàn)如下:

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
}

抽離出存粹的執(zhí)行完就可以銷毀的函數(shù)對(duì)象,每次要操作的時(shí)候都 new 一下

函數(shù)對(duì)象內(nèi)部對(duì) SetCost 函數(shù)實(shí)現(xiàn)鎖機(jī)制

這三個(gè)方案是按推薦指數(shù)從高到低排序的,因?yàn)槲覀€(gè)人認(rèn)為:資源允許的情況下,盡量保持對(duì)象不可變;同時(shí)怎么統(tǒng)計(jì)、存儲(chǔ)使用時(shí)長其實(shí)是統(tǒng)計(jì)時(shí)間模塊自己的事情。

單元測試

最后補(bǔ)上單元測試:

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,
   }
  }) 
 }
}

測試通過,驗(yàn)證了時(shí)間統(tǒng)計(jì)是沒問題的。至此,這個(gè)時(shí)間統(tǒng)計(jì)裝飾器就介紹完了。如果這個(gè)實(shí)現(xiàn)有什么問題,或者大家有更好的實(shí)現(xiàn)方式,歡迎大家批評(píng)指正與提出~

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • Golang記錄、計(jì)算函數(shù)執(zhí)行耗時(shí)、運(yùn)行時(shí)間的一個(gè)簡單方法
  • 通過匯編看golang函數(shù)的多返回值問題
  • Golang學(xué)習(xí)筆記之延遲函數(shù)(defer)的使用小結(jié)
  • Golang中的自定義函數(shù)詳解
  • golang 實(shí)現(xiàn)每隔幾分鐘執(zhí)行一個(gè)函數(shù)

標(biāo)簽:吐魯番 貴州 曲靖 東營 許昌 常州 德宏 保定

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解》,本文關(guān)鍵詞  Golang,函數(shù),執(zhí)行,時(shí)間,統(tǒng)計(jì),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解》相關(guān)的同類信息!
  • 本頁收集關(guān)于Golang 函數(shù)執(zhí)行時(shí)間統(tǒng)計(jì)裝飾器的一個(gè)實(shí)現(xiàn)詳解的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    佛学| 珠海市| 华坪县| 娄底市| 北辰区| 新晃| 吴堡县| 邳州市| 灌云县| 达拉特旗| 高雄县| 平定县| 巴楚县| 灵武市| 若尔盖县| 东港市| 达孜县| 宁明县| 且末县| 宜城市| 栾城县| 延安市| 临漳县| 浦东新区| 敦化市| 江川县| 襄城县| 石家庄市| 竹溪县| 常山县| 江山市| 宜城市| 大连市| 北碚区| 田阳县| 蓬莱市| 三河市| 泰州市| 邵阳县| 噶尔县| 文水县|