前两天在工作过程中又遇到了一直以来困惑我的一个问题,就是Go配置项的管理问题。
在开发一个新项目的时候,往往涉及到配置项的管理。个人小项目可能会通过配置文件来传入、环境变量来传入,也可能通过命令行参数来传入,公司级别的项目还可能用到各种各样的config center。那么,如何来管理这些配置项就会很麻烦。
在我的习惯中,通常会使用至少两种方式来传入配置——如配置文件加命令行。原因有三:
大部分情况下我运行的程序无需进行定制化,此时常使用默认配置文件。但有时候我们要临时修改一些选项,可以直接通过命令行参数传入覆盖默认配置
我通常会将默认配置文件直接添加到Git仓库里,但其中有部分包含敏感信息的配置(如加密密钥),我需要通过其他方式传入,如环境变量、命令行等
程序如果需要同时在测试环境和生产环境运行,我可以通过命令行选项来控制一些选项,而无需准备两个不同的配置文件
以往在命令行覆盖配置文件中配置项的时候,有一个很大的痛点就是,我不可能给每一个配置项都编写一个对应的命令行参数,而且随着项目的迭代,每次添加新的配置项都要添加对应的命令行参数,不太方便。
Go生态里有一个开源项目viper可以用于处理类似的问题,但是项目比较大,和pflag、cobra的耦合也比较深。
其实我需要的功能很简单,一个类似Java -Dserver.port=8000
中的-D
这样的选项,让我可以动态的修改配置文件中的一些配置项。这样,不管配置来自哪里,里面有哪些字段,我都可以通过-Da.b.c=1
这样的方式来修改。
按照我的这个痛点,我准备开发一个库,这个库的工作很简单,就是可以使用一定的语法,获取和设置任意对象中的属性。
比如,下面这个YAML对应的对象(这实际上是一个docker-compose.yml的配置文件):
version: "2.0"
services:
web:
image: openjdk:8-jre
ports:
- "8080:8080"
- "8081:8081"
我要将web容器的镜像由openjdk:8-jre
换成openjdk:8-jdk
,可以编写这样的语句:services.web.image=openjdk:8-jdk
;如果我想将8080端口修改成9090,则可以编写这样的语句:services.web.ports[0]=9090:8080
。
这并不是一个非常困难的项目,但作为一个尊贵的ChatGPT Plus会员,我想让GPT4辅助我完成这个项目代码的编写。
首先,我们需要将自己的需求清晰地描述给ChatGPT,比如,我将我的需求抽象成一个名为SetAttr
的函数,并将这个函数的作用和例子发给它:
ChatGPT返回给我的函数,看起来大致没有什么问题。接着,我还让其帮忙生成了与SetAttr
对应的GetAttr
函数,由于前面已经生成过SetAttr
,所以对于GetAttr
函数的描述可以比较简单,GPT4具有一定逻辑思考能力:
这两个函数的代码主体上没有什么问题,但细节是否能完全满足我们的需求,还需要编写单元测试来验证。
我这里的建议是,如果代码由GPT生成,那么单元测试需要我们人工来编写;如果代码是我们人工编写,那么单元测试可以让GPT生成。人工编写的过程中,可以让GPT来生成一些辅助片段加快我们工作效率,但最好不要把这两部分全部都交给GPT来实现,否则很可能在它那里是逻辑自洽的,但实际上有很多情况没有被考虑,需要大量修改。
最后,我投入了一些时间在单元测试的编写上,处理了一些panic,让项目可以适应大部分情况。
完成了代码开发,我们可以让ChatGPT帮忙生成一下README:
这就是我第一个使用ChatGPT辅助生成的完整项目。ChatGPT的工作大概70%,我的工作大概30%,相比于正常实现一个类似的项目,我大概节省了50%时间。
我的贡献除了编写部分单元测试,我还优化了API,让其更方便被使用,用可以直接通过这样的方式来设置config
对象的任意属性:
goattribute.New(&config).SetAttr("Services.Web.Ports[0]", "9090:8080")
有这样一个库,我就可以比较方便地实现文章开头的需求了——从命令行获取-D
参数的值后,使用=
将其分割成键名和值,键名作为SetAttr
的第一个参数,值作为SetAttr
的第二个参数即可。
完整代码已经发布在https://github.com/phith0n/goattribute,如果你与我有相似需求,可以考虑使用这个库来优化你的配置管理流程。当然,goattribute并不仅限于我上面说到的场景,也可以用于操纵任意对象的值。
封面图片由MidJourney生成。喜欢这篇文章,点个在看再走吧~