匈牙利命名法是過去的一種變數命名方式。簡單的說,藉由在變數的開頭加上一些字母的前綴,比方說是字串的 name
前面加上 str
,變成 strName
,來標註變數的形態(比方是字串),或者該特殊注意的地方(比方說,用 us
代表不安全字串)。
在過去,這可以讓維護的工程一看就知道這個變數的形態,或有哪些該特別注意的地方,是過去解決這些問題的一種設計方式。
到了現在,這個命名方式不僅用途不大,而且還可能造成後續維護的困擾。換句話說,非常符合我們建立難以維護專案的目的。
沒有什麼比起良好設計的匈牙利命名法,能更有效地殘害之後維護的工程師。如果我們在裡面,又時不時脫離原本匈牙利命名法設計的初衷,那效果就更好了。
以下技巧,可以保證你毀掉原本匈牙利命名法的初衷,為之後的工程師帶來無盡的痛苦:
雖然 PHP 已經以大寫作常數的慣例,堅持使用 c 前綴代表常數。
例如,將
public const DUE_DATE = "02-28";
改成
public const c_duedate = "02-28";
違背匈牙利命名法內,常用的變數應該要攜帶較少其他資訊的原則。
要達到這點,可以堅持每個類別都應該有自己的前綴。不讓別人提醒你針對什麼是類別這件事情,根本不存在匈牙利命名法前綴。
這個原則非常重要,如果沒有遵守,那程式碼可能會充斥許多簡短的,非常好唸的變數名稱。最差的狀況甚至可能導致整個混淆的努力失效,程式碼裡面出現用英文就可以看懂的段落!
比方,將
$user = User::find(1);
$post = Post::find(1);
public const UPDATE = 'update';
if ($user->can(UPDATE, $post)) {
//
}
改成
$suuser = User::find(1); // 第一個 s 代表 safe,u 代表這是一個 User 物件
$sppost = Post::find(1); // 第一個 s 代表 safe,p 代表這是一個 Post 物件
public const str_update = 'update'; // str 代表 字串
if ($suuser->can(str_update, $sppost)) {
//
}
C++ 曾經有個命名慣例是使用 m_
開頭的字代表成員(member)變數。
這個前綴可以幫助你區分儲存方法的變數,直到你想起方法(method)這個字也是 m 開頭。
使用 o
或 obj
前綴代表所有物件(object),代表你有看到多型結構的大局。
堅持在匈牙利命名法綴詞裡面,包含所有獨立的資訊。
參考這個曾經真實出現過的變數名稱 a_crszkvc30LastNameCol
。
花費一整個團隊的維護工程師大概三天的時間,才搞清楚這個變數是一個常數(c),參考(reference),函式的引數(a_
),null 結尾字串(sz),某個資料庫欄位(Col)裡面名為 LastName 的資料,資料長度是 Varchar[30]
(vc30),而且是某個表的 primary key 裡面的一部分(k)。
搭配「所有變數都是公開的」原則,這個技巧可以讓數千行程式碼直接廢掉!
文章很有趣~ 但匈牙利命名法本身是很好的。只是在誤用跟縮寫的情況下被污名化了。
可以看一下 Joel Spolsky 十五年前的這篇文章。如果你沒聽過這個人的話,提示一下他是 Excel VBA 的規格制訂者,跟 Stackoverflow 的創辦人(之一)。
感謝回應,我知道這個人,也知道這篇文章。
這篇文章我自己看到的重點是
應用匈牙利命名法非常有用,特別是當初C語言盛行,而編譯器尚未提供很有用的型別系統時。
現在的 IDE 已經可以很好的提供有用的型別系統。隨著語言的進化,更長的變數不再是問題,Joel 文章裡面的
利用這套編碼規範,你的眼睛學著看到Write usXXX就知道是錯的,而且你也立即知道要如何修正。
也已經可以變成
「眼睛學著看到 Write unsafeXXX 就知道是錯的」,不需要特別的編碼規範。
感謝回應。同意你所說的,但是「用 unsafe 來 prefix 變數」這件事就是一種編碼規範啊 XD
用 unsafe
來 prefix 變數,是以命名的方式,來標記這個變數該特殊注意的地方。
對一般的工程來說,他只要看到 unsafeXXXX
就應該要知道這個變數是「不安全」的(除非他看不懂「unsafe」的意思,這是其他問題,這裡先不討論)。
但是一個工程看到 usXXX
,他並不會知道這裡 us
的前綴是什麼意思。必須要透過其他人的告知,或者文件的資訊,他才會知道 us
代表「不安全」。
借用 Joel 文章裡面舉例
我花了好幾個月每天早上打掃才真正瞭解他們的意思。對麵包工廠來說,乾淨是指機器裡沒有生麵糰在烤,垃圾堆裡沒有發酵的麵糰,而且地板上也沒有堆生麵糰。
麵包工廠的乾淨,不是一般人所認知的乾淨,這是不可避免的,所以你需要訓練才會知道。
但是,現在要知道一個變數該特殊注意的地方,你可以從變數的命名直接得到資訊,這是工程通用的做法,而不再是單一專案或團體的做法。
這也是我上面提到「『眼睛學著看到 Write unsafeXXX 就知道是錯的』,不需要特別的編碼規範。」的意思:你不需要特別去記 us 代表 unsafe,你直接寫 unsafe 就好了。
啊。我覺得是我沒有講清楚。
如果用文章裡的範例,大家說好要用某些字要讓系統內的程式碼看得出錯,並讓名稱配合變數名稱,那些說好變數跟函數要配合,以及選用哪些字就是個這個應用程式裡的編碼規範。因為如果不說好,大家就會各自用各自的字了,甚或根本不 prefix 了。
例如說大家講好用 safe 來 prefix 好了,那麼這個規範就是要改成:
unsafe = UnsafeRequest("name")
safeName = SafeFromUnsafe(recordset("unsafeName"))
那麼在不縮寫的情況下,文中另一個視窗座標例子就會變成:
YpointLayout = YpointLayoutFromYpointWindow(XPointWindow) #錯的
除了很長更難讀懂之外,真正的造成麻煩是變數的意義跟防錯標識會混在一起,反而更不容易看。
其實我想表達的立場可以簡單這樣講:「有些團隊了解匈牙利命名法的正確用法及好處,不需要污名化它。而應用程式內的 meta knowledge 是必然存在的。一部份可以靠編碼規範緩解,但有一部份非得用溝通解決。」
大概理解你的意思,我也覺得確實如此:應用程式內的 meta knowledge 是必然存在的。一部份可以靠編碼規範緩解,但有一部份非得用溝通解決。
YpointLayout = YpointLayoutFromYpointWindow(XPointWindow)
這種寫法也確實不好讀懂。
文中的視窗座標例子的話,我想引用一下原文:
So, to tell you the truth, I’ve never seen the Word source code, but I’ll bet you dollars to donuts there’s a function called YlFromYw which converts from vertical window coordinates to vertical layout coordinates.
以這段話為出發點,我們可以比較看看:
VerticalLayoutFromVerticalWindow()
是否比 YlFromYw()
需要的溝通更少?
VerticalLayout = VerticalLayoutFromVerticalWindow(HorizontalWindow)
是否比起
Yl = YlFromYw(Xw)
更易讀?
一旦 VerticalLayout 的轉換出錯要維護,是要找到所有的VerticalLayout
容易,還是找到所有的 Yl
容易?
這幾點或許是可以想想的。
我覺得 Yl = YlFromYw(Xw)
比較容易「一眼看出錯誤」,而且 考慮到整個程式的屬性, 一定有很多跟方向及視窗有關的變數。YL 跟 YW 重複率不會比 Vertical、Layout 這種字高太多。(笑)
當然換字是種選擇,不過立場還是一樣的,特別是前半段:「不需要污名化縮寫 prefix 這件事。」
大概可以理解你的立場,不過這裡並沒有要污名化匈牙利命名法或縮寫 prefix 這幾件事情。
匈牙利命名法是針對過去難以馬上知道變數的形態,或難以馬上知道該特別注意的地方,所設計的一套方式。他的創造者跟內容,在當時都是有意義的。
可是在現代,IDE 可以協助你知道變數的形態,較長的命名和註解可以協助你知道該特別注意的地方,所以我的文章說「到了現在,這個命名方式不僅用途不大,而且還可能造成後續維護的困擾。」。
YL、YW、Vertical、Layout 重複率的部分,我搜尋了一下 Laravel 8 全新專案個別出現的地方,或許可以參考看看。