YAML:很可能不如想像中那麼好

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

我早先寫了一篇文章《為什麼把json格式作為對人類友好的配置文件格式不是一個好主意》。今天我們將一起討論下YAML格式裡的幾個普遍問題。

默認非安全的

YAML是默認非安全的。加載一個用戶提供的(非可信的)YAML字符串需要謹慎考慮。

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

運行這條命令print(yaml.load(open("a.yaml")))你將得到如下結果:

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的翻譯文件中,有一個例子可以說明:

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

上面的例子看上去還好是嗎?但是,如果文件是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的提示物的。

奇怪的行為

下面的配置文件會解析成什麼呢?

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

沒錯,Yes被解析成了bool值True,如下:

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

再看一個例子:

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

3.5.3被識別成字符串,而9.3被當成數字處理,結果是:

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

再看下一個例子:

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

013是Tilburg一個著名的音樂廳,但是卻被解析成了八進制數:

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

由於以上這些錯誤,還有更多的錯誤case,許多經驗豐富的YMALers在使用字符串時,即使有時候不是必須那麼做,他們總是加上引號。許多人習慣不使用引號,這讓繼續編輯這個文件的也不使用引號的人(很可能是別的人)很容易忘記。

可移植性差

YAML很複雜,官方聲稱的可移植性強其實是被誇大了的。我們看從YAML specification中拿過來的一個例子:

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

先不說大多數讀者不知道這玩意兒是什麼意思,在Python中我們用PyYAML對它進行解析:

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

用Ruby對它解析是正常的:

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

python解析失敗的原因是Python不支持用列表作為字典的鍵

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

這個限制並不是只在Python中有,很多語言像PHP,JavaScript,Go等語言都有這樣的限制。

所以在YAML文件中使用這樣的表達,在大多數語言中都沒法解析。

另外一個例子是:

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

python報錯如下:

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

ruby輸出如下:

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

這是因為在一個yaml文件中包含多個文檔(每個文檔以---開頭),Python中需要使用load_all解析包含多文檔的yaml文件,而Ruby中使用load只能解析第一個文檔。據我所知,Ruby中沒有任何方法可以讀取多文檔配置。

在不同的實現之間還有許多不兼容的地方。

達到目標了嗎?

YAML specification中說:

YAML的設計目標,按照優先級從高到低排序:

  1. YAML便於人閱讀;

  2. YAML數據方便地在多種語言之間轉化;

  3. YAML格式和各種輕巧的語言的原生數據結構很接近;

  4. YAML能支持通用的工具;

  5. YAML支持一次性通過;

  6. YAML表達語義能力豐富,且可擴展性強;

  7. YAML實現和使用簡單

那麼YAML到底做得怎樣呢?

  • YAML便於人閱讀

當你只使用它的一個小小的子集的時候,這一說法才成立。使用它的全集很複雜,甚至比JSON和XML都複雜。

  • YAML數據方便地在多種語言之間轉化;

事實並非如此。很容易找出一些例子,是常規語言不支持的。

  • YAML格式和各種輕巧的語言的原生數據結構很接近;

見上文。為什麼只支持輕巧的語言(動態語言)呢?其他語言支持嗎?

  • YAML能支持通用的工具;

對此我都不知道它具體意思是什麼,也沒有找到關於此的詳細闡述。

  • YAML支持一次性通過;

對此我持保留意見。

  • YAML表達語義能力豐富,且可擴展性強;

是,它的確表達語義豐富,但是太豐富了,因為它語法很複雜。

  • YAML實現和使用簡單

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

結語

請不要誤解我的意思,並不是說YAML很糟糕,它既不是和JSON一樣問題多,也不見得比JSON好很多。它有一些在使用之初不會明顯顯露出來的缺點,我們也有一些另外的選擇,比如TOML或者其他格式。

就我個人來說,如果有其他選擇,我不太可能繼續使用YAML。

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

腳註

1在PHP中你需要修改ini配置文件來啟用安全的配置,而不是僅僅使用yaml_safe。PHP使用者通過配置使得一些機械的方法更機械。

2我並不想在空格和tab製表符之間製造爭論,但是如果允許使用tab鍵,通過增加tab寬度到更多的字符,可以使得閱讀縮進更清晰一些——這是tab鍵使用的意義所在。

反饋

如果有問題或者反饋,您可以發郵件給我:[email protected],或者在GitHub上創建一個issue。

英文原文:https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html
譯者:周遊


分享到:


相關文章: