如何編寫更加自解釋的代碼
當你發現代碼中的某些注釋完全無用時你會怎么辦?
我們經常會犯一個錯誤:當我們更新代碼時,卻忘記更新相應的注釋。不友好的注釋并不會影響代碼的執行,但使我們的調試和閱讀帶來極大困擾,注釋描述的是一種邏輯,而代碼確是另外一種,結果會浪費我們大量時間來搞懂這段代碼的意思,更糟糕的是這樣的注釋很可能誤導我們。
這并不是說注釋完全沒有必要,優秀的代碼有具有相應優秀的注釋。我們可以利用某些編程技術來減少我們的注釋,使我們的代碼更加自解釋。這不僅僅使我們的代碼更加容易理解,還有助于改善項目的整體設計。
這樣的代碼通常被稱為 自解釋 的代碼,下面我將介紹一些編寫自解釋代碼的方法。
概覽
一些程序猿將注釋也作為自解釋代碼的一部分,注釋很重要,可以用很大的篇幅單獨討論。在本文中,我們只討論代碼。
我先將要討論的技術分為三大類:
- 代碼結構 ,清晰的代碼和目錄結構能更好地表達我們的意圖;
- 命名相關 ,比如方法和變量命名;
- 語法相關 ,使用(不使用)某些語法特性可以使代碼更清晰。
這幾個點看起來都很簡單,難點在于在合適地方選擇合適的技術,下面我將用分別用實例講解如何使用這些技術。
代碼結構
改善現有代碼的結構來增加項目整體的清晰度。
提取幫助函數
將一些通用的代碼提取為幫助函數。例如,很難想到下面代碼是什么意思:
varwidth = (value -0.5) *16;
可以在這里添加注釋,或者將其提取成為一個函數:
varwidth = emToPixels(value);
functionemToPixels(ems){
return(ems -0.5) *16;
}
唯一的變化就是我們將計算過程移到一個函數中,通過函數名使其自解釋,同時我們還得到一個可以復用的幫助函數,減少了代碼冗余。
將條件表達式提取為函數
一個包含多個條件判斷的表達式在沒有注釋的情況下很難理解,看下面的代碼:
if(!el.offsetWidth || !el.offsetHeight) {
}
是不是很難理解,我們可以將條件判斷部分提取為一個函數,是不是瞬間就變得很好理解:
functionisVisible(el){
returnel.offsetWidth && el.offsetHeight;
}
if(!isVisible(el)) {
}
用變量替換表達式
這和上個方法很像,這里只是將表達式的計算結果放在一個變量中,看上面討論過的那個例子:
if(!el.offsetWidth || !el.offsetHeight) {
}
引入變量后:
varisVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}
當一段邏輯非常特殊,僅僅用在一個位置,使用變量就比提取函數更加合適。這種方法最常用于數學表達式:
returna * b + (c / d);
我們可以這樣重構:
varmultiplier = a * b;
vardivisor = c / d;
returnmultiplier + divisor;
類和模塊接口
類和模塊中的公共方法和屬性可以使代碼更加清晰,看下面示例:
classBox{
setState(state) {
this.state = state;
}
getState() {
returnthis.state;
}
}
當然,這個類還可以包含其他代碼,這里我特意寫了這樣一個簡單類來演示。你可以一眼就看出如何使用這些方法嗎?這些方法名看似都非常合理,盡管如此,我們還是不知道該如何使用這些方法,我們還需要閱讀類的使用文檔才能明白這些方法的作用。
如果改成如下實現呢:
classBox{
open() {
this.state ='open';
}
close() {
this.state ='closed';
}
isOpen() {
returnthis.state ==='open';
}
}
是不是更加容易理解和使用?現在,你可以一眼就看出來如何使用 Box 類。這里我們僅僅改變了公共接口,在內部仍然使用 this.state 屬性來表示。
代碼分組
將不同的代碼分組也可以作為“文檔”的一部分,例如,我們應該保證變量的聲明位置盡量靠近變量的使用位置,而且盡可能按組使用這些變量。
分組可以暗示代碼內部之間的關系,將來其讓人修改你的代碼時也可以更快找到該修改的位置。
varfoo =1;
blah()
xyz();
bar(foo);
baz(1337);
quux(foo);
你可以一看看出 foo 使用了多少次嗎?對比以下實現:
varfoo =1;
bar(foo);
quux(foo);
blah()
xyz();
baz(1337);
我們將使用 foo 的代碼分組到一起,這樣就可以清楚地知道哪些代碼依賴了這個變量。
使用純函數
純函數比依賴狀態的函數更加容易被理解。
什么是純函數呢?如果一個函數,對相同的輸入參數,無論何時調用這個函數總是返回相同的結果,這個函數沒有任何改變函數返回值的副作用(如,時間因素,Ajax請求等)。
這類函數更加容易理解,函數的輸出結果僅由輸入參數決定,你不必糾結這個結果到底是如何得到的,會不會有其他因素影響的了結果,你可以完全信任這類函數的返回值,更不會影響函數外部的狀態。
文件和目錄結構
在同一個項目中保持相同的命名約定,如果項目中沒有明確的命名約定,可以遵循你選擇的語言的標準。
比如,你正在添加 UI 相關的代碼,可以先在項目中找到這類代碼的位置,如果 UI 相關的代碼放在 src/ui/ 下面,那么請將你的代碼也放在這里。
命名相關
先看一個名言:
在計算機領域只有兩個難題:緩存失效和命名。– Phil Karlton
下面我們就來看看如何通過命名來使我們的代碼自解釋。
函數命名
函數命名并不復雜,但有幾個原則可以遵循:
- 避免使用語義模糊的動詞,比如“handle”或“manage”: handleLinks() , manageObjects() 我們很難理解這些方法到底是用來干什么的?
- 使用主動動詞: cutGrass() , sendFile() ;
- 暗示返回值: getMagicBullet() , readFile() ;
- 對于強類型的語言,還可以使用方法簽名來暗示函數的返回值。
變量命名
對于變量命名有兩個經驗法則:
- 暗示數值的單位:對于數值類型的變量,我們可以通過更好的命名來暗示該值對于的單位,例如,使用 widthPx 代替 width 可以讓我們更清晰地知道該值的單位是像素;
- 不要使用簡寫: a 或 b 是不規范的變量命名,循環中的計數器除外。
遵循現有的命名規范
盡量遵循現有項目中的命名規范。例如,對于特殊類型的對象,請保持相同的命名:
varelement = getElement();
請不要突然命名為:
varnode = getElement();
使用更有意義的錯誤提示
Undefined is not an object!
這個錯誤我們經常可以看到,這是一個反例,我們應該確保我們的代碼中拋出的任何錯誤都有一個有意義的錯誤消息。
如何做呢?
- 應該描述清楚具體的問題;
- 如果可能,盡可能包含導致該錯誤的變量或數據;
- 關鍵點:錯誤信息應該幫助我們找到錯誤所在,應該作為文檔告知我們函數應該如果工作。
語法相關
不要使用某些語法技巧
看下面示例:
imTricky && doMagic();
下面的方式更加一目了然:
if(imTricky) {
doMagic();
}
請總是使用后面這種方式,前一種語法技巧不會給任何人帶來任何好處。
使用命名的常量
如果在代碼中有一個特殊的數字或字符串字面量,請將其聲明為一個常量。如果在代碼中直接使用一個特殊的數字字面量,現在可能很好理解其意義,但在一兩個月之后,沒人會理解這個數字的具體意義。
constMEANING_OF_LIFE =42;
避免使用 Boolean 字面量
使用 Boolean 字面量可能導致一些不好理解的代碼:
myThing.setData({ x: 1},true);
我們壓根不知道這里的 true 是什么含義,除非閱讀 setData() 的源碼。我們可以添加另外一個方法來完成相同的功能:
myThing.mergeData({ x: 1});
充分利用語言特性
一個很好的例子是循環代碼:
varids = [];
for(vari =0; i < things.length; i++) {
ids.push(things[i].id);
}
在上面代碼中,我們收集數組每項的 ID 放到一個新數組中,為了搞懂這段代碼我們需要閱讀整個循環體中的代碼,請比較實用 map 的實現方式:
varids = things.map(function(thing){
returnthing.id;
});
另一個實例是 JavaScript 的 const 關鍵字。通常,我們可能會定義一些永遠都不會改變值的變量,一個非常常見的例子是實用 CommonJS 加載一個模塊:
varasync=require('async');
我們可以直接將其聲明為一個常量,使其意義更加清晰:
constasync=require('async');
結論
編寫自解釋的代碼能夠提高系統的可維護性,每一段注釋都需要額外的維護精力,所以應該盡可能減少注釋的數量。然而,自解釋的代碼并不能完全替代注釋,有必要在適當的位置保留某些關鍵的注釋,而且 API 文檔也非常必要,除非你的代碼庫非常輕量級 – 開發人員可以直接閱讀你的代碼。
來自:http://bubkoo.com/2016/09/08/15-ways-to-write-self-documenting-javascript/