YAML:很可能不如想象中那么好

我早先写了一篇文章《为什么把json格式作为对人类友好的配置文件格式不是一个好主意》。今天我们将一起讨论下YAML格式里的几个普遍问题。

默认非安全的

YAML是默认非安全的。加载一个用户提供的(非可信的)YAML字符串需要谨慎考虑。

运行这条命令print(yaml.load(open("a.yaml")))你将得到如下结果:

其他许多编程语言(包括PHP1和Ruby)也是默认非安全的。在GitHub上搜索yaml.load,你会得到高达280万条结果,而搜索yaml.safe_load只能得到2.6万条结果。

值得一提的是,许多情况下,yaml.load()是OK的——也就是说用yaml.load()加载配置文件是OK的,这主要是因为配置文件的来源通常(而非总是)可靠的,其中许多配置文件来自于静态的yaml测试文件。但是仍然有人会好奇,在280万条结果中,究竟有多少不安全的case呢?

这不是一个纯理论的问题,在2013年有人发现每一个Ruby on Rails应用程序在远程执行代码的时候容易受到攻击。

有人可能会说这本质上不是yaml的错,而是调用yaml的库使用不当导致的。但是需要明白的一点是,许多的库默认是不安全的——尤其对于那些动态语言来说。所以,归根到底,还是YAML的问题。

也有人会说,这个问题可以通过用safe_load()代替load()轻松地得到解决。但是问题是许多人并没有意识到load()的这个问题,或者即使知道load()存在这样的问题,也很容易忽略这一点。所以load()是一个很不好的API设计方案。

难以编辑,尤其对于大文件

YAML文件编辑起来费力,当文件变大时,编辑愈加困难。

在Ruby on Rails的翻译文件中,有一个例子可以说明:

上面的例子看上去还好是吗?但是,如果文件是100行,甚至是1000行呢?那你很难知道你在文件的哪处,因为屏幕已经显示不下了。你必须滚动页面才行,但是这时候你还得跟踪缩进。即使有缩进提示,这时候跟踪缩进也很难,尤其是YAML使用2个字符缩进,而禁止使用tab进行缩进2。

当你意外地弄错了缩进的时候,并不会以error的方式提示你,但你会得到一个你根本想不到的结果。那就尽情享受debug吧。

我用Python编程已经有十年了。我习惯了使用空白字符,但有时候我仍然会纠结于YAML。由于Python一般没有长达几页才能写下的函数,失去清晰性和可读性这一点不是很明显,但是对于数据或者配置文件来说,长度并没有限制。

对于小文件而言,这当然算不上什么问题,但是一旦变成大文件,YAML就不那么好用了,尤其是你将来还要对它进行编辑的时候。

它很复杂

从一个简单的demo来看,YAML简单明了,而实际上并不是这样的。YAML specification包含23449个词,对比来看,TOML只有3339个词,JSON只有1969个词,而XML是20603个词。

我们中有谁完整地读了它的详细说明?有谁不仅读了,还完全理解了所有内容?又有谁读了、理解了并全部记住了它们?

例如:你知道YAML有9种不同的方式书写多行字符串吗?这9种方法只有细微的不同。

如果你查看《YAML的9种实现多行字符串的方式》那篇文章的修改历史,事情会变得更有趣,因为作者发现越来越多的方式可以实现多行字符串,而且其间的微小差别也越来越多。

YAML specification的序言如下(黑色加粗部分是我的强调):

这一章节简单地展现了YAML强大的表达能力。我们并不期望第一次阅读此文的读者理解所有的东西。相反,这些章节是用来作为YAML specification的提示物的。

奇怪的行为

下面的配置文件会解析成什么呢?

没错,Yes被解析成了bool值True,如下:

再看一个例子:

3.5.3被识别成字符串,而9.3被当成数字处理,结果是:

再看下一个例子:

013是Tilburg一个著名的音乐厅,但是却被解析成了八进制数:

由于以上这些错误,还有更多的错误case,许多经验丰富的YMALers在使用字符串时,即使有时候不是必须那么做,他们总是加上引号。许多人习惯不使用引号,这让继续编辑这个文件的也不使用引号的人(很可能是别的人)很容易忘记。

可移植性差

YAML很复杂,官方声称的可移植性强其实是被夸大了的。我们看从YAML specification中拿过来的一个例子:

先不说大多数读者不知道这玩意儿是什么意思,在Python中我们用PyYAML对它进行解析:

用Ruby对它解析是正常的:

python解析失败的原因是Python不支持用列表作为字典的键

这个限制并不是只在Python中有,很多语言像PHP,JavaScript,Go等语言都有这样的限制。

所以在YAML文件中使用这样的表达,在大多数语言中都没法解析。

另外一个例子是:

python报错如下:

ruby输出如下:

这是因为在一个yaml文件中包含多个文档(每个文档以---开头),Python中需要使用load_all()解析包含多文档的yaml文件,而Ruby中使用load()只能解析第一个文档。据我所知,Ruby中没有任何方法可以读取多文档配置。

在不同的实现之间还有许多不兼容的地方。

达到目标了吗?

YAML specification中说:

YAML的设计目标,按照优先级从高到低排序:

YAML便于人阅读;

YAML数据方便地在多种语言之间转化;

YAML格式和各种轻巧的语言的原生数据结构很接近;

YAML能支持通用的工具;

YAML支持一次性通过;

YAML表达语义能力丰富,且可扩展性强;

YAML实现和使用简单

那么YAML到底做得怎样呢?

YAML便于人阅读

当你只使用它的一个小小的子集的时候,这一说法才成立。使用它的全集很复杂,甚至比JSON和XML都复杂。

YAML数据方便地在多种语言之间转化;

事实并非如此。很容易找出一些例子,是常规语言不支持的。

YAML格式和各种轻巧的语言的原生数据结构很接近;

见上文。为什么只支持轻巧的语言(动态语言)呢?其他语言支持吗?

YAML能支持通用的工具;

对此我都不知道它具体意思是什么,也没有找到关于此的详细阐述。

YAML支持一次性通过;

对此我持保留意见。

YAML表达语义能力丰富,且可扩展性强;

是,它的确表达语义丰富,但是太丰富了,因为它语法很复杂。

YAML实现和使用简单

结语

请不要误解我的意思,并不是说YAML很糟糕,它既不是和JSON一样问题多,也不见得比JSON好很多。它有一些在使用之初不会明显显露出来的缺点,我们也有一些另外的选择,比如TOML或者其他格式。

就我个人来说,如果有其他选择,我不太可能继续使用YAML。

如果你一定要使用YAML,我建议你使用StrictYAML,它移除了一些(尽管不是所有的)不安全的部分。

脚注

1在PHP中你需要修改ini配置文件来启用安全的配置,而不是仅仅使用yaml_safe()。PHP使用者通过配置使得一些机械的方法更机械。

2我并不想在空格和tab制表符之间制造争论,但是如果允许使用tab键,通过增加tab宽度到更多的字符,可以使得阅读缩进更清晰一些——这是tab键使用的意义所在。

反馈

英文原文:https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html

译者:周游

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180723A09CTA00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券