為什麼不要使用匈牙利命名法?
這篇文章並不完全是我的原創,文章的原始主體來自於 PTT 上相關主題討論的對話節錄 , 重新編排修飾後又加入了一些我自己的觀點整理而成。
什麼是匈牙利命名法?
匈牙利命名法是一種在程式設計中使用在各種變數的命名上的規則,
因這種命名的名稱形似匈牙利人名而得其名(雖然我怎麼看都看不出來到底哪裡像了)。
匈牙利命名要求所有的變數名稱需要在前面加上幾個簡短的小寫字母,用以標示該變數的型別。
列舉幾個範例如:
iIndex
表示它是一個整數、
fSpeed
表示它是一個浮點數、
cSex
表示它是一個字元、
sName
表示它是一個字串、
szName
表示它是一個以零結尾的字串、
pszName
表示它是一個指標,指向一個字串、
lpszName
表示它是一個遠指標,指向一個字串、
g_lpszName
表示它是一個遠指標,指向一個字串,而且這是個全域變數、
a_crszkvc30LastNameCol
啊!我也不知道它的型態是什麼……
為什麼匈牙利命名法不受喜愛?
匈牙利命名法多很多綴字,又不好看又不好記打起來又麻煩,在現代 IDE 輔助下,實用性並不高。 舉例,有個變數 Var 型態是 Long Pointer to a Zero-terminated String, 那你就必須在變數名稱前面加上 lpsz,變成 lpszVar, 凡是遇到 long pointer 變數,命名一律必須前綴 LP, 於是傳統採用匈牙利命名的 Win32 SDK 程式中到處都充滿 LP。 Handle of WiNDow 這個型別,就必須命名為 HWND,這類變數有時得前綴 hwnd, 這樣很難記又沒意義,又降低可讀性,在現代的程式已經沒有實用價值了。
匈牙利命名法最大的缺點是,現今它沒有任何基於必要性的優點, 以至於它所有缺點的負分加起來,就差不多是它的總得分。
匈牙利命名法的優點
變數的型別是什麼,從變數的定義就看得出來了,何必要把型別縮寫加在變數上呢? 可能需要它的理由是:
找不到變數定義
如全域變數,不知道它被定義在哪一個檔案,又沒有 IDE 的時候。
懶得找變數定義
對於一個千百行的函式,你不會想中途看到一半, 時常要 scroll 到前面去看變數定義,尤其是 C 程式。
猜不到變數型別是什麼
以前的變數命名比較短,如
kn
,猜都猜不出來可能的型別是什麼。
匈牙利命名法的優點為什麼不復存在?
然而,環境不一樣了。 現在的程式寫作習慣改變了,尤其是 C++ 或說物件導向的程式寫作。
容易知道變數被定義在哪裏
物件導向程式寫作有一個重要的認知是:物必有主。一個變數一定會有個主。 global 變數不被鼓勵使用,而以 class static member 取代之, 你很容易知道他被定義在哪裏。
變數定義就在旁邊而已,不必你找
小函式的使用被鼓勵,對於一個 20 行內的函式,你絕對看得到變數定義。 對於大一點的函式, C++ (以及 C99 以後的 C)可以讓你在變數使用的前一刻宣告即可, 這種寫作方式也被鼓勵。對於一個不認識的變數,它的定義就在前幾行而已。
猜得到變數型別是什麼
完整的變數命名被鼓勵,加上物件導向的特性,很容易猜得出變數型別。 如視窗
window.height
會是一個unsigned int
。man.height
大概就是一個float
了。
再加上近代軟體開發工具的進步,補足了匈牙利命名法被需要的條件, 所以基本上,匈牙利命名法在現在已經沒有任何基於必要性的優點了。
匈牙利命名法的缺點
好煩!
我已經知道這個變數是
int
了,為什麼程式碼到處都在說它是int
。不清爽!
vector.x
不是很好嗎,為什麼要寫vVector.fx
。變數改型別的時候怎麼辦?
變數改變型別是有可能的,如果一個變數分佈在 20 個檔案,最起碼要改 20 次。 當然,有好的開發環境可能會方便一點; 但如果是發佈給人家用的 API 呢,改變型別縮寫,鐵定會被客戶抱怨一下。
五技而窮!
基於物件導向,以及程式規模愈來愈大,大量的自定型別被定義, 如何去為這些型別找個容易識別的縮寫呢?
未能適用於函式名稱!
以理類推,函式前面也要加回傳型別的縮寫呀,這樣才統一呀! 如果變數前面要加型別縮寫才行,那為什麼有回傳值的函式不用,總是要平等對待呀。 如
fsqrt()
、man.fGetHeight()
、vBeep()
。天阿!應該沒有人會這樣子用。 一個無法貫徹於各種情況的風格,可能不是一個好的風格。難以為多型物件命名!
如下,應該是這樣好?
Man* mnRenderer = new Programmer;
還是這樣好?
Man* pgmRenderer = new Programmer;
在物件導向程式寫作裏,一個變數的角色與行為,會比它的型別來得重要。
無法用於泛型程式寫作!
如:
template<class TYPE> class Vector { public: TYPE x; TYPE y; TYPE z; };
這裏的 x,y,z 前面要加什麼縮寫好?
另外,像是 g_XXX、m_XXX 等也是沒有必要的
g_XXX
:程式裏不太應該要有 global 變數, 放到 class static member,
g_
就用不上了。 如Man::Count
,你一定知道它是一個唯一的變數。m_XXX
:m_XXX
也是沒有必要的,如:vector.m_x;
m_
是個贅字,”.
” (或者 “->
”) 本身就說出 x 是它的 member 了。 另外:Vector& Vector::operator = (const Vector& b) { this->x = b.x; this->y = b.y; this->z = b.z; return *this; }
是比下面的寫法來得簡明的:
Vector& Vector::operator = (const Vector& b) { m_x = b.m_x; m_y = b.m_y; m_z = b.m_z; return *this; }
m_
唯一有用的地方是在 member function 裡,然而加this->
就好了。this->
是語言提供的本尊原生寫法, 另立主張,有人喜歡,有人不喜歡,這樣就不好了。另外,應該讓 class 的使用者使用乾淨的介面,
vector.m_x
,其實是多給使用者麻煩。 考慮使用者的方便,會比讓自己方便來得重要。
匈牙利命名法並不能幫助理解
「如果不知道變數的意義,就算知道它的型別也沒用的!」
嚴格來說,這並不能算上是匈牙利命名法的缺點, 只能視為一個對使用匈牙利命名的人可能走入歧途的提醒。
匈牙利命名並不是提升閱讀性的解決方案,對於理解程式碼也沒有幫助。
他雖然讓你知道一個變數的型別,但若主名稱本身意義不明,比如說 fAaa
,
你一樣無法知道這是一個什麼東西?
因此有另一些人認為,程式人員應該良好的命名變數,而不是妄想用匈牙利命名修飾來解決一切。
妥善的變數名稱(主名)可以讓人一看就明白它的意義與用途,而這通常會比變數是什麼型態還要重要!
有一些時候,良好命名的變數甚至可以直覺的表達它的型別,
比方說看到 WindSpeed
應就知道它會是一個浮點數、
TryCount
就是一個整數、而 IsOpened
應就是布林值。
匈牙利命名法的時代背景
既然匈牙利命名法看來只有一卡車的缺點而沒有任何在現代必要的優點, 那麼為什麼這種命名法可以流行在程式設計領域呢? 這就需要了解一下匈牙利命名法誕生那個年代的一些背景。
在古時候的程式設計領域,還沒有像現代 IDE 等的方便工具, 也缺少現代已經稀鬆平常的一些如語法變色、自動完成、語法檢查、 宣告與定義的分析搜尋、和編輯器內的查找與跳轉等等的功能。 再加上當時硬體和軟體上的技術限制,變數等符號能使用的名稱長度極為有限; 大家也不喜歡做太多層的函式呼叫,這使得內容很長的函式相當常見(這是手工最佳化的一部份)。 所以在那個時代流行一些試圖增加程式閱讀性和減少人為出錯的小技巧, 其中就包括了著名的匈牙利命名法。
另一方面,模版、泛型編程還沒被發明,物件導向與多型等程式設計概念還未出現, 使得匈牙利命名的許多缺點並不明顯。 這全部的條件造就了讓匈牙利命名被需要的環境,使得其曾經大為流行。
匈牙利命名法的現況
雖然軟硬體環境的推進使得需要匈牙利命名的情景不再,但是工程人員的習慣已經養成, 對於那些已經用了半輩子匈牙利命名的人,他們總還是習慣繼續使用匈牙利命名。
在某些地方,匈牙利命名法可能被推崇為「嚴謹的命名方案」,並繼續這樣的教育新的程設人員, 使得很多不明所以的新進人員以為使用這種命名法看起來比較專業, 而不明所以的公司研發單位會將其寫入設計風格規範中。 這些導致了雖然匈牙利命名法已無必要且缺點不少,但仍大量出現於產業界的程式碼中的狀況。
另外,雖然匈牙利命名法常見於 C/C++ 程式, 然而匈牙利命名法並不是只能用在 C/C++ 上, C/C++ 的各種規範中也未曾要求或推薦使用匈牙利命名法。 只是因為匈牙利命名法被發明並流行的時代剛好就是 C/C++ 流行的時代, 而在更新的程式語言出現的時候,已經不再推崇匈牙利命名了,所以不常在 C/C++ 以外的地方被看見。 這可能導致了某些人會把 C/C++ 和匈牙利命名法連結在一起,然而實際上並不是這樣。