[声明]:本英文资料源自于Herb Sutter 创建的“Conversation”栏目,“C++ 翻译小组”的翻译作品供学习交流与参考用途,不得用于任何商业用途。未经Herb Sutter、Jim Hyslop同意,不得转载;对于违反以上条款,翻译小组对此不负任何责任;特此声明。
文章来源:http://www.gotw.ca
版权归属:Herb Sutter and Jim Hyslop
译 者:徐波
对话#17: 匈牙利命名
“突破!”一个官员大叫道,她的脑袋探入杂乱的大厅。
好几个人,包括珍妮和我都抬起了头。“什么?”“谁的突破?”“怎么回事?”
这突然的兴奋是显而易见的,而且可以理解,自从高官们知道我们失去了与轨道站和主地面基地的联系后,情况已变得很糟了。看来,它们已被入侵部队所占领,尽管我们的官员尚未正式承认我们已处于敌对的环境中。当消息到来时,我们已经秘密来到靠近挖掘地点的当地基地两天了。而这些古外星文物的研究工作进展之缓慢令人发狂,因为我们知道离突破只是一纸之隔。珍妮看上去比任何时候更接近于解决电源问题。
“雅格说他已破解了部分语言,就是这样。”她告诉我们。那些刻在古老的,绝大部分已被废弃的设备上的隐晦文字正是困扰工作进展的一个主要障碍。到目前为止,对它的破解大体上还是成功的。“是一些关于内置的冗余性,他大致揣摩出它的意思。不要涌过去打扰他,他需要集中精神。虽然你们很想知道。”随后,她的脑袋又不见了,肯定是到别的地方传播这个消息去了。
“冗余信息,”我坐了回去,高声自语,“这听上去有点熟悉。为什么,回想我的第一个工作……”
- - - - - - - - - - - - - - - - - - - - - - - - -
“疙瘩!”
“您说什么?”我回过头,把目光转向正发表评论的Guru。
“你长疙瘩了,我的孩子。”
我听完松了一口气,自信地问道:“哦!哪里?我用的药物护肤膏通常能……”
“你最近的代码里有疙瘩!”她打断说,“也就是那种称作匈牙利记法的东西。你的变量开始象这副样子,”她拿起笔,在白书写板上写道:wartHog(疣猪),“在这种情况下,变量hog的疙瘩(wart)与陶器学校(pottery school)[1]毫不相干。”
“哦,是这样,”我长出一口气,“匈牙利记法?是这样吗?是的,对。我读过一篇很酷的关于匈牙利记法的文章,这种方法的思想很不错,有一阵子大家对它可是趋之若鹜啊!它几乎成了清楚表达类型的灵丹妙药,从某个角度讲确实如此,你也知道的,所以我……”
“胡说八道!”Guru大叫。
“啊…咋,”我结结巴巴地说,“我想我已经解释清楚了。”
“不是你,我的孩子,”Guru修正道:“你所选择的命名习惯在这些代码中的试验结果一塌糊涂,令人晕头转向。即使是伟大的先知Petzold也并不墨守成规。”(译注:Petzold在他的巨著《Programming windows》中采用匈牙利记法。由于该书影响力巨大,故他间接也成了匈牙利记法的布道者)
“呜呼!”我登时眼冒金星,稍俟平静后,我辩解道:“是这样的,匈牙利记法被认为能使代码更容易书写和阅读。例如我可以仅仅通过阅读代码来捕获一些类型错误。如果我写了象strcpy(szDestination, pachSource)这样的代码,我马上就能发现问题——第二个参数是个指向字符数组的指针,并不能确保满足strcpy必须以null结尾的要求。或者,如果我写了printf(“%s”, ulValue);,我能够发觉我在需要传递一个以null结尾的字符串的地方传递了一个unsigned long。这些代码直接就能告诉我错在哪里。”
“为了很值得怀疑的实际好处,却要付出使用C语言中的类型不安全的调用,或营造一个几乎一切都是可能造成类型混乱的int或void*的句柄的环境的代价,”她悲哀地摇摇头,“并要在C++这种类型安全的面向对象语言里引入没有实际意义的东西。这些,你必须忘却。赘物并非有用信息,它们只会起误导作用。”
“考虑一下你自己的比喻。以你的第一个例子为例,在C++中,你会使用string,这是对象,而且象第一个这样的问题不会发生,因为string定义良好,封装严密。象string destination = source;这样的代码不会产生缓冲区拷贝语义上的错误,因为它们从不暴露自身,永远不会要求调用它们的程序员来帮助处理。实现细节被合理而又切实地封装,无需你来操心。至于你的第二个例子,在C++中,你应正常地使用流或其它类型安全的方法来写输出,而类型检查和重载解析能保证产生类型正确的结果,即使需要类型转换。象cout << value;这样的代码不会引起类型错误:如果value是个unsighed long,那么作用于unsigned long的operator<< 将被触发;或者,如果value属于一种没有定义过operator<< 的类型,但它可以明确地转换成一种定义过operator<< 的类型,那么该operator<< 就会被触发;在其它情况下,就产生一个编译错误。C++语言和标准库不仅能检测到使用其它类型不安全的调用可能引起的运行期类型错误的隐患,而且能将它转换成编译期错误。
“总之,关于对象的类型,编译器知道的要比你多得多。改变变量的名字,蕴入类型信息,其好处实在有限,而且使之更脆弱。如果说在C风格的语言里使用匈牙利记法还存在一些理由的话(即使这点也存在争议),那么在类型安全的语言中使用它则是有害无利。”
“也许如此,”我勉强同意,如果我不说几句,就变成她唱独角戏了,“但你必须承认,一旦你搞清楚规则,匈牙利记法能使创建变量名更容易。”
“你这么确信?”Guru扬起眉,靠在客椅上,我知道她正在酝酿一场辩论。“当我说脆弱时,我自有道理,你倒说说看,怎么个更容易法?”
“因为类型会告诉你变量的身份,这已经成了机械意识了,”我说,“变量名从它的类型名产生更可亲一些。如果你知道类型,你就可以引申出变量名。”我边说边晃来晃去,这时才意识到自己动个没完,赶紧停了下来。当我开始说话时,我隐约感到要说点更深奥的东西;现在我就听见这些词迸出我的嘴,听上去有些迷信色彩。我记得自己当时感觉很奇怪,第一次把话说得如此深奥。
“如果类型改变了呢……?”她催促道。
“哦,我想您不得不修改变量名了,但是!”我激动地说,“这也迫使你在自己的程序中检查变量的每个用途,保证它仍然被适当地使用。”
“强辞夺理!”她反驳道,“它会迫使你不这样做。”在许多情况下,程序员将简单地忘记,或更糟,干脆不愿坚持在全局范围内修改变量的名字,更不想去检查其用途。一旦他们不修改变量名,麻烦就横在你面前,违反了下面这条戒律:‘把你的每个方面都讲给他的邻居听’而这,”她平静地摇摇头,“却是该受批评的。或迟或早,但多半很快就会从欺骗性的疣猪风格中诞生不幸的累赘信息。
“哈,”我笑了,“这正是全局搜索替换大显身手的地方!”
“事实正好相反,”她再次摇头,“难道没有想过你可能不经意地修改其它类似对象的名字,而它们的类型事实上并无改变!但即使全局替换能正确地工作,它会增加什么价值吗?它给程序员带来的麻烦远大于好处。如果在类型改变时产生了一个错误,比如由于隐式类型转换引起,不管对象叫什么名字,错误还是依旧。修改变量名即笨拙又毫无意义,除了增加文档工作量,还有什么好处?这样做只是马后炮,到头来还是一场空。”
看来我该稍作退让,并改变作战目标:“但您还是存在错误,不管您采用哪种命名体系。假如我有个int count;我把它改为short count;,这样许多程序员可能不会去检查其用途,只是希望编译器捕捉各种跟范围有关的问题。”
“这个嘛,”Guru承认,“正是我刚才说的。不管你是否丑化变量的名字(指使用匈牙利记法),问题是一样的。而且一旦丑化了变量名,你只会增加无益的维护性工作,因为你必须额外地维护这些臃肿的名字。如果你还不确信这一点,我的孩子,我给你准备了一个小问题:你怎么将匈牙利记法应用于模板?”
这把我问住了。“接触? ”我承认,“我想模板实际上并没有自己的类型,因为模板产生未知数量的类型,每一个都对应于实例化的参数。在实例化之前不存在类型,所以你事实上无法创建一个能代表模板自身类型的变量名。”
“讲得好,说得妙。更进一步,”Guru补充道,“在模板内部定义中,你怎样在模板参数类型的对象的名字里增加些丑陋的前缀?你不知道它们是什么。”
“哦,我明白了,”我说,“你的意思是这样。”我信手在白书写板写了一个例子:
template<typename T>
T AddOne( T wartT ) // 该添加怎么样的前缀?papuch?或者lpsz,还是huh(handle to unbounded harm)?
{
return wartT + T(1);
}
“我相信源代码是一种交流的方式,”Guru强调说,“问题是,交流的目标是谁?编译器?不,源代码是一个程序员与其它程序员交流的一种媒介。它是一种对意图的表达,一种对期望结果的表达。我们必须努力使交流尽可能的简单和清楚。为了做到这一点,变量名应当反映变量所扮演的角色。而变量的确切类型相形之下居次要地位。一个象sz这样的变量名只能告诉你你所看到的是C风格的字符串。至于该字符串所扮演的角色,你无法通过它来了解。”
“可是,”我插嘴道,“我并不认为有人会仅仅以sz作为变量名。”
“为什么不是?如果要讨论匈牙利记法,我们应当讨论Simonyi[2]博士所提出的规范版本。他在例子中使用了象sz、pch这样的变量名。这些变量名啊!真是没提供什么有用信息。如果这些变量名分别换成象xyzzy和yeti这样的名字,或简单地以x和y作为变量名,我只要简单地看一下它们的声明就能知晓第一个变量表示一个字符数组,而第二个变量是指向字符的指针。把它们称作sz和pch并不能增加一些在代码中未提供的有用信息,特别是,它并没有增加编译器尚未熟知的信息。更糟的是,一旦确定了前缀,当类型改变时,它就会提供虚假信息。在任何情况下,即使该前缀不曾表达错误信息,问题仍然存在:这些变量是什么?使用它们的目的何在?它们到底做了些什么?它们是怎样使用的?而这些前缀根本无法解答这些问题。我必须仔细研究代码来确定它们所扮演的角色。这样,代码就不能体现程序员间相互交流的意图。所以象sz和pch这样的名字是很糟糕的选择。”
“那是您的看法,”我继续顽抗。
“是我的想法,”Guru点点头,“是的,现在仍只能说是一种想法,而不是无情的事实,虽然事实上象sz这样的名字接近无用,而匈牙利记法在类型转变面前显得脆弱。几年前,当我们草拟代码风格指导方针时,匈牙利记法是激烈辩论的焦点之一。不象其它有些讨论,这方面的讨论相对理智和温和。我们检查了命名约定,聆听了该方案使用者的经验之谈,最后意见趋于统一,也就是我们不提倡这个约定。虽然并没有厌恶到彻底禁止其使用的程度,但我们认为它是脆弱的,可以说不提倡使用它。这里,我引用了小组的标准:
“避免使用匈牙利记法,它会让你的承诺落空。
赘物并非信息,而是混淆耳目的伪信息。”
“匈牙利记法不仅是个说谎精,而且富含胆固醇。它很可能使你大腹便便,让你的生活变得挥霍放荡。说真的,我记得只有一次,在某个工程中匈牙利记法找到了用武之地。”
这引起了我的兴趣:“是怎么样的?”
“在这项工程里,有个程序员名叫保罗(Paul),”Guru解释道,“在工程中奋战几个月后,头发长到可以扎马尾辫了,可仍然忙于创建他的报告-书写模块(report-writing module)。他认为匈牙利记法帮助他找到了身份的感觉,因为现在他知道了自己的身份……”Guru停住话头。
我眨了眨眼,约十来秒钟后,我闭上双眼,扮了个痛苦的鬼脸。“指向unsigned long数组的指针(Pointer to array of unsighed long,首字母缩略语为Paul)。”我悲叹道。
她笑了,我的痛苦成了她的欢乐之源。“这是真实故事。”她说道[3]。
此时,我觉得自己找到了一种方法,能让Guru难以自圆其说。“好,”我迅速从窘迫中恢复过来,故作无辜地问道,“那我们为什么还要在成员变量中使用下划线这种拖尾巴后缀呢?这难道不是匈牙利记法的一个变种吗?
Guru愉快地笑了:“所以这看上去象是不确定的东西,但此时很容易让人上当。下划线跟类型毫无关系,它只跟标志范围和私有化有关。它也有一个小小的实际好处:我们能意识到传递一个参数到一个成员函数,尤其是初始化函数或构造函数,我们可能经常想用同一个名字来表示成员变量和传递参数。例如:
class T
{
int count_;
public:
T() : count_(0) {}
void init(int count) { count_=count; }
};
“我们给予init函数一个叫count的变量,为什么要给参数取个另外的名字呢?参数和成员变量扮演的角色是一样的啊!事实上我们为这种情况讨论了好几种方案,比如在成员变量前加上my(如果是static变量,加上our)前缀,又如在参数名前加上given前缀,其它方案还有好多。最后,我们决定采用在成员变量末尾加下划线,而在参数末尾不加下划线的方案。”
我皱了皱眉:“难道这个约定就毫无争议之处吗?”
Guru斜嘴一笑:“温迪就倾向于别的方案。但没有东西是完美的,除了珍妮。温迪实在是生了个漂亮宝贝,所以任何东西都必须留点余地。”
- - - - - - - - - - - - - - - - - - - - - - - - -
“你可能应该多想一下此时此地的情况。”同桌的另一个人嘀咕道。气氛很紧张,每个人都希望珍妮的工作能很快出结果。
我并不奇怪珍妮马上跳出来为我辩护,但她回击的用词却令我吃惊。很广的词汇,运用得很熟练,较之单纯的反复咒骂有效得多,珍妮的痛斥尤其有效。嗯,我对自己说,现在有个少女知道怎样交流了……
[更深入的阅读]
Ottinger的变量命名规则:<www.objectmentor.com/publications/naming.htm>。
[参考文献]
[1]第一个正确地领会pottery school这个隐喻词意思的读者,并能说出为什么它适用于2001年11月的这个专栏,将得到一份Sutter亲笔签名的《More Exceptional C++》,该书于2001年11月发行。所有来信请发给hsutter@acm.org,主题为“I know! I know!”,并包含一个有效的电子邮件回复地址和传统邮政地址。有效期截止至2001年10月15日午夜。
[2] Charles Simonyi. "Hungarian notation(匈牙利记法)." 重印于<http://msdn.microsoft.com/library/en-us/dnvsgen/html/hunganotat.asp>。它鼓励读者研究命名规范,决定自己的方案,而不是依赖于作者的想法。
[3] 这是真实的故事,就发生在本文作者之一的身上。
[4]此珍妮并非系列故事中的珍妮。参见"Conversations: Back To Base-ics," C/C++ Users Journal C++ Experts Forum, September 2001, <www.cuj.com/experts/1909/hyslop.htm>.
[关于作者]
Herb Sutter
是个独立顾问,也是ISO/ANSI C++标准委员会的秘书。你可通过hsutter@acm.org.联系他
Jim Hyslop
Leitch Technology International Inc.资深的软件设计师,你可通过jim.hyslop@leitch.com联系他