忍者程式碼(Ninja Code)
過去的程式忍者使用這些技巧來讓程式碼維護者的心思更加敏銳。
程式碼審查大師得在測試任務中尋找它們。
初學的開發者有時候將它們用的比程式忍者更好。
細心閱讀然後找出你的角色是 — 程式忍者、初學者、或者程式碼審查者?
很多人試著跟隨忍者的腳步,但很少有人成功。
要言不煩(Brevity is the soul of wit)
讓程式碼盡可能簡短,以顯示出你多麼聰明。
讓微妙的語言特性指引你。
舉例,看一下這個三元運算子 ?
:
// 從知名 javascript 函式庫取得的程式碼
i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
酷,對吧?若你也像這樣寫,那些看到這一行程式碼並試圖理解 i
值的開發者們,將會有個美好的時光,接著就會來找你尋求答案。
告訴他們更短總是更好,引領他們進入忍者之路。
單一字母變數
道隱無名。夫唯道,善貸且成。
— 老子(道德經)
另一個寫程式更快的方式是到處使用單一字母的變數名稱,像是 a
、b
或 c
。
短變數會像個真正的忍者處於森林中一樣,消失於程式碼之中,沒有人能夠使用編輯器的 “搜尋” 找到它們。即使有人辦得到,他們也無法 “破譯” a
或 b
名稱的意義。
…但有個例外,一個真正的忍者永遠不會在 for
迴圈內使用 i
作為計數器。任何地方都行,就是這裡不可以。觀察四周看看,還有其他像是 x
或 y
這種異樣的字母呢。
若迴圈本體有 1–2 頁這麼長(若可以就讓它盡量長),那麼以異樣的變數作為迴圈計數器會是特別的酷。然後若有人深入迴圈內部,他們將無法快速知道以 x
為名的變數就是迴圈計數器。
使用縮寫
若團隊規則禁止使用單一字母或模糊的名稱 — 盡量縮短它們,使用縮寫吧。
像這樣:
list
->lst
。userAgent
->ua
。browser
->brsr
。- …等等
只有真正擁有良好直覺的人才有辦法理解這些名稱。盡量縮短一切事物,只有天選之人才夠格接手你的程式開發。
突破天際的抽象化
大方無隅,
大器晚成,
大音希聲,
大象無形。
— 老子(道德經)
在選擇一個名稱時,試著使用最為抽象的詞,像是 obj
、data
、value
、item
和 elem
等等。
- 變數的理想名稱是
data
。在任何能用地方都用,每個變數都確實都有 data 不是嗎?
…但如果 `data` 已經被用過了怎麼辦?試著使用 value
,它也很普遍,畢竟一個變數最終總會得到一個 value。
- 使用變數類型命名:
str
、num
…
嘗試看看,新手可能會懷疑 — 這種名稱真的對成為忍者有用嗎?是的,就是會!
確實,該變數名稱依然含有意義。它說明了變數內部有些什麼:一串字串、一個數值或其它東西。但當外部使用者試著理解程式碼時,他們會驚訝地發現事實上根本不具有任何資訊!且最終將無法改變你精思熟慮過的程式碼。
值的類型很容易就可以經由除錯得知,但此變數的意義呢?它儲存著哪種 字串/數值?
不經過良好的冥想是無法理解的!
…但如果這種名稱不夠用怎麼辦?加個數字就好了:data1, item2, elem5
…
注意力測試
只有真正細心的程式設計師才夠格理解你的程式碼,要如何確認?
其中一個方法 — 使用相似的名稱,像是:date
和 data
。
盡可能的混合在一起。
快速閱讀這種程式碼是不可能的,而且當還有錯字時… 嗯… 我們卡在這很久了,來喝個茶吧。
聰明的同義詞
最困難的事是在黑暗的房間內尋找一隻黑貓,尤其當那裡根本沒有貓時。
— 孔子(譯者註:這則引用應是個烏龍,孔子沒有說過這句話,可上網查詢相關來源)
對 同件 事情使用 相似 的名稱使得生活更為有趣,並向大眾顯示出你的創意。
例如,函式前置。若某個函式在螢幕上顯示一段訊息 — 使用 display...
開頭,像是 displayMessage
。然後若另一個函式在螢幕上顯示別的東西,像是使用者名稱,就用 show...
開頭(像是 showName
)。
暗示這些函式之間有些微妙的不同,而其實並沒有。
與團隊中的忍者夥伴達成一個協議:若 John 在程式碼中用 display...
作為 “顯示” 函式的起始,那 Peter 可以用 render..
,而 Ann 就用 paint...
,注意看看程式碼會變得多麼有趣且多樣化啊。
…接著是帽子戲法!
對於兩個有著重要差異的函式 — 使用同樣的前置!
舉個例,函式 printPage(page)
將會用到印表機,而函式 printText(text)
將會把文字放到螢幕上。讓某個不熟悉的讀者思考一下這個相似的函式名稱 printMessage
:”它會把訊息丟到哪去?印表機還是螢幕上?” 為了讓它更為耀眼,printMessage(message)
應該要輸出訊息到新的視窗中!
重複使用名稱
始制有名,
名亦既有,
夫亦將知止。
知止所以不殆。
— 老子(道德經)
只在絕對需要時才加入新的變數。
否則,重複使用已存在的名稱。就只要對它們寫入新的值。
在函式中試著只使用作為參數傳遞的變數。
這樣做變數 現在 的值到底是什麼會變得很難確定,也會不知道它從哪來的。這麼做的目的是為了開發閱讀程式碼的人的直覺和記性。直覺不佳的人必須一行一行分析程式碼,並追蹤每段程式碼分枝的變化。
這種方法的進階變化是偷偷地(!)在像是迴圈或函式之中換掉它的值。
舉個例:
function ninjaFunction(elem) {
// 20 行程式碼用來處理 elem elem = clone(elem); // 另外的 20 行,用來處理複製後的 elem!
}
想要在函式第二部分使用 elem
的程式夥伴會非常驚訝… 只有在除錯檢查完程式碼後,他們才會發現原來他們是在使用複製體!
這經常在程式碼中看到,即使是對於經驗豐富的忍者來說也是非常致命。
底線的樂趣
在變數名稱前使用底線 _
和 __
,像是 _name
或 __value
。若只有你知道它們的意義就太讚了,或者更棒的是,加上去只是為了樂趣,根本沒有特別的意義存在,或是在不同的地方就有不一樣的意義。
你一槍殺死了兩隻兔子耶。首先,程式碼變得更長且更不易讀了,再來,開發夥伴會花很長的時間試圖理解底線的意義。
一個聰明的忍者會把底線放在某處,然後刻意避免在其他地方使用。這會讓程式碼更為脆弱且增加未來出錯的機會。
展現你的熱情
讓大家看看你是多麼的氣壯山河!像是 superElement
、megaFrame
和 niceItem
這種名稱,一定可以達到啟發讀者的功效。
從某方面來看,這樣確實有寫下些什麼:super..
、mega..
、nice..
,但從另一方面來看 — 毫無細節可言。讀者也許得在上班時間花一兩個小時冥想,來尋找背後隱藏的意義。
重疊外部變數
夫處明者,不見暗中一物,
而處暗者,能見明中區事。
— 關尹子
將函式的內部與外部變數都使用一樣的名稱。很簡單,也不用花時間創造新名稱。
let user = authenticateUser();function render() {
let user = anotherValue();
...
...many lines...
...many lines...
...
...
... // <-- 某個程式設計師想在這裡使用 user,然後...
...
}
一個跳進 render
內的程式設計師,可能會沒注意到有個區域的 user
隱蔽了外部的變數。
然後他們會試圖將 user
視為外部變數 authenticateUser()
的結果來使用… 翻開覆蓋的陷阱卡!哈囉,除錯器…
副作用(Side-effects)無所不在!
有些函式看起來不改變任何東西,像是 isReady()
、checkPermission()
、findTags()
… 他們被設想為執行計算、找出並回傳資料,而不改變內部的任何東西,換句話說就是沒有 “副作用(side-effects)”。
有個真正漂亮的技巧,就是在他們的主要任務之外,再加個 “有用的” 動作。
當你的同事看到一個名為 is..
、check..
或 find..
的函式改變了某些東西時,他的臉上一定會充滿迷惑 — 絕對能拓展你理性的界線。
另一個給人驚喜的方式是回傳非標準的結果。
展現你原始的想法!讓 checkPermission
呼叫不回傳 true/false
,而是回傳某個包含檢查結果的複雜物件。
那些試圖寫下 if(checkPermission(..))
的開發者,會懷疑為什麼這麼寫不起作用。告訴他們:”看文件!”,然後把這篇文章丟給他們。
強大的函式!
大道泛兮,
其可左右。
— 老子(道德經)
別讓名稱限制了函式,變得更廣泛吧。
舉個例,函式 validateEmail(email)
可以(除了檢查 email 是否正確之外)顯示錯誤訊息並要求重新輸入 email。
額外的動作不該明顯出現在函式名稱中,真正的忍者程式人員會使它們在程式碼中也不這麼顯眼。
把多個動作合併成一個以避免你的程式碼被重複使用。
想像一下,別的開發者只想要檢查 email 而不要輸出任何訊息時,你的函式 validateEmail(email)
做這麼多事就不適合他們啦。所以他們才不會在你冥想的時候來問你問題。
總結
上述的 “這些建議” 都由實際的程式碼得來… 有時甚至是由有經驗的開發者寫下的,甚至是比你更有經驗的人 ;)
- 遵循部分,你的程式碼會變得充滿驚奇。
- 遵循多數,你的程式碼會真正成為你的程式碼,沒有人會想改變它。
- 遵循全部,你的程式碼將成為年輕開發者尋求啟發的寶貴案例。