写点什么

为什么配置模式令人抓狂?尝试用编程语言来写吧

  • 2020-04-29
  • 本文字数:4317 字

    阅读完需:约 14 分钟

为什么配置模式令人抓狂?尝试用编程语言来写吧

本文将试着解释为什么大多数配置格式用起来都不太舒服,作者建议大家尝试使用一门真正的编程语言(例如,像 Python 这样的通用编程语言)来编写配置,通常这是一种可行的选择,且使用过程更感愉悦。


大多数现代配置格式都很糟糕

本节,我主要针对 JSON/YAML/TOML/ini 文件,这是我遇到过最常见的配置格式。


我们暂将这种配置称为常见配置(如果有更好的名字,欢迎在评论中留言,谢谢)。


大家可能遇到过如下情况:



例如,虽然 YAML 在理论上支持重用/引用配置(他们称之为),但有些软件(如Github Actions)却并不支持。通常,开发者无法重用配置的一部分,必须复制粘贴。


  • .gitconfig 使用一个自定义语法来合并这些配置

  • 不能包含任何逻辑


很多人认为这是一种积极的做法,但我认为,如果不能定义临时变量、辅助函数、替换字符串或连接列表,那就有点差劲。变通方法(如果有的话)通常也不好用,因为它们额外增加了认知开销。于是,出现了一批重新发明的编程语言:



此外,他们有自己的一套函数来处理变量。你得为此学习一门从来都未曾想过要学习的新语言。


  • 范围

  • 例如,在 Github 操作中有几个针对于env指令的自定义作用域。

  • 控制流

  • for 循环:构建矩阵和“排除”总是让人头疼不已

  • if 语句:例如,CircleCI 中的when

  • 无法被校验。可以校验配置语法本身(例如,检查 JSON 串的正确性),但无法做语义检查。这是因为在配置文件中没有逻辑。通常情况下,你必须编写一个辅助程序来检查配置,并在传递给程序之前调用。很少有程序会遇到这个问题,通常,使用简单的类型系统就可以发现程序中的细小错误。

  • YAML 的隐式转换和可移植性问题非常突出。这一点已经饱受非议,所以在此只提供一个相关链接,供感兴趣的读者自行了解:“YAML:可能没那么好


总结:我们在花时间学习没什么用处的语法,而不是在富有成效地完成工作

解决方法

当遇到这些问题时会出现什么情况呢?通常最终会使用一种“真正的”(即通用的、图灵完备的)编程语言来解决问题:


  • 编写一个过滤自定义注释语法的程序;

  • 编写一个合并配置或使用模板引擎的程序;

  • 编写一个“evaluate”配置的程序,在此过程中,常常需要为一门简单的函数式语言重新实现一个解释器

  • 编写一个校验配置的程序。


在大多数情况下,它就是类型检查的样板文件。你不仅要处理已解决的问题,而且得到的错误消息质量也不高,所有这些事情都会分散你在主要目标上的注意力。

使用一门真正的编程语言

其思想是用目标编程语言编写配置。这里我将使用 Python,但是,这一思想也适用于其他语言,只要足够动态即可(比如 Javascript、Ruby 等等)。这样,只需 import 或 evaluate 配置文件就可完成。


一个小例子:


config.py


from typing import NamedTupleclass Person(NamedTuple):    name: str    age: intPEOPLE = [    Person('Ann'  , 22),    Person('Roger', 15),    Person('Judy' , 49),]
复制代码


使用这个配置(如果你想知道为什么我使用 exec 而不是 import,请看看这个回复):


from pathlib import Pathconfig = {}exec(Path('config.py').read_text(), config)people = config['PEOPLE']print(people)
复制代码


[Person(name='Ann', age=22), Person(name='Roger', age=15), Person(name='Judy', age=49)]
复制代码


我觉得它很简洁。让我们看看如何解决上文所述问题:


  • 注释:很明显,不需赘述

  • 包含:很简单,使用 import


你甚至可以 import 正在配置的包,可以针对配置定义一个 DSL,它将在配置文件中进行导入和使用。


逻辑


你可以使用语言的语法和库。例如,单独使用像pathlib之类的可以节省大量重复配置。


当然,随意乱用可能会让人难以理解。就我个人而言,我宁愿接受语言被滥用,也不愿受限制。


校验


你可以将逻辑校验保留在配置中,以便在加载时进行检查。成熟的静态分析工具(如 JS flow、eslint、pylint、mypy)对此可以有所帮助。

缺点

互操作性


如果程序是用 Python 编写的,那没什么问题。但如果不是,或者稍后将以另一种语言(比如 C++之类的编译语言)重写它,该怎么办呢?


将来,软件是否无需解释器即可运行?现代的 FFI 很是繁琐,链接配置将相当棘手。


我们特别以 Python 为例,大多数现代 OS 发行版中都有它。那么,你可以按以下方式来做:


  1. 使 Python 配置可执行

  2. 在 main() 函数中构建配置,转换为 JSON 串并输出到 stdout


由于 Python 是动态的,所以无需样板文件即可执行此步骤。


  1. 在代码中执行 Python 配置(比如,使用 popen()),读取原生的 JSON 串并予以处理。仍然需要手动在代码中将配置反序列化,但这至少不像只使用 JSON 并手动编辑它那么糟。


通用编程语言很难推理


这多少有点主观。就我个人而言,我更有可能被一个过于冗长的普通文本配置搞得不知所措,我一直都更喜欢简洁的 DSL。其中一个重要因素是代码风格:我确信你可以使配置文件在几乎任何编程语言中都具有可读性,甚至根本不熟悉该语言的人也能够看得懂,最大的问题可能是安全性和终止检查。


安全性


例如,如果配置可以执行任意代码,那么它可能会窃取密码、格式化硬盘等。


如果配置是由你不信任的第三方提供的,那么,我认为普通文本配置更安全。然而,通常并非如此,一般都是用户自己控制自己的配置。


此外,也可以通过沙箱解决这一问题,是否值得这样做取决于项目的性质,但是如果你使用像 CI executor 之类的东西,无论如何都需要它。


另外要注意,使用普通文本的配置格式不一定能躲过这些麻烦。参见“YAML:一般并不安全”。


终止检查


即使不关心安全性,也不希望配置会挂起程序。我个人从来没有遇到过这样的问题,但这里有一些潜在的解决方法可供参考:


  • 为加载配置指定显式的超时时间

  • 有些语言能够有所帮助,例如,Bazel Skylark


有人知道在通用语言中检查终止的保守的静态分析工具的例子吗?注意,使用普通文本配置并不意味着它不会无限循环,参阅"Accidentally Turing complete".


配置会花很多时间去 evaluate,虽然技术上需要在有限的时间内完成,请参阅"Why Dhall advertises the absence of Turing-completeness"。虽然Ackermann函数是一个人为设计的例子,但它表明如果你真的关心恶意输入,那么无论如何都要做沙箱处理。

为什么是 Python?

我发现出于以下原因,大家都特别喜欢用 Python 来编写配置文件:


  • 几乎所有的现代操作系统中都有 Python

  • 大家认为 Python 语法很简单(不是件坏事),所以 Python 配置很有可能不会比普通配置更难理解

  • 数据类、函数和生成器构成了精简的 DSL 的基础

  • 类型标注同时用作文档和校验


其实,你可以在大多数现代编程语言中获得类似的愉快体验(只要它们足够动态)。

还有谁在做这件事?

一些项目允许用代码作为配置:


  • Webpack,Web 模块打包器,使用 Javascript 作为配置

  • setuptools,安装 Python 包的标准方法


允许同时使用 setup.cfg 和 setup.py 文件。这样的话,如果你不能以普通文本配置完成你的需求,那么可以在 setup.py 中进行调整,从而使你可以在声明式和灵活性之间取得平衡。



使用一个python文件配置输出。


  • Emacs:大家都知道使用 Elisp 进行它的配置


虽然我一点也不喜欢 Elisp,但它确实使 Emacs 非常灵活,可以实现你想要的任何配置。另一方面,如果你曾经读过其他人的 Emacs 设置,那么你可以发现,当你允许使用通用语言进行配置时,有些事情可能很难操控。



有些语言是专门为配置而设计的:



虽然为了确保终止检查和确定性而特意对 Bazel 进行了限制,但是配置 Bazel 比我使用过的任何其他构建系统都要愉快得多。


  • Meson构建系统:借鉴 Python 的语法

  • Nix:专门为 Nix 包管理器设计的语言


虽然弄一门全新的语言让人感觉有点大材小用,但是仍然好过用普通文本来进行配置。


  • Dhall:专门为配置文件设计的语言


Dhall 宣称自己是“JSON +函数+类型+导入”。的确,它看起来很棒,解决了我上文列出的大部分问题。



它们之间的具体区别,请参阅其他配置语言间的比较


这种语言的缺点是还没有被广泛使用。如果你没有绑定目标语言,那么需要二次解析 JSON。


但是,至少它能使你可以愉快地编写配置。


然而,如果你的程序是用 Javascript 编写的,并且不与其他语言交互,那么为什么不直接用 Javascript 编写配置呢?

如果一个也不选要怎么办?

在使用普通文本配置的时候,我找到了一些减少那些问题的方法:


尽量少写配置文件


这通常适用于 CI 流水线配置(例如 Gitlab、Circle、Github Actions)或 Dockerfiles。通常情况下,这样的配置使用了大量的 shell 命令,如果不逐行复制,就不可能在本地运行。


是的,的确也有调试的方法,但是它们的反馈周期非常慢。


  • 使用更适合设置本地虚拟环境的工具,如tox-dev/tox

  • 更多地采用 helper shell 脚本,并从你的流水线中调用它们


这多少有点令人沮丧,因为它引入了间接而分散的代码。但是,同时它也是一个优势,你可以剥离(例如 shellcheck)你的流水线脚本,使它更容易在本地运行。有时,如果你的流水线很短,你可以视情况做出自己的判断。让 CI 只负责为你设置 VM/容器、缓存依赖项和发布构件。


生成而不是手动编写


这样做的缺点是,相比于手工编辑而言,生成的配置可能会更分散。


你可以添加警告注释,提醒该配置是自动生成的,并附上生成器的链接,同时将配置文件设置为只读,以防止有人手动编辑。


此外,如果你正在实行 CI,可以将一致性检查作为流水线本身的一部分。

参考资料


总体上,我同意这一观点,但是仍然有些情况是不适用于标记的。


它也容易泄露机密(密钥、令牌、密码)——无论是在你的 shell 历史记录中还是通过 ps 都可以看到。


  • Xmonad:配置文件可执行文件


一个有趣的方法,但不一定总是可行的,例如,你可能没有安装编译器。


  • Mage:以 Go 编写用于 makefile 的工具

  • Dhall wiki:可编程的配置文件

  • 扩展语言的演变:Lua 的历史——显然 Lua 已经开始成为配置语言

  • Cue:定义、生成和验证数据的语言


我在网站上找了很久才找到一个代码例子,就在这里


最后的问题

之于现在为什么 YAML 成为一个主流选择,我还没有答案。我相信,Ansible/CircleCI 或者 Github Actions 都出自于非常优秀的工程师之手,他们应该考虑过使用 YAML 的利弊。


欢迎大家在评论区留言,分享你在做配置时经受过的痛苦,以及是如何解决它的。


原文链接:Your configs suck? Try a real programming language.


2020-04-29 16:359344
用户头像
赵钰莹 极客邦科技 总编辑

发布了 887 篇内容, 共 658.4 次阅读, 收获喜欢 2681 次。

关注

评论 3 条评论

发布
用户头像
这不可能吧,写配置都上 py?
2020-06-21 12:21
回复
用户头像
其实是个代码化选型的问题,一般来说这些场景中DSL强于通用编程语言强于标识性语言
2020-05-10 00:35
回复
用户头像
怎么感觉有点文不对题?
2020-04-30 08:03
回复
没有更多了
发现更多内容

AI 场景的存储优化之路(二)

焱融科技

人工智能 云计算 AI 高性能 分布式存储

imtoken钱包搭建,区块链去中心化钱包搭建

ToB迎来上市潮,谁是下一个IPO黑马?

ToB行业头条

IPO

JVM调优(一)

彭阿三

Github高分爆赞,一天遭狂转 10w+ 次!20万字的Java面试手册来了

Java~~~

Java 架构 面试 JVM 架构师

阿里P8耗时一个月肝出这份26W字Java面试手册,在Github标星30K+

Java~~~

Java spring 架构 面试 JVM

全靠这份阿里大佬的“Java进阶面试手册”收获蚂蚁offer

Java~~~

Java 架构 面试 算法 JVM

学完阿里P8推荐的实时流计算系统设计与实现PDF,终入淘宝

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

币安智能链智能合约DAPP开发|智能合约DAPP源码搭建

量化系统19942438797

dapp 币安智能链

Flutter 与 Swift - 在创建 iOS 应用程序时应该押注什么技术?

iOSer

flutter swift ios开发

引领异构时代,英特尔发布重大架构创新

科技新消息

Docker

彭阿三

全链路压测实践

超凡生

多种云资源管理用什么软件好?你知道吗?

行云管家

云计算 云服务 云资源

谷歌高级架构师十年心血终成Kubernetes微服务实战文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

阿里大牛耗时三年整理出来的4588页Java面试诛仙手册,已全面开源

Java~~~

Java 架构 面试 JVM 架构师

安装ApiPost-接口工具 发送HTTP请求

CodeNongXiaoW

大前端 测试 后端 接口测试 接口管理工具

【ShardingSphere技术专题】「ShardingJDBC」SpringBoot之整合ShardingJDBC实现分库分表(JavaConfig方式)

洛神灬殇

ShardingJDBC ShardingSphere ShardingSphere-Proxy 8月日更

上线半天下载量破100W!美团内部微服务进阶笔记,超详细

Java 架构 面试 微服务 美团

云小课|MRS基础原理之ClickHouse组件介绍

华为云开发者联盟

mapreduce 开源 Clickhouse EI企业智能 列式数据库

结合scipy.linalg在Python中使用线性系统

华为云开发者联盟

Python 矩阵 Numpy 线性系统 向量

币安智能链智能合约DAPP开发

Geek_23f0c3

智能合约 DAPP智能合约交易系统开发 DAPP系统开发 币安智能链

GitHub再现神作,阿里大牛面试30家大厂,整合出这份Java面试手册

Java~~~

Java 架构 面试 JVM 架构师

图解:为什么非公平锁的性能更高?

Java 程序员 面试 后端 计算机

神策分析 iOS SDK 代码埋点解析

神策技术社区

程序员 数据采集 埋点

MySQL 系列教程之(九)MySQL 必修:事务

若尘

MySQL 数据库 8月日更

gopher成长之路(三):出差小记

非晓为骁

个人成长 蜕变

人类高质量 Java 学习路线【一条龙版】

程序员鱼皮

Java 数据库 后端 求职 编程学习

终于读完谷歌高级架构师分享的Kubernetes源码剖析文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

聊聊 Kafka: 在 Linux 环境上搭建 Kafka

编程susu

Java IT 计算机 编程开发 技术宅

深度解读鸿蒙轻内核CPU占用率

华为云开发者联盟

鸿蒙 cpu 任务 CPUP LiteO

为什么配置模式令人抓狂?尝试用编程语言来写吧_语言 & 开发_佚名_InfoQ精选文章