作者:三点水 来源:https:///2021/Explicit-is-Better-than-implicit “Explicit is better than implicit” 是 The Zen of Python 中的一句格言。长久以来都觉得挺在理,直到有天有人用这句话为基础,提出了一个我不甚赞同的观点,才发现从来就没有真正理解过它。 神奇的是在搜索过程中,发现讨论这句格言的并没有多少,不同的讨论中对 "explicit" 含义的理解差别也很大,最终发现最好的讨论来自 Elixer 社区:On 'Explicit is better than Implicit’ 。本文尝试列举见过的一些观点,以及自己的理解。 Explicit 是什么含义?Explicit 这个单词释义为:
“清楚详细地陈述,不容混淆或怀疑”。翻译成中文有“显式的”、“精密”、“不含糊”、“明确的”等多种翻译。在代码的语境下,什么样的代码才能称得上是 “explicit” 呢?网上看到了不同角度的观点。 一些观点把代码显式写出来显式地写出代码,也可以有多种理解方式,Making Games with Python and Pygame 中举了一个示例:
书中提到最后一行显式写 return None 能让读者更直接理解代码的用途。 思考:这个样例容易理解,也很赞同,但是如何推而广之呢?Explicit 在上面的例子中体现在哪呢? 我理解它的重点在于,当所有的 if 语句都不命中时,默认的行为是未知的,而显示写出的 return None 则清楚地描述了默认的情形,即使 Python 的默认行为发生变化,该方法的行为也不会发生变化。 要具体、要特化这篇文章 明确表达了自己对“Explicit is Better than Implicit”的理解:
Explicit 意味着要具体、特化,不要抽象、通用。同时不要隐藏函数的行为。 对于要具体、特化,文章中举例如下:
对这个例子也是比较认同的。但我认为重点不在于 requests.get 怎么好,而在于 import * 不好。因为这样的话 get 方法的来源就不明确了,容易混淆,需要靠猜。相对的,下面的代码我认为也是好的:
这里我的理解是,代码执行的逻辑,从溯源的角度上没有二义。如果用了 import *,同时两个模块中都有 get 方法,则容易混淆,不知道真正执行的是哪个方法。 不要隐藏函数行为同样来自上面提到的文章 ,示例如下:
这个示例我不认同。从观念上,它与 OOP 中提到的封装的思想;从使用上,文件类型的判断的要求并没有消失,只是丢给用户自己实现了;进而从结果上,只要有多种文件类型存在,在某个层级上一定会有一个 read 方法的。 举例说,如果说不要隐藏函数的行为,那么我们在写 Web 服务的时候,在我们访问 DB 时,我们会希望直接处理 TCP 连接吗?Spring 框架选择隐藏这些行为,可以说是错误吗? 关键在于“预期”,与预期相符就是“Explicit”的,正如Elixer 社区的讨论 : “Don’t surprise me”。于是 Explicit 的要求演变成如何给用户正确的预期?我的回答是:良好命名,遵循 common sense,除此之外需要教育。 No MagicDjango 的 Design philosophies 中有如下描述:
这里指的是不要用复杂的语言特性(大家常把元编程称作 Magic)。 这个观点的持保留意见,我认为重点还是在于知识、背景是否匹配。例如当我熟悉 Decorator 时,就会觉得用 decorator 来指定一个 REST API 的路由很直观,很容易理解。但对于不熟悉的人可能就完全不能理解数据的路径(data path),不知道为什么一个注解是怎么真正完成 URL 到函数的绑定的。 含义更直接还有一些讨论会指向同一段逻辑的不同写法,例如SO 上的讨论 举的例子:
这是一个“矛盾”的讨论,题主认为 ② 是 explicit,下面的回答则指出写成 ① 的方式能应对更多的情形,如 a 不是列表的情形。我能理解 ① 的作用,但同时也赞同题主的观点,从阅读的角度来说 ② 是更直接的。 还有 Elixer 社区的讨论 ,例子一方面说明什么是语义上的“直接”,也间接反驳“不要隐藏函数行为”的观点:
从阅读代码的角度,明显上最后一种最容易阅读,更符合语言习惯,不容易有歧义。 我所理解的 Explicit上面我们看到,“Explicit is Better than Implicit”这句话本身就是 implicit 的,有很多歧义的理解。 我自己的总结是:Minimal Knowledge, No Surprise。 对于阅读者/使用者而言,需要最少的知识去理解它,在我们隐藏复杂度的过程中,要保证函数/API/…行为符合预期,没有意外。 例如对于函数最后加上 return None 比不加要好,因为加上后,我们就不需要了解 Python 函数的默认返回值是什么。类似的,显式引用会更好:from requests import get,因为读者不需要去找 get 方法的来源,以及有重名时是哪个函数生效。 对于“不要隐藏函数行为”的做法,就有一定的反对意见。例如 read_csv 和 read_json 是否优于 read 方法?我认为此时 No Surprise 很重要。对于 csv, json 等文件格式我认为 read 更优,因为根据扩展名判断类型是一个共识,并不会有 surprise 发生。而如果读取的是 HDFS 上的文件,由于很多文件保存时并不会按扩展名保存,我认为此时 read 就容易有 Surprise,因此是不合适的。 对于 Magic,如果做法不是 common sense,则需要我们额外学习 Magic 的含义,就是属于"Implicit" 的,此时用来是不用,就要看它能给我们带来多大的好处了。同样的还有语言中的语法糖,经常需要额外学习知识才能看懂/自己使用。 最后对于“含义更直接”,认为在 No Surprise 的前提下,越接近“共识”越好,因为需要更少的知识。 小结关于 “Explicit is Better than Implicit?” 的理解,文章罗列了网上搜索的一些观点:
最后总结并说明了自己对 “explicit” 含义的理解:Minimal Knowledge, No Surprise 当然,我们会发现 Minimal Knowledge 或者说“共识”对于不同的群体,在不同上下文之下是不同的。这也是我们需要经验去理解,需要花时间去沟通的内容了。 |
|