我應該在代碼中寫注釋嗎?或者是不惜一切代價避免呢?抑或是有節(jié)制的使用它們呢?在開發(fā)軟件的時候,所有的開發(fā)者對何時何地使用注釋都有他們自己的見解。這篇文章只是闡述我的觀點,而不是什么真理。
我的這個觀點僅限于JavaScript面向過程編程或者面向對象編程。
注釋和可維護性
這篇博客是關于編寫可維護的代碼,即:
- 容易理解
- 易于擴展
- 易于調試
- 方便測試
可維護的代碼就需要大量的注釋嗎?如果代碼寫的好,那我覺得就不是必須的。代碼本身就能說明問題。
可維護的代碼不需要任何的注釋嗎?下面我們通過一些例子來說明。
注釋的演化
方法1:JavaScript diluted with BS
假設一個昵稱為 ByTheBook 的初級開發(fā)者下了下面的代碼:
/**
* getWinner
*
* Gets the name of the winner who has the best score from the
* participants array.
*
* @param {Array} participants
* @return {string} the name
* @throws {Error} wrong participant list
*/
function getWinner( participants ) {
var maxScore = 0; // temporary variable to keep maximum score
// initially -1 to always find a participant
var name = null; // name with maximum score
// debugger; // uncomment for debugging
// participants is an array, so it has a length property
for( var i = 0; i < participants.length; ++i ) { // i: loop variable
var currentParticipant = participants[i]; // we examine this participant
if( currentParticipant.score > maxScore ) { // initially any number > null
// we found a new candidate to return
name = currentParticipant.name;
// DON'T FORGET TO UPDATE THE MAX SCORE!!!
maxScore = currentParticipant.score;
}
}
// console.log( name, typeof name );
// Correct result has to be a string
if( typeof name === 'string' ) {
return name;
} else { // technically an else is not needed here
throw new Error( 'Wrong participants' ); // Wrong participants
}
// unreachable
}
這段代碼有什么問題嗎?
首先,一個原則是,不應該在你的應用中留下任何注釋掉的代碼,這顯得非常的不專業(yè)。如果注釋的代碼只是用于調試的目的,那將更糟。清理掉你的提交,如果已經晚了的話,以后要養(yǎng)成本地提交的習慣,然后使用 rebase 來清理掉提交。
保留注釋的代碼只是很大問題中的一小部分:從技術上講,90%的這些注釋的代碼只會增加麻煩,它們沒有任何的價值,而且還會分散讀者的注意力。例如,在讀完上面的代碼后,你是否考慮到如果有不止一個的參與者有相同的最高分,我們的程序應該如何處理呢。
方法2:Self-documenting code
Flash 是另一個初級開發(fā)者,假設他寫了下面這段沒有任何注釋的代碼:
function getWinner( participants ) {
if( participants.length === 0 ) return null;
var lastIndex = participants.length - 1;
var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
return topParticipant.name;
}
從技術上講,該解決方案是有效的。并且,在沒有參與者的時候會返回 null 。然而,該代碼并沒有對處理并列第一名的問題給出任何的注釋。
另外,在沒有讀完所有代碼的時候,你能知道 participants 對象的格式嗎?你必須從代碼中反向獲得信息:participants 是一個數組對象,它包含 name 和 score 兩個元素,類型分別為 string 和 integer 。
方法3:The Startup code
Startupper 注意到了該問題,然而他有很多其他的事情要做,所以他只是放了一個 TODO 的注釋:
// TODO: sort out ties when two participants have max score
function getWinner( participants ) {
if( participants.length === 0 ) return null;
var lastIndex = participants.length - 1;
var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
return topParticipant.name;
}
使用 TODO 的問題是它們可能一直保留在代碼中。一些 TODO 可能放在另一些 TODO 前面。一些TODO可能會與另外的有沖突。另一些可能描述的很不清楚,沒人能看懂他的初衷是什么。
在這個例子中 TODO 描述了實質的問題,雖然還沒有給出解決方案。
另外, participants 參數的類型仍然不清楚。
方法4:Declarative head comments
Declarator 有個寫頭部聲明注釋的習慣:
- 簡短而精確的功能描述
- 參數類型列表以及返回值
- 異常
/**
* getWinner returns the name of the first participant in participants
* with the highest score.
*
* @param {Array of Objects} participants
* Elements: {string} name
* {integer} score
* @return {Mixed(string|null)} name
*/
function getWinner( participants ) {
if( participants.length === 0 ) return null;
var lastIndex = participants.length - 1;
var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
return topParticipant.name;
}
該解決方案清楚的記錄了 getWinner 的作用。大部分代碼都非常有幫助,并且做到了與代碼分離,不存在行內注釋。
方法5:修復問題
PickyPerfectionist 是一個不會妥協(xié)的開發(fā)者。他以編寫可維護的代碼為榮。他的口頭禪是:“只有爛代碼才需要注釋”。他的第二個信條是,從不發(fā)布不夠好的代碼。這意味著他從來都不寫注釋。
在寫功能之前,PickyPerfectionist 都會先明確需求。在有相同最高分的時候,他會把所有的參與者姓名都打印出來,以逗號分隔。
function getWinners( participants ) {
var winnerNames = [];
var winnerScore = null;
for( var i = 0; i < participants.length; ++i ) {
var currentName = participants[i].name;
var currentScore = participants[i].score;
if( currentScore > winnerScore ) {
winnerScore = currentScore;
winnerNames = [ currentName ];
} else if ( currentScore === winnerScore ) {
winnerNames.push( currentName );
}
}
var result = winnerNames.join( ', ' );
return result.length > 0 ? result : null;
}
這段代碼非常易讀。并且解決了前面存在的問題,不需要做任何的注釋。雖然對 participants 的結構沒有給出定義,但你能從代碼中了解到所有你需要的信息。即使函數名改變了,那也不會有任何的問題。
假設利益相關者決定不是列舉所有贏家,他們改變了游戲規(guī)則,只有一個贏家。這個你剛才看到過的注釋非常少的代碼將可以解決問題:
function getWinner( participants ) {
if( participants.length === 0 ) return null;
var lastIndex = participants.length - 1;
var topParticipant = _.sortBy( participants, 'score' )[ lastIndex ];
return topParticipant.name;
}
假如有一天,有個開發(fā)者發(fā)現(xiàn)了問題,這個方法只返回第一個得分最高的參與者?;诎l(fā)現(xiàn)這個問題的開發(fā)者類型的不同,有可能會出現(xiàn)不同的解決方案。解決方案可能是一個 TODO 注釋或者是與相關利益者要求不符的方法。所有的解決方案都有下面兩個共同點:
- 都會需要時間
- 在某種程度上增加麻煩
你會考慮使用注釋嗎?你有其他避免浪費時間的替代解決方案嗎?如果是的話,假設你看文檔,這也是需要時間的。
從技術上講,自動化測試可以確保只有一個勝利者。單元測試可以避免開發(fā)者與要求不符的提交。但提前測試并不能指出問題,甚至在特殊情況下,現(xiàn)有的測試可能會讓你修改
getWinner方法,而返回多個贏家。
常識性的注釋
我的觀點是,只要是有價值的注釋,都是有用的。沒有最終的對與錯。
禁止代碼的注釋就是計算你飲食的卡路里,在一定程度上,它們是有幫助的,如每天攝入2000卡路里而不是8000或者300。吃沙拉的好處遠比喝汽水要多。相似的,禁止代碼的注釋可以促使你寫更好的代碼。然而,在代碼確實需要注釋的時候,禁用注釋在長遠上看是非常有害的,這最終會導致代碼混亂,可維護性差。
什么時候注釋是有用的呢?首先,JavaScript不允許指定參數和返回值的類型。因此,關于類型定義的信息是有用的。這些類型定義在許多其他語言的函數中都有。如果你知道 == 和 === 的區(qū)別,并且想使用 === ,那么變量類型是很重要的。
比如下面的例子:
/**
* listenTo
*
* Registers callback so that it's immediately called once emitter emits
* the event eventName.
*
* @param {Object} emitter
* @param {string} eventName
* @param {function} callback
*
* @return {void}
*/
listenTo : function( emitter, eventName, callback ) { /* ... */ }
類型定義非常明確。
當涉及到類或者函數定義的時候,我更偏向于頭部聲明。一句話就可以說明類的作用,它里面的方法也一樣。面向過程的編程,頭部聲明也是很有用的。
頭部注釋聲明也可以包含下面這些:
- 可選參數:有時候最后一個參數可能是未定義的
- 混合類型
- 符合類型:參見方法4
- 異常信息
- 調用該方法的前提條件
- 副作用
決定在注釋中使用以及避免哪些信息,非常簡單,卻很重要。一個周末項目或者原型的注釋與一個需要長期開發(fā)和維護的網絡項目的注釋應該是不同的。
編寫優(yōu)秀的頭部注釋是一門藝術。但寫很好的頭部注釋是需要常識的。常識告訴我,頭部注釋應該是包含信息的。下面是一個不好的例子:
/**
* onRender
*
* Callback on render
*
* @return {void}
*/
onRender : function() {
this.removeAllBindings();
this.cleanOpenedWidgets();
}
而這個會好一些:
/**
* onRender: called before rendering the component for the purpose
* of cleaning up model-view bindings and removing nodes left by
* opened widgets.
*
* @return {void}
*/
onRender : function() {
this.removeAllBindings();
this.cleanOpenedWidgets();
}
一個好的頭部注釋使得99.99%的行內注釋變得沒有意義。我基本上同意以下觀點:不管方法多么的復雜,行內注釋只會分散開發(fā)者的注意力,而不會對他們有任何幫助。
如果一個頭部注釋很難寫,那么這恰恰說明你應該好好考慮下是否應該把方法分解成多個。因此,頭部注釋可以幫你寫出更好的代碼。
從代碼維護的角度來看,頭部注釋至少有以下兩個好處:
- 當審核人只需要了解方法的作用的時候,頭部注釋可以節(jié)省很多時間;
- 對于錯誤的方法,審核人能夠方便的了解到該方法的原始意圖
為了這些,頭部注釋都是必須的。這并不是一項多么艱難的工作,因為開發(fā)者知道他們自己正在做什么。
我是否應該寫注釋呢?
這取決于你,看哪樣對你更有幫助。有些項目不需要任何的注釋,而有些則必須包含頭部注釋。不像其他強類型語言,JavaScript的注釋給出了參數以及返回值的變量類型。緊湊的頭部注釋可以使你的代碼更容易理解。此外,頭部注釋會使你思考你的代碼,這有時候會讓你的代碼更容易維護。
綜上所述,我不認為所有的注釋都是沒用的。世界也不是絕對的黑的或者白的。極端的意見通常會引起更強烈的爭論,而折中的方法在長遠來看卻更加有幫助。
哈爾濱品用軟件有限公司致力于為哈爾濱的中小企業(yè)制作大氣、美觀的優(yōu)秀網站,并且能夠搭建符合百度排名規(guī)范的網站基底,使您的網站無需額外費用,即可穩(wěn)步提升排名至首頁。歡迎體驗最佳的哈爾濱網站建設。
