背景
最近在搭一个新项目的架子,在生产环境中,为了能实时的监控程序的运行状态,少不了逻辑执行时间长度的统计。时间统计这个功能实现的期望有下面几点:
实现思路
统计细节的剥离
最朴素的时间统计的实现,可能是下面这个样子:
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, } }) } }
测试通过,验证了时间统计是没问题的。至此,这个时间统计装饰器就介绍完了。如果这个实现有什么问题,或者大家有更好的实现方式,欢迎大家批评指正与提出~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。
本文向大家介绍使用python装饰器计算函数运行时间的实例,包括了使用python装饰器计算函数运行时间的实例的使用技巧和注意事项,需要的朋友参考一下 装饰器在python里面有很重要的作用, 如果能够熟练使用,将会大大的提高工作效率 今天就来见识一下 python 装饰器,到底是怎么工作的。 本文主要是利用python装饰器计算函数运行时间 一些需要精确的计算函数运行了多久的程序,都可以采用这种
问题内容: 我想这就是它们的称呼,但我会举一些例子以防万一。 装饰类: 装饰器功能: 使用一个或另一个只是口味的问题吗?有实际区别吗? 问题答案: 如果您可以编写函数来实现装饰器,则应首选它。但是并非所有装饰器都可以轻松地编写为一个函数-例如,当您要存储一些内部状态时。 我见过人们(包括我自己)经过荒唐的努力,只用函数编写装饰器。我仍然不知道为什么,一个班级的开销通常可以忽略不计。
问题内容: 举个例子: 我遇到的问题是,甚至在我调用要装饰的函数之前就调用了。 开始输出: 在这一点上,我什至没有调用过一个装饰过的函数。 我刚刚开始使用装饰器,所以也许我缺少了一些东西。 问题答案: 我相信python装饰器只是语法糖。 和…一样 如您所见,即使没有调用 bar 也将调用 foo 。这就是为什么您看到装饰器函数的输出的原因。对于您将装饰器应用到的每个函数,您的输出应只包含一行。
有时候,能够知道一个计算执行消耗的时间是非常有意义的,尤其是在对比和基准测试中。最简单的一个办法就是在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 time 包中的 Now() 和 Sub 函数: start := time.Now() longCalculation() end := time.Now() de
本文向大家介绍Python装饰器的函数式编程详解,包括了Python装饰器的函数式编程详解的使用技巧和注意事项,需要的朋友参考一下 Python的装饰器的英文名叫Decorator,当你看到这个英文名的时候,你可能会把其跟Design Pattern里的Decorator搞混了,其实这是完全不同的两个东西。虽然好像,他们要干的事都很相似——都是想要对一个已有的模块做一些“修饰工作”,所谓修饰工作就
本文向大家介绍Android Chronometer控件实现计时器函数详解,包括了Android Chronometer控件实现计时器函数详解的使用技巧和注意事项,需要的朋友参考一下 本文为大家演示了如何使用Chronometer控件实现Android计时器的实例。 先贴上最终的实现效果图: Android计时器实现思路 使用Chronometer控件实现计器的操作。通过设置setBase(lon