挪威问题 - 为什么 StrictYAML 拒绝进行隐式类型转换,以及你为什么也应该这样做
有一段时间前,我遇到了一个老同事,他开始告诉我他遇到的一个有趣错误
"所以,我们开始通过创建配置文件来国际化网站。我们首先添加了英国、爱尔兰、法国和德国。
countries:
- GB
- IE
- FR
- DE
"一切都很顺利。但是,有一天,经过一次快速的配置更改后,情况变得一团糟。事实证明,虽然英国、法国和德国都很好,但挪威却不行..."
"当网站崩溃,我们损失了金钱时,我们追踪了许多死胡同,直到最终找到根本原因。
"事实证明,如果将此配置文件输入 pyyaml:
countries:
- GB
- IE
- FR
- DE
- NO
"你得到的返回值是:"
>>> from pyyaml import load
>>> load(the_configuration)
{'countries': ['GB', 'IE', 'FR', 'DE', False]}
在 False 中,雪很多。
当这被馈送到期望字符串形式为 'NO' 的代码时,代码通常会崩溃,通常会显示一个神秘的错误,通常是尝试使用 'False' 作为字典中的键时出现的 KeyError,而实际上不存在这样的键。
它可以通过使用引号来进行“快速修复” - 这肯定是一种修复方法,但有点像黑客 - 到那时,损害已经造成。
countries:
- GB
- IE
- FR
- DE
- 'NO'
然而,这个错误最悲剧的一面是,根据 YAML 1.2 规范,它是预期的行为。真正的修复需要显式地无视规范 - 这就是大多数 YAML 解析器拥有它的原因。
StrictYAML 通过忽略规范的关键部分来回避这个问题,试图创建一个“零惊喜”的解析器。
所有内容默认情况下都是字符串
>>> from strictyaml import load
>>> load(the_configuration).data
{'countries': ['GB', 'IE', 'FR', 'DE', 'NO']}
字符串还是浮点数?
挪威只是冰山一角。我第一次遇到这个问题时,我正在维护一个应用程序版本的配置文件。我最初有一个这样的文件 - 它没有造成任何问题
python: 3.5.3
postgres: 9.3.0
但是,如果我非常轻微地更改它
python: 3.5.3
postgres: 9.3
我开始收到类型错误,因为它被解析为:
>>> from ruamel.yaml import load
>>> load(versions) == [{"python": "3.5.3", "postgres": 9.3}] # oops those *both* should have been strings
同样,这导致我的代码出现类型错误。同样,我用引号进行了“快速修复”。然而,我真正想要的解决方案是
>>> from strictyaml import load
>>> load(versions) == [{"python": "3.5.3", "postgres": "9.3"}] # that's better
世界上最容易出错的名称
Christopher Null 的名字因破坏软件代码而臭名昭著 - 航空公司、银行,任何程序员由于不了解类型而造成的错误都曾让他头疼。
不幸的是,YAML 也不例外。
first name: Christopher
surname: Null
# Is it okay if we just call you Christopher None instead?
>>> load(name) == {"first name": "Christopher", "surname": None}
使用 StrictYAML
>>> from strictyaml import load
>>> load(name) == {"first name": "Christopher", "surname": "Null"}
类型理论上的问题
类型理论是编程语言中一个热门话题,设计良好的类型系统被(恰当地)视为一种可以早期捕获错误的工具,而设计糟糕的类型系统则为边缘情况错误提供了滋生的温床。
(同样真实的是,极其严格的类型系统需要更多前期投入,并且收益递减规律适用于类型严格性 - 这是回答“为什么用 Haskell 写的软件这么少?”这个问题的明智答案)。
另一个不太流行,但同样正确的观点是,像 YAML 这样的标记语言也存在类型问题 - 如上所示。
用户体验
从某种程度上来说,类型系统可以被视为数学问题和 UX 设备。
在以上以及大多数情况下,隐式类型违反了 UX 最小惊讶原则。