如果读者已经加入某个软件公司开始编码,应该已经接触到贵公司的命名规范。一般规范都会解释怎么做,解释为什么的估计会比较少。在本节中笔者尽所能,去挖掘这些命名规范是什么,规范确立的缘由,并且从哪里来的。不过有的缘由已经失落难以寻找,有的可能没有多少理由。有错漏之处,请多多指点。
命名法中对Windows影响最深远的就是匈牙利命名法(Hungarian Notation,HN。参见中文维基百科的“匈牙利命名法”主题(http://zh.wikipedia.org/wiki/匈牙利命名法)或者英文主题“Hungarian Notation”(http://en.wikipedia.org/wiki/Hungarian_notation)),由Charles Simonyi(http://en.wikipedia.org/wiki/Charles_Simonyi)在微软的期间(1981~2002)开始迅速推广的命名规则。因为这套规则前缀并不都是英文,其规则是“类型+变量名”,类似于匈牙利人“姓氏+人名”(和中国人一样),与一般欧洲人习惯(“变量名+类型”)不同,Charles又是匈牙利人,所以称为匈牙利命名法。这个命名法又分为“Systems Hungarian”(系统匈牙利)和“Apps Hungarian”(应用匈牙利)。Systems Hungarian基于变量的Type(类型),而Apps Hungarian基于Purpose/intent(意图)。比如:
//Systems Hungarian
UInt32 unIndex; // or dwIndex. An index of UInt32 type
LPWSTR lpwszName; // a long distance point widechar zero-terminated String
// LPWSTR is a Windows C type, not existing in C#.
//Apps Hungarian
Int32 arrIndex; // An index of an array
string szName;
//Recommended as the alternate notation of Apps Hungarian
Int32 indexOfArray;
string nameString; // or nameStr
最后两个indexOfArray和nameString是现在不少人比较提倡的命名法,用来代替Apps Hungarian。其它比较出名的前缀,如“sz”,表示以空零结尾的字符串(“sz”实际上是匈牙利字母表中的一个合体字母,例如,szName,表示字符串Name。因为没有指明字符串是如何组成,这个是Apps Hungarian。如果写成lpcwszName,lp:长跳转指针,c:const,w:宽字符,sz:空零结尾字符串,则是Systems Hungarian),如全局变量用“g_”前缀,类成员变量用“m_”。这套命名规范已经被.Net摒弃(参见MSDN上“General Naming Conventions”主题),但在微软内外还有不少人用,而且还经常出现在C#的代码中(包括笔者)。所以,有必要拉出来讨论一下在C#的优缺点。
Hungarian Notation(HN) | C#上的代替方案 |
[优点]通过肉眼就能分辨变量类型,而无需向上翻页去找变量的声明定义。 | 把鼠标放到变量上即可看到变量类型,而且在强类型语言中在变量加类型前缀显得冗余。 |
[优点]多个语意类似的变量可以在一起使用,减少命名冲突。比如UI编程的时候,TextBox(输入框)为txtUserName,Label(提示框)为lblUserName。 | 笔者在UI编程的时候还是遵循这套规则。可以用“变量名+控件名”的方式,如userNameTextBox。 |
[优点](Apps Hungarian)当作不恰当赋值的时候,可以肉眼判断。在Joel Spolsky的文章“Making Wrong Code Look Wrong”(http://www.joelonsoftware.com/articles/Wrong.html)有精辟的阐述。简要说明一下。假设有如下代码: int hWindow = GetWindowWidth(); 其中“h”前缀表示“height”。明显用肉眼就能看到这个赋值有问题,应该是: int wWindow = GetWindowWidth(); 或者 int hWindow = GetWindowHeigth(); | 按照camelCasing(驼峰命名法),可以这样写: int WidthOfWindow = GetWindowWidth(); 或者 int windowWidth = GetWindowWidth(); |
[优点] (特指Systems Hungarian)代码量很大的情况下,(如UI编程中)可以更容易找到所要变量。例如要找的变量的类型是TextBox(输入框,缩写为txt),但记不清楚变量名后缀是UserName还是User,可以敲txt然后按“ctrl+j”在代码提示中选择。 | 这是笔者在UI编程中使用HN的另外一个原因。 |
[缺点]在泛型编程中是无法在编写代码的时候确定变量类型的。 | 问题同样存在。 |
[缺点](特指Systems Hungarian)当变量类型改变的时候需要在变量出现过的地方逐个修改,特别是一些比较常用的变量名会让查找修改变得困难。 | 问题同样存在。 |
匈牙利命名法(Hungarian Notation,HN)的历史故事 |
Charles Simonyi在微软内部推行的HN其实是Apps Hungarian,但是却被内部文档人员误传(参见Larry Osterman博客的文章“Hugarian notation - it's my turn now :)”(注:Larry的原文把Hungarian写成Hugarian),链接:http://blogs.msdn.com/larryosterman/archive/2004/06/22/162629.aspx。还有Rick Schaut的文章“Hungarian Notation”,链接:http://blogs.msdn.com/rick_schaut/archive/2004/02/14/73108.aspx),结果导致Systems Hungarian在内部泛滥。加上Charles Petzold曾经“大力”传播Systems Hugarian(参见Charles Petzold的文章“The Demise of Hungarian Notation”,链接:http://www.charlespetzold.com/blog/2006/05/040939.html),导致很多程序员错误的使用了很长一段时间。笔者倒是不反对Apps Hungarian,只要约定明晰,还是很好用。特别是在一个函数内出现多个语意相同的变量会显得更加简洁明了。比如在获取Name字符串变量的操作时可能会需要如下变量:szName(字符串Name),iName(Name字符串的index)。代码如下: string szName = "Charles Simonyi"; int iName = szName.IndexOf(' '); string firstName = szName.Substring(0, iName); |
微软推荐的编程风格请参阅MSDN中的“Guidelines for Names”主题。简单概括如下。
类型 | 规则 |
namespace(命名空间),interface(接口),class(类),property(属性),method(方法) | 遵循PascalCasing,即首字母大写,然后每个单词的首字母都是大写 |
namespace(命名空间),interface(接口),class(类),property(属性),parameter(参数)、local variable(局部变量) | 都由名词或者动名词组成 |
interface(接口) | 由“I+名词”组成 |
method(方法) | 由“动词+名词”组成 |
parameter(参数)、local variable(局部变量) | 遵循camelCasing,即首字母小写,从第二个单词开始首字母大写 |
但是MSDN上并没有内部成员的命名规范。大概是觉得内部成员,编程人员可以自行规定。笔者曾经工作过的一个部门有一位高级开发人员,他当时在写一个相当复杂的功能。其伪代码片段如下:
class RealCaseStudy
{
// member variable
private string usrId;
//...other code
private void DoAComplexCalculation()
{
//local variable usrId. It is a typo.
string userId = String.Empty;
//some complex code
//...
// Actually should use the member variable
// 'usrId' here.
userId = CallAnotherMethod();
// other complex code
}
}
因为这个DoAComplexCalculation方法本身很复杂、很长,谁没有留意有一个局部变量“userId”和成员变量“usrId”只差一个字符。结果在应该给成员变量赋值的地方,错赋给局部变量。这个小小的typo(打字错误)让他的调试工作毫无进展并牵动了很多资源,结果还让整个进度完了一周,最后才由另外一位程序员发现解决。他当时很可能是把局部变量移到到成员变量的时候少敲一个字符。在赋值的时候,敲“us”出现的代码提示中局部变量先于成员变量。因为两个变量本来就是代表同一个意思,开发者并没有仔细查看选中变量,从而导致天大的失误。这正是所谓的“细节决定成败”。如果他遵循良好的命名规范,这个问题自然就会被避开。解决的方法有两种:
1.“this.[memberVariable]”
2.“_[memberVariable]”
两者没有本质的区别,只是习惯体验不同。笔者采用的是方法二。好处就是简单,只需要敲一个字符“_”就可以按照代码提示选择正确的成员变量,而方法一需要多敲几个字符才能获得代码提示。要是算上删除这5个字符,如果还要修改,工作量起码翻倍。下划线不好的地方是看起来奇怪。读者可以自行选择自己喜欢的方式。笔者也在网上见过的一些大侠用“this.m_[xxXx]”的,这个双保险有点画蛇添足(或者说Over-Kill)。“m_”在MFC中非常常见,但对于C#这个没有全局变量,即,不会用到“g_”前缀(global);没有局部静态变量,即,不会用到“s_”前缀(static)的语言来说,用“m_”前缀明显没有意义。因为没有其它语意干扰单个“_”前缀代表成员变量的语意。下面简要总结笔者在本书中采用的命名规范:
1. 首选MSDN上的规则(即Pascal/Camel规则);
2. 在UI编程时,控件对应的变量依然用Systems Hungarian;
3. 成员变量遵循“_[memberVariable]”;
4. 只读(static readonly)的简单类型用“全大写名词+[_全大写名词]”
如果读者不喜欢条款2,网上提倡的代替方式是用“变量名+控件名”,如“nameTextBox”(变量名:name+控件名:TextBox)。一般来说,大家都按照一个规范来做,持之以恒,也没什么大碍。只是写代码是天天都要做的事情,也应该知道这些规矩的来龙去脉。这些细节积小成多,可能会导致很不一样的结果。另外,让笔者小惊讶的是搜中文资料(Google或者Baidu)时发现除了中文版的维基百科,几乎都只是提到Systems Hungarian,而且似乎流传多年的版本都是出自同一个人之手。也许是看英文的懒得转载,看中文的从来不会想到去看个究竟。
--摘自我新出书的一节