我眼中的匈牙利命名法

上个月Linus通过Linux内核邮件列表一怒为注释,要求内核开发者“get rid of thebrain-damaged stupid networking comment syntax style”。估计Linus早就对网络协议栈代码里面不伦不类的注释风格颇有微词。当有人存在即合理式的要求沿用这种注释风格时,终于爆发了,火力全开的喷了一顿。最后号召大家看到类似风格怪异的注释,顺手改之。终于找到提交内核代码的捷径!窃喜窃喜:)

 

窃喜之余,想到匈牙利命名法又是何等的相似。要求弃用匈牙利命名法的固网新规范已推出许久,但匈牙利命名法依然大行其道。新规范遇冷的一个原因正是已有代码大多都使用这种命名法,于是就像协议栈的情况一样,开发者自然而然的沿用匈牙利命名法。如果使用新的命名方式,势必会出现两种命名法共存,这该如何是好?

 

在寻找破冰的方法之前我们先拨开历史的迷雾,看看匈牙利命名法到底是何方神圣

匈牙利命名法(下文简称为HN,即HungarianNotation)的雏形始于无类型语言BCPL,后经匈牙利裔美国人Charles Simony引入到开发Office的微软应用软件部,因其单词排列顺序类似古怪的匈牙利姓名而得名,后来也称作应用型匈牙利命名法(Apps Hungarian,下文简称为AHN)。而后AHN被曲解引入到系统软件部,形成了系统型匈牙利命名法(SystemsHungarian,下文简称为SHN)。再随着Windows的方兴未艾,四处开花以及各种开发文档的推波助澜特别是广为流传的《Windows 程序设计》第一版大量使用匈牙利命名法SHN逐渐成为Windows开发的事实标准。但是随之而来的也有反对的声浪,并在.net第一次发布时达到了高潮。微软在.net框架的《通用命名约定》中明确说明不建议使用匈牙利命名法(原文为DO NOT useHungarian notation)。而后Windows 程序设计》第六版已经不怎么使用匈牙利命名法了。

 

为什么SHN会这么不受待见呢?我们先看看SHN是怎样一种命名方式。它是把变量实际的类型简单的罗列在变量名之前。以C语言为例,如变量iCount中的i表示这个变量的类型是语言内置的intstFooTmp的类型是用户自定义的结构体Foo。这些类型因为是实际的类型,又称之为物理类型。

SHN犹如符在变量身上的咒语。好处在于我们随时随地都可以透过这段奇异的符文看到变量的类型。在以前IDE尚显稚嫩的时代,倒是一用无妨。但是到了如今IDE足够智能之时,SHN就有些时过境迁了。借助IDE的魔法,我们也能随时随地知道变量的类型,甚至更多,比如变量是在哪里定义的,还能提供一些实时的错误检查。

对比之下,SHN更像是一种没有约束力的提示,就像注释一样。就算变量名里面的类型信息跟实际不符,编译器依然是安静的那一个,这时只能依靠人肉检查器,我想没几个人愿意成为肉盾。毕竟这是编译器的领域琐碎的类型检查交给工具去做不是更好?编译器不会眼花,还能给我们更多的提示。

从设计上来看,SHN没有提供有益的抽象,是把类型平铺直叙的列举出来,有时还十分冗长和拗口。例如,下面这个摘自viki上的变量说明:

a_crszkvc30LastNameCol:

a constant reference argument, holding the contentsof a database column LastName of type varchar(30)which is part of the table's primary key.

这个例子中变量的含义淹没在类型中。如果C++模板用上SHN,变量名该有多长。细思极恐。。。KISSKeep It Simple Stupid原则看来是满足不了了。

再看看下面这个赋值语句能不能赋值

ulFoo = ulBar;

单单从变量名上,这两个变量的物理类型是一样的。但是从概念上是不是同一类的就不得而知了。

再则SHN违反了SRPSingle Responsibility Principle原则。变量名聚合了两个不怎么相关又不在同一层次的东西,变量的含义和类型。变量含义在概念层次上,而变量类型是在实现层上。思维在两个层次上来回跳转,一会上天,一会入地,最后头昏眼花。于是我基本上已经养成忽略变量前缀的习惯,一如忽略注释的习惯。我之所以如此,还有因为HN本身的免责声明:本变量名按现状提供,不提供变量名同步于变量类型之保证。自然时不时会遇到变量名跟变量类型不相符的情况修改变量类型时,还得同步修改变量名,而且不只一处!于是DRYDon't Repeat Yourself原则也被打破了。

SHN依赖过甚,部分是由于变量定义和使用隔得太远导致的。换言之,函数过长。如果函数动辄上百行,再加上C89要求变量定义必须放在代码块开头(再加上有的同学把代码块开头误解为函数开头),于是乎,变量的定义和使用不在一屏里面就只能借助额外的手段来查变量类型了。如果函数很短,比如十行,还需要这样煞费苦心吗?

 

相比SHNAHN做了一层抽象,放在变量名前面不是物理类型,而是逻辑类型。例如如下代码:

int rwWidth, colWidth;

……

rwWidth = colWidth;

rw表示行(row),col表示列(column)。行跟列虽然物理类型是一样的,但是逻辑类型不一样(一个是行,一个是列)。把列赋给行,跨越了逻辑类型,可能存在问题。

从上可以看出,AHN构建了一个薄层,将物理类型转换为逻辑类型。逻辑类型将对象分门别类,紧随其后的名字有时候像是个属性。一般来说,逻辑类型跟名字要像X轴和Y轴一样尽量正交。

AHN的问题在于这一层只对人有意义,编译器不在乎,运行时也不关注。当然,这也有好处--几乎没有编译和运行的开销。但是坏处也显而易见没有编译器的支持,约束力有限,就算将两个不同概念的变量赋值也能编译通过,又得人肉编译器出马了。。。Coder表示不开心,压力山大。

到了面向对象的时代,逻辑类型的约束可以通过自定义的类来展现出来。比如以前例子中的行和列,可以分别定义一个RowColumn类,重载算操作符,再禁止RowColumn相互的赋值。这种约束方式既可以借助编译器的检查,也能通过代码显式的表达出来。

 

搞清楚匈牙利命名法之后,下一个问题是怎么破冰?让我们先看看Linus是怎么号召大家清理注释呢?他在邮件最后说道:So just get rid of the(no-no) and (no-no-no) forms. Not in one big go, but as people touch the code,just fix that mess up.

同样的,我们也不需要发起一次大扫荡,而是按需重构。那些本来就很稳定而又没有新需求的代码可以保持不变。新增文件和函数就按新规范来,不要再用匈牙利命名法了。至于修改已有代码,建议顺手重构。慢慢的,代码就变得更简洁了。

 

后记

最近听闻又冒出一个有望取代C++的新语言Rust。好吧,我只想说C++ will never die""字说明了一切。再加上C++11的进化步幅是如此之大。说话回来,Rust的名字由来众说纷纭而十分有趣。其中一个版本的解释来源于其字面意思--铁锈。话说一直以来编程语言领域有许多"老旧"的技术本是某些问题的灵丹妙药,却止步于实验室,随即束之高阁,无人问津。例如最近火起来的两种通用并发模型--actor模型和CSPCommunicating Sequential Process模型,本是上个世纪70年代的老古董。值得一提的是CSP是由Tony Hoare提出的,Tony的一句名言对我影响很大,详见附录。所以,Rust不引入闪闪发亮的新技术,而是将生锈的老技术付诸实践,化腐朽为神奇。我们也莫不如是三分热情的关心最新出炉的黑科技,以期找到银弹,结果却陷入死循环当中。高手之路看似高冷,其实一方面是打破已有的知识结构学习吸收新的编程范式;另一方面把知识化整为零(就像迭代开发一样),不断的重复练习小小的“零”。我们如果把已有的优秀编码方式和习惯真正践行起来,效果将是巨大的。借用《周易》上的一句话:那将是积善之家,必有余庆”。

 

附录

  1. Linux咆哮邮件,里面他列举了正确和错误的注释方法: http://lkml.iu.edu/hypermail/linux/kernel/1607.1/00627.html
  2. 微软.net框架的命名约定: https://msdn.microsoft.com/zh-cn/library/ms229045.aspx
  3. Joel Spolsky 的《软件随想录》第十三章“让错误的代码显而易见”对于匈牙利命名法有更详细的介绍
  1. Tony Hoare的名言:构建软件有两种方式:一种是使其足够简单,以至于明显没有错误;另外一种是使其非常复杂,以至于没有明显的错误。原文为:There are two ways of constructing a piece of software: One is to make it so simple that there are obviously no errors, and the other is to make it so complicated that there are no obvious errors.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值