为什么需要配置文件?
这个问题好像很白痴。
但我相信还是有很多人答不上来。
程序是现实世界中万物之间规则的映射。但规则并非一成不变的,所以程序需要不停的适配变化。是否能够快速响应变化,是评判程序好坏的一项指标,这需要很强的设计能力。
配置,是一种将程序运行时所以依赖的变量以一种外挂的形式存储,而不需要将配置硬编码到源代码,这样在每次需要修改变量时都不再需要修改源代码。
而配置化思想,是将配置合理运用到程序运行时的一种思想,同时也提高程序扩展能力和自由度的一个重要思想。
我将它归纳为三重境界。
第一重境界:命令行参数
要实现将变量提取到程序外部,最简单的方法当然是通过命令行参数来实现了。
现代编程语言一般是在启动程序的命令脚本后面直接追加变量,每个变量以空格分割。
比如 Go:
go run main.go hello world
比如 Java:
java MainTest hello world
再比如 Node.js:
node test.js hello world
而在程序内部都以一个数组类型的变量来接收传入的配置,并以传入顺序作为下标,这个变量通常叫做 args,arguments 的缩写。
比如 Go:
args := os.Args for _, arg := range args { fmt.Printf("%v\n", arg) }
比如 Java:
public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } }
再比如 Node.js:
const args = process.argv; for (let arg of args) { console.log(arg); }
命令行参数的扩展
这种简单的传参方式当然会存在一些问题。
比如配置参数的类型默认都是字符串,需要自行转换。配置参数无法指定名字,必须按照顺序依次传递,如果有一些可选参数很难处理。
比如我需要这样:
node test.js name=hello -help
解决方式有很多,有的语言内置了解决方案,比如 Go 语言基础库的 flag 包。或者使用功能更加强大的第三方包来实现,比如 Node.js 的 commander。
第二重境界:文件
当配置参数过多时,再用命令行参数的形式会很繁琐,因为每次都需要输入一堆配置。
使用文件来持久化的存储配置是一个更优的选择。
实现配置文件的方式有很多,假设现在你忘记你掌握的文件配置手段。从命令行参数的角度继续思考,最简单的实现方案应该是这样的。
node test.js name=hello -help
把上面这段脚本存储到一个 shell 文件中,每次维护这里面的配置就可以了。
这种方式虽然一样可以实现利用文件持久化保存配置,但很明显,它不优雅。它把运行命令与配置耦合在一起了。
优雅的实现方式是将配置文件单独保存。
下面就要选择用何种配置文件存储配置了。常见的配置文件格式有三类,一类是通用型的文件格式,一类是自定义的文件格式,还有一类是脚本配置文件。
通用型的文件格式有很多,比如:ini、env、xml、conf、json、yaml、toml 等等。
INI、ENV 与 XML
这三者最为简单,ini 格式是初始化文件 Initialization File 的缩写,是 Windows 系统所采用的格式;env 是环境变量 Environment 的缩写,在 PHP 中备受欢迎;xml 应用范围极广,在早期的 Java 项目中非常常见,甚至曾被用作 API 的数据交互格式。但目前来说这三者都不再是最佳选择。
ini:
Name=hello
env:
Name=hello
XML:
<name> hello </name>
JSON
由于与 JS 的天然契合性,json 是当之无愧的最流行配置文件格式之首。可惜 json 仍然有缺点,过于冗余,不支持注释。
{ "name": "hello" }
YAML 与 TOML
如果只是为了配置文件这个目的,YAML 和 TOML 是比 JSON 更优雅的选择。
两者都极其简洁,而且功能强大,可以非常友好的表示数组与对象。
YAML:
name: 'hello'
TOML:
name = "hello"
不同的是,相比于 13 年才出现的 TOML,YAML 更加成熟,它是在 2001 年诞生的。
自定义文件格式
自定义文件格式是自己拟定的一种格式,本质上与通用型的文件格式没有什么区别。
像 gRPC 的 proto 文件、babel 的 babelrc 文件等,都是自定义的文件格式。
自定义文件格式的实现并不复杂,最基础的无非是提供一种文件格式的解析器。但同时还要考虑在多个环境中配置高亮的问题。
脚本配置文件
当然还有更复杂的场景,比如 Webpack 的配置文件,是一个可以运行的 JS 文件。这是为了拥有更高的扩展能力而设定的。
第三重境界:UI 界面
程序员的世界里曾经流传着一句传说:
一切能用 JavaScript 实现的东西终将会用 JavaScript 实现
我在这里补充一句:
一切能用 UI 操作的东西终将会用 UI 操作
很显然,程序员不会满足于此。
配置文件的高亮、提示以及校验等配套功能是一个简单的文件系统无法提供的。
我们可能会需要在各个编辑器中提供插件来实现这些功能,但编辑器的市场非常庞杂,Sublime Text、VSCode、Intelij 系列...数不胜数。我们不可能为每一个编辑器都提供一个插件,而且用户安装插件的过程也很繁琐。
而校验配置是否正确也是一个麻烦事,我们不能在程序启动时再进行校验。常见的做法比如 Nginx 的 -t 参数。
而这些弊端,都可以通过在线的 UI 界面来帮助我们更好的完成配置编写。
到目前为止,我们讨论的用途主要仍然是在程序启动阶段依赖的配置。实际上,配置的作用不仅仅可以程序启动,在程序运行时依然可以起到作用。
常见的形式,比如 GraphQL Editor 和 Swagger Editor。
更甚者,可以通过拖拽的图形化界面来生成配置,以此来影响程序的运行,比如工作流引擎、Node-Red 等。
以上是我对现代软件工程配置化的分析与总结,希望对你有所帮助。