曹大带我学 Go之如何优雅地指定配置项

开发 后端
最近一个年久失修的库导致了线上事故,不得不去做一些改进。这个陈年库的作用是调用第三方的 RPC 拿一些比较重要的配置,业务代码中有段逻辑会根据读到的配置调用不同端的下游。

[[411399]]

本文转载自微信公众号「码农桃花源」,作者小X。转载本文请联系码农桃花源公众号。

你好,我是小X。

曹大最近开 Go 课程了,小X 正在和曹大学 Go。

这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go。

最近一个年久失修的库导致了线上事故,不得不去做一些改进。

这个陈年库的作用是调用第三方的 RPC 拿一些比较重要的配置,业务代码中有段逻辑会根据读到的配置调用不同端的下游。如果没拿到配置,就会默认地调一个兜底下游。恰好这个兜底下游最近新上了一些逻辑,不兼容这种跨端调用,直接把它打挂了。

先抛开这个下游不健壮不谈,假设它是健壮的。

陈年库的问题在于:进程启动时它会去调一个下游拿数据,之后会定时更新。但如果启动时调用失败就直接 panic 了,所以之后也不会定时更新。理论上这个也没什么问题,服务在初始化时如果检测到了库的 panic,进程退出,重启就好了。

但是阻塞启动是比较危险的,所以有些服务就会吞掉 panic。于是,整个进程生命周期内这个配置就一直是缺失的状态。

因为阻塞服务的启动风险太高,所以当前的状态是把 panic recover 住了,但是之后这个配置也就一直没有更新的机会了。而陈年库其实是可以在后台静默更新数据的。

因此我要对陈年库要做一点改进:如果初始化时拉取配置失败,不 panic,后台静默修复。这个设置要在调用 Init 函数时设置,因为库就暴露了 Init 和 Get 函数。

但因为这个库有很多使用方,所以不可能更改函数签名和现在的行为,否则影响其他人使用。万一有业务都对这个是强依赖,就是要感知 panic,初始化失败就进程退出,你改了不就 gg 了。

我们知道,Go 语言里面有可变参数,调用它的时候可以不传实参,或者传多个实参。向陈年库函数的 Init 函数签名后加一个可变参数:

  1. func Init(a int

变成:

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

这样就不影响已有的用户了,并且我可以增加更多的设置项。这里的关键是 optionFunc 的实现原理是什么?

它其实是一个函数类型,它接受 options 结构体指针:

  1. type optionFunc func(*options) 

再定义一个 options 结构体用于放 bool 型变量 PanicWhenInitFail,表示 Init 失败后是否 panic:

  1. type options struct { 
  2.  PanicWhenInitFail bool 

再来定义一个导出的函数,用户传入 bool 型变量就可以设置 options,而不用定义 options 对象。这种方法美妙的地方就在这里,要多次回味才能感受到:

  1. func WithPanicWhenInitFail() optionFunc { 
  2.  return func(o *options) { 
  3.   o.PanicWhenInitFail = true 
  4.  } 

初始时,Init 函数的实现如下:

  1. func Init(a int) { 
  2.  fmt.Println(a) 

修改后:

  1. func Init(a int, opts ...optionFunc) { 
  2.  fmt.Println(a) 
  3.  
  4.  var gOpt = &options{PanicWhenInitFail: false
  5.  
  6.  for _, opt := range opts { 
  7.   opt(gOpt) 
  8.  } 
  9.  
  10.  fmt.Println(gOpt) 
  11.  

这样,main 函数就可以非常优雅地设置 PanicWhenInitFail 了:

  1. func main() { 
  2.  Init(8) 
  3.  Init(8, WithPanicWhenInitFail()) 

不管加不加后面的配置,两种调用方式都可以编译成功,不会影响现有的用户,完美。

为什么这篇文章和曹大扯上关系,因为在曹大写的 mosn/homels[1] 这个库里也有类似的代码。当然,本文这种形式很常见,可以算作标配了。不过,有一点点不同之处,曹大定义了一个 interface,不过看起来感觉有点更难懂了。??

  1. // Option holmes option type. 
  2. type Option interface { 
  3.  apply(*options) error 
  4.  
  5. type optionFunc func(*options) error 
  6.  
  7. func (f optionFunc) apply(opts *options) error { 
  8.  return f(opts) 

去 Google 上一查,其实这种形式,叫 Functional Options Pattern,早在 2014 年 Rob Pike 就写过一篇博文[2]来说这个事,没几行代码,但是真的很优雅。

总结一下,当我们要修改已有的函数时,为了不破坏原有的签名和行为,可以使用 Functional Options Pattern 的形式增加可变参数,即可以增加设置项,又能兼容已有的代码。 

好了,这就是今天全部的内容了~ 我是小X,我们下期再见~

 

责任编辑:武晓燕 来源: 码农桃花源
相关推荐

2021-06-10 09:00:32

Go底层代码

2021-06-07 10:47:02

GoGoexit函数

2021-08-09 07:47:39

ExtraGoMap

2021-05-27 08:59:09

Go汇编命令

2021-06-01 09:27:53

Ast Go语言

2021-05-20 08:59:47

Go调度本质

2022-01-05 08:56:20

Go火焰图编程

2021-09-08 08:34:37

Go 文档Goland

2018-08-20 10:40:09

Redis位图操作

2021-03-24 10:20:50

Fonts前端代码

2024-01-30 12:08:31

Go框架停止服务

2023-02-28 08:17:31

Go远程配置apollo

2024-11-13 16:37:00

Java线程池

2022-04-20 20:27:51

Hydra配置文件开发工具

2020-03-26 11:04:00

Linux命令光标

2021-05-12 22:07:43

并发编排任务

2021-01-18 13:17:04

鸿蒙HarmonyOSAPP

2022-05-13 21:20:23

组件库样式选择器

2021-01-28 14:53:19

PHP编码开发

2022-05-24 06:07:48

JShack用户代码
点赞
收藏

51CTO技术栈公众号