我早先寫了一篇文章《為什麼把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鍵使用的意義所在。
反饋
如果有問題或者反饋,您可以發郵件給我:[email protected],或者在GitHub上創建一個issue。
英文原文:https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html
譯者:周遊
閱讀更多 Python部落 的文章