如何編寫高質量和可維護的代碼
其中一個主要的方法就是讓代碼自文檔化。其優勢在於,既不用寫註釋,又能使得代碼易於維護。
下面就是三種使得代碼自文檔化的基本方法:
- 命名:利用名字來解釋變量、函數等的目的。
- 封裝函數:將一些特定功能的代碼封裝成一個函數以明確目的。
- 引入變量:將表達式插入至專用變量。
- 類和模塊接口:將類和模塊中的函數暴露出來,讓代碼更加清晰。
- 代碼分組:用組來區分不同的代碼片段。
1.命名
先看幾個如何運用命名的方法來闡述代碼使得其自文檔化的例子。
一、重命名函數
給函數命名通常都不會太難,但是這裏面也有一些需要遵循的簡單規則:
1、避免使用含糊的字眼,例如“handle”或“manage”——handleLinks、manageObjects。
2、使用主動動詞——cutGrass、sendFile,以表示函數主動執行。
3、指定返回值類型——getMagicBullet、READFILE。強類型的語言也可以用類型標識符來表明函數的返回值類型。
二、重命名變量
1、指定單位——如果裏面有數值參數,那可以加上其單位。例如,用widthPx來取代width以指定寬度的單位是像素。
2、不要使用快捷鍵——a和b都不能作爲參數名。
封裝函數
關於這一點,我們將舉幾個如何把代碼封裝成函數的例子。此外,這麼做還有一個好處是,可以避免重複代碼。
將代碼封裝成函數
這是最基本的:將代碼封裝成函數以明確其目的。
猜猜下面這行代碼是幹什麼的:
var width = (value - 0.5) * 16;
好像不是很清楚,當然有註釋就一清二楚了,但是我們完全可以封裝成函數以實現自文檔化……
var width = emToPixels(value);
function emToPixels(ems) {
return (ems - 0.5) * 16;
}
唯一改變的是計算過程被轉移到了一個函數裏。該函數名明確地表達了它要做什麼,這樣一來就不必寫註釋了。而且,如果有需要後面還可以直接調用此函數,一舉兩得,減少了重複勞動。
用函數表示條件表達式
If語句如果包含多個運算對象,不寫註釋的話理解起來就比較難。
if(!el.offsetWidth || !el.offsetHeight) {
}
知道上面這代碼的目的不?
function isVisible(el) {
return el.offsetWidth && el.offsetHeight;
}
if(!isVisible(el)) {
}
其實,只要將這些代碼封裝到一個函數裏,那就很容易理解了。
引入變量
最後再講講如何引入變量。相較於上面兩個方法,這個可能沒那麼有用,但是無論如何,知道比不知道好。
用變量替換表達式
還是上面這個if語句的例子:
if(!el.offsetWidth || !el.offsetHeight) {
}
這次我們不封裝函數,改用引入變量:
var isVisible = el.offsetWidth && el.offsetHeight;
if(!isVisible) {
}
用變量替換程式
我們也可以用來清楚說明覆雜程式:
return a * b + (c / d);
var divisor = c / d;
var multiplier = a * b;
return multiplier + divisor;
類和模塊接口
類和模塊的接口——也是面向公共的方法和屬性——有點像說明如何使用的文檔。
看個例子:
class Box {
public function setState(state) {
this.state = state;
}
public function getState() {
return this.state;
}
}
這個類也可以包含其他代碼。我特意舉這個例子是想說明公共接口如何自文檔化。
你能說出這個類是如何被調用的嗎?很顯然,這並不明顯。
這兩個函數都應該換個合理的名字以表述它們的目的。但即便做到這一點,我們還是不怎麼清楚如何使用。然後就需要閱讀更多的代碼或者翻閱文檔。
但是如果我們這樣改一下呢……
class Box {
public function open() {
this.state = open;
}
public function close() {
this.state = closed;
}
public function isOpen() {
return this.state == open;
}
}
明白多了,是吧?請注意,我們只是改動了公共接口,其內部表達與原先的this.state狀態相同。
這麼一改,我們一眼看去就知道怎麼用。原先那個函數名雖然不錯,但是依然讓我們覺得雲裏霧裏,還不如後者直截了當。像這樣做一個小小的改動產生大大的影響,何樂而不爲呢?
代碼分組
用組來區分不同的代碼片段也是自文檔化的一種形式。
例如,像這篇文章中說的那樣,我們應該儘可能將變量定義在靠近使用它的地方,並且儘可能將變量分門別類。
這也可以用來指定不同代碼組之間的關係,這樣更加方便其他人知道他們還需要了解哪些代碼組。
看看下面的例子:
var foo = 1;
blah()
xyz();
bar(foo);
baz(1337);
quux(foo);
與
var foo = 1;
bar(foo);
quux(foo);
blah()
xyz();
baz(1337);
將foo的所有使用組合放在一起,一眼望去就能知道各種關係。
但是有時候我們不得不在中間調用一些其他函數。所以如果可以那就儘量使用代碼分組,如果不可以,那就不要強求。
其他建議
- 不要自作聰明。看看下面這兩個等價的表達式:
imTricky && doMagic();
if(imTricky) {
doMagic();
}
很顯然後者更好。
- 命名常量:如果代碼裏面有一些特殊值,那最好給它們命名。var PURPOSE_OF_LIFE = 42;
- 制定規則:最好遵循相同的命名規則。這樣閱讀的人就能在參考其他代碼的基礎上正確猜測出各種事物的含義。
結論
要想能使代碼自文檔化提高其可維護性是一個非常漫長的歷程。每個註釋都需要花心力去寫,所以儘量精簡方可省時省力。
然而,自文檔化的代碼永遠取代不了文檔和註釋。因爲代碼在表述上總有其限制,所以寫好註釋亦是不可或缺的。此外,API文檔於類庫而言非常重要,因爲光靠閱讀代碼是理解不了的,除非這個類庫真的是小得不能再小。
譯文鏈接:http://www.codeceo.com/article/how-to-coding-best.html
英文原文:How
to make your code self-documenting?
翻譯作者:碼農網 –
小峯