@
yagamil 明明命名困难是两大计算机科学难题之一,你不在乎就无所谓了?
你这首先用复用不复用来判断命名的方式是病,得治。
使用符号命名是有明确的目的性的,就是塞入语义上无关紧要(操作语义上就是某种 alpha renaming ;虽然遇到反射会失效)但有确切含义的元数据来辅助人类阅读者改善推理,同时语言实现不影响语义等价翻译(比如编译优化)的可行性。后者可以在语义上约定自己的假设而同时能被机器改善推理(比如 devirtualization )。
其实这就是所谓的封装性(encapsulating)的确切内涵。因为教学质量的普遍垃圾,很少有人理解,不管是 access control 这样在 name resolution 上加 hook 还是 inclusion polymorphism 这样在 call 上加 hook ,都只是 encapsulation 的实现细节而已。
你若只是因为要让看得清楚,而不管这里的原始目的,这是胡搅蛮缠。因为只是为了看清楚,而不是区分“这个不是那个”这种依赖元数据内容的场合,完全可以用编辑器辅助的纯语法方式(例如高亮边界)解决。
一般地,在一个合理的设计中要求实体具名,充分必要条件是这个命名属于用户关心的 API 的一部分——只有这种场合人理解命名的冗余含义才能确保可能帮助准确地正确使用(当然前提是命名对了)而不是干扰人类读者的冗余噪声。否则,都是耍流氓。
有一种常见的误解是关于人为在源代码(而非实现内部变换)中引入“中间变量”的必要性。
实际上如果不考虑一等状态(比如 C++的那种 RAII ),任意的显式声明引入的中间变量在逻辑上是冗余的,只不过因为考虑读者的脑容量有限,避免发生 register spill 之类的问题,所以有的作者才试图这样做。
但是,这种中间变量完全可以 canonicalize 成 let form 。(学 ALGOL-like 语言学傻的不容易理解这一点,因为不知道 block 实际上是 lambda 的 derivation——用 JS 的话来说就是 IIFE 。)
这种情况下,到底什么变量才是真正有资格当中间变量的问题才会显著——只有能被复用时,才配被提升到 let 的变量名的位置上。
那么不被复用的复杂表达式可读性差怎么办?很简单,拆成具名函数的调用。引入的函数名就不是这种中间变量了,它可以是内部 API ,即便只被用到了一次。
之所以函数名和普通的变量名区别对待,是因为具名函数的实现默认是封装的(函数体不可见),因此它有资格单独占据名称。
当然,如果考虑一等状态,比如 lifetime 这种依赖对象自身存在的情况,那就有复杂多了的例外——因为存在隐含的资源释放,实际上有类似函数调用的作用。不过,遵循约定的代码反而会更清晰可读:如果严格遵循“不复用的对象不在 let form 中出现为变量名”,人很容易推理出为什么需要有孤零零(甚至一次都没用到)的对象;反之就麻烦多了,读者要自行验证这里到底复用了几次才放心。结果,在这个上下文中尽量排除没有复用的具名对象,反倒更合理了。
题外话,显式类型(explicit typing)和命名的必要性同等,也就是在 API 上才配用。所以 bb 什么 auto 啊 var 啊看不清楚类型的,那也是耍流氓。