如何編寫高質量和可維護的代碼
我們怎么做才能既不需要寫很多注釋,又能保證代碼易于理解呢?
其中一個主要的方法就是讓代碼自文檔化。其優勢在于,既不用寫注釋,又能使得代碼易于維護。
下面就是三種使得代碼自文檔化的基本方法:
- 命名:利用名字來解釋變量、函數等的目的。
- 封裝函數:將一些特定功能的代碼封裝成一個函數以明確目的。
- 引入變量:將表達式插入至專用變量。
這可能看上去很簡單,但在實際操作過程中會讓人覺得有點棘手。首先你得明白哪些地方有問題以及哪些地方適用這些方法。
除了上面三個以外,還有一些應用范圍也比較廣的方法:
- 類和模塊接口:將類和模塊中的函數暴露出來,讓代碼更加清晰。
- 代碼分組:用組來區分不同的代碼片段。
接下來我們將具體講一講如何在實際應用中運用上面這5個方法。
1.命名
先看幾個如何運用命名的方法來闡述代碼使得其自文檔化的例子。
重命名函數
給函數命名通常都不會太難,但是這里面也有一些需要遵循的簡單規則:
避免使用含糊的字眼,例如“handle”或“manage”——handleLinks、manageObjects。
使用主動動詞——cutGrass、sendFile,以表示函數主動執行。
指定返回值類型——getMagicBullet、READFILE。強類型的語言也可以用類型標識符來表明函數的返回值類型。
重命名變量
指定單位——如果里面有數值參數,那可以加上其單位。例如,用widthPx來取代width以指定寬度的單位是像素。
不要使用快捷鍵——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?
翻譯作者:碼農網 – 小峰