匈牙利命名法
引用一片博文:
匈牙利命名法的衰落和建议
首先要说明的是,此文只是一篇关于个人看法的随笔,不是paper。我也无意再次挑起这场旷日持久却已结束的争论。
1.不太久远的历史
匈牙利命名法(Hungarian Notation)最初雏形来自Charles Simonyi的一篇论文,后来Charles Simonyi进入Microsoft并担任Microsoft Office开发的要职后得到完善,并在M$内部得到推广,最终演变为当时Windows开发的一种既定编码风格。
Hungarian Notation得到普及另一个人也功不可没(难辞其咎?),Charles Petzold在编写他那本极具影响力的Programming Windows(Windows程序设计)时,将Hungarian Notation作为标准编写了示例代码。而Hungarian Notation也随着这些示例代码影响了好几代Windows上的C程序员。
2.应用型和系统性匈牙利
据传Simonyi最初在M$内部推荐使用的命名法称为“应用型匈牙利”。最显著的特点是,它并不表示数据实际类型,而是给出变量所代表的意义。
例如:
rw/col 表示行和列
us 表示非安全型字符串,且在使用前需要处理
s 表示安全型字符串
大多数的前缀都来自于自然语言的单词缩写。
但是不巧的是,Simonyi在编写文档时,用了“type”这个单词。其他的工程师在翻阅文档时,误以为type表示的是数据类型。纵使Simonyi在文档中非常详细且准确地揭示了type所代表的意义,一切都为时已晚。
经过错误理解的系统型匈牙利在M$内部飞速流传开来,Simonyi也意识到自己对此已经无能为力,最终也只能将错就错。
ps:以上故事来自Joel编写的More Joel On Software(软件随想录)
于是,Hungarian Notation就变成了现在这个样子,每个名字前都用前缀来表示数据类型。
例如:
int -> n
long -> l
char -> ch
unsigned * -> u
pointer -> p
char*/wchar_t* -> psz
char[]/wchar_t[] -> sz
HANDLE -> h
DWORD -> dw
….
系统型匈牙利的好处是,你可以一眼从变量名中获取变量的类型,这在IDE尚不是很强大的时代具有一定的作用。
于是,慢慢地,Hungarian Notation开始占据Windows开发的主流风格,影响了一代又一代的C/Windows程序员
3.(系统型)匈牙利命名的缺点
Hungarian Notation最大的缺点是:(现在而言)几乎没有优点
虽然可以一眼从变量名中获取类型,但是在实际开发中,强大的IDE可以马上显示变量的具体定义信息,这使得命名法的作用大大降低。
更恐怖的是,进入C++之后,随着数据类型的增加和template的出现,使得为变量加入类型前缀变得不靠谱。
例如:
如果有一个变量的类型是 std::map<std::vector<std::wstring>, std::vector<std::wstring> >,那么确定它的类型前缀是一件很痛苦的事情,其程度不亚于你要陪你的GF/WIFE去逛街挑选衣服。
在猛烈的抨击和强烈的抗议下,M$终于在进入.NET大潮时发表宣言,表示不赞成继续使用Hungarian Notation
除了Word和Excel开发团队外(当年Simonyi直接负责的团队,也只有他们才知道事情的个中缘由),几乎所有人都感到如释重负,因为他们终于可以不用再理会这种操蛋的命名了。
4.个人观点
作为一个在Windows上用C/C++写程序,且受到Charles Petzold熏陶的程序员来说,使用Hungarian Notation为变量命名几乎成为了一种本能。
第一次反思是否应该摒弃这种命名法是在高三阅读Steve McConnell那本Code Complete之时,有意思的是,McConnell也曾经在M$工作过一段时间。
第二次反思是在不久前阅读Joel的More Joel On Software之时,并且这次反思最终促使我写下这篇文章,表达一些极具个人角度的观点。
我的观点很简单(我把它称作最小化原则的匈牙利命名法),分为两部分:
(1)控件的命名依旧采用Hungarian Notation,例如:
Button -> btn
Label -> lbl
TextBox -> txt
…
控件上的Hungarian Notation要靠谱的多
(2)变量命名上,仅保留以下前缀
global -> g_
member -> m_
static -> s_
pointer -> p
char*/wchar_t* -> psz
char[]/wchar_t[] ->sz
m_能够与非成员变量区分,而且基本可以避免变量重名而需要使用this指针的情况
g_和s_的意图无需多说,臭名昭著的global和static从来都需要特别对待
有人可能会对剩下的三个前缀颇有微词,我的理由是,这三个类型(其实只有两个)实在太特殊而且需要引起足够的注意,加前缀的意图则是告诉你:be careful! be careful!be careful!
其他的类型都尽量不加前缀。
5.两种风格的代码
为了对比效果,我特意临时写了一段新版本的代码,将两个版本进行对比。
代码则是MergeSort排序算法
不过要说一点,新版本不是在原版本基础上改写的,而是重新写的,所以某些代码会稍稍不同
老版本,采用近乎标准的匈牙利命名法
void MergeSort(int a[], int nLeft, int nRight) { if (nLeft < nRight) { int nMid = (nLeft + nRight) >> 1; // take apart MergeSort(a, nLeft, nMid); MergeSort(a, nMid + 1, nRight); Merge(a, nLeft, nMid + 1, nRight); } } /* nLfet - the first index of the elements in left part nMid - the first index of the elements in right part nRight - the last index of the elements in the right part */ void Merge(int a[], int nLeft, int nMid, int nRight) { int nLVer = nLeft; int nMVer = nMid; int nLen = nRight - nLeft + 1; int* pArr = new int [nLen]; int* pVernier = pArr; while (nLVer < nMid && nMVer <= nRight) { if (a[nLVer] <= a[nMVer]) { *pVernier++ = a[nLVer++]; } else { *pVernier++ = a[nMVer++]; } } while (nLVer < nMid) { *pVernier++ = a[nLVer++]; } while (nMVer <= nRight) { *pVernier++ = a[nMVer++]; } for (int i = nLeft, k = 0; i <= nRight;) { a[i++] = pArr[k++]; } delete [] pArr; }
新版本,最小化匈牙利命名法
void MergeSort(int a[], int low, int high) { if (low < high) { int midIndex = (low + high) >> 1; // take apart MergeSort(a, low, midIndex); MergeSort(a, midIndex + 1, high); Merge(a, low, midIndex + 1, high); } } void Merge(int a[], int lowFirst, int highFirst, int highLast) { int l = lowFirst; int r = highFirst; int len = highLast - lowFirst + 1; int* pBuffer = new int[len]; _ASSERT(pBuffer != NULL); int* p = pBuffer; while (l < highFirst && r <= highLast) { *p++ = (a[l] <= a[r]) ? a[l++] : a[r++]; } while (l < highFirst) { *p++ = a[l++]; } while (r <= highLast) { *p++ = a[r++]; } for (int i = lowFirst, p = pBuffer; i <= highLast;) { a[i++] = *p++; } delete [] pBuffer; }