当前位置: 首页 > 科技观察

曹大带我学习了如何在Go中优雅地指定配置项

时间:2023-03-23 11:30:30 科技观察

转载本文请联系码农桃花源公众号。大家好,我是小X,曹达最近开了围棋课程,小X跟曹达一起围棋。本系列将讨论从课程中学到的一些有启发性的东西,拨开乌云,带你回到Go。一家年久失修的图书馆最近发生了一次上网事故,不得不进行一些改进。这个老化库的作用是调用第三方RPC获取一些比较重要的配置。业务代码中有一段逻辑会根据读取的配置调用不同端的下游。如果不获取配置,默认会向下游调整一个pocket。正好最近底线增加了一些新的逻辑,不兼容这种跨端调用,所以直接挂掉。撇开下游不稳健,假设它是稳健的。老化库的问题是,当进程启动时,它会去一个下游获取数据,然后定期更新。但是如果启动时调用失败,会直接panic,所以之后就不会定时更新了。从理论上讲,这没有问题。如果服务在初始化过程中检测到库出现panic,则进程退出并重新启动。但是阻塞启动比较危险,所以有些服务会吞掉panic。因此,这个配置在整个流程生命周期中一直缺失。因为阻塞服务启动的风险太大,目前状态是panicrecover,但是之后就没有机会更新这个配置了。老化库其实可以在后台静默更新数据。因此,我想对老库做一点改进:如果初始化时拉取配置失败,不会出现panic,后台默默修复。此设置应在调用Init函数时设置,因为库公开了Init和Get函数。但是因为这个库的用户很多,所以不可能改变函数签名和当前的行为,否则会影响其他人的使用。如果任何企业强烈依赖于此,则有必要感受到恐慌。如果初始化失败,进程将退出。换了就gg了。我们知道Go语言中有可变参数。调用时可以不传实参,也可以传多个实参。在老库函数的Init函数签名中增加一个可变参数:funcInit(aint)变成:funcInit(aint,opts...optionFunc)这样就不会影响现有的用户,我可以增加更多的设置项。这里的关键是optionFunc的实现原理是什么?它实际上是一个函数类型,它接受options结构体指针:typeoptionFuncfunc(*options)定义了另一个options结构体来放置bool变量PanicWhenInitFail,指示Init失败后是否panic:typeoptionsstruct{PanicWhenInitFailbool}再次定义一个导出函数,用户可以通过传入布尔变量而不是定义选项对象来设置选项。这个方法的妙处就在这里,需要多多回味才能感受:funcInit(aint){fmt.Println(a)}修改:funcInit(aint,opts...optionFunc){fmt.Println(a)vargOpt=&options{PanicWhenInitFail:false}for_,opt:=rangeopts{opt(gOpt)}嗯。println(gOpt)}这样main函数就可以很优雅的设置PanicWhenInitFail:funcmain(){Init(8)Init(8,WithPanicWhenInitFail())}不管加下面的配置,两种调用方式都能编译成功,不影响现有用户,完美。这篇文章为什么和曹大有关,因为在曹大写的库mosn/homels[1]中有类似的代码。当然,本文这种形式很常见,可以算作标准。不过有一点点不同,草大定义了一个接口,但是好像有点难理解。??//Optionholmesoptiontype.typeOptioninterface{apply(*options)error}typeoptionFuncfunc(*options)errorfunc(foptionFunc)apply(opts*options)error{returnf(opts)}去Google查了一下,其实就是这个形式调用FunctionalOptionsPattern,早在2014年,RobPike就写了一篇博文[2]讨论此事。没有几行代码,但是真的很优雅。综上所述,当我们想要修改一个已经存在的函数时,为了不破坏原有的签名和行为,我们可以使用FunctionalOptionsPattern来添加可变参数,既可以增加设置项又可以兼容已有代码。好了,今天就到这里吧~我是小X,下期见~

最新推荐
猜你喜欢