前言我們在學校寫作業(yè)習慣上把我們要用的變數(shù)用單一字母來命名,比方說a或b.c.i,這種命名履見不鮮,但是筆者要強調(diào)的是,大型程式,這樣阿貓阿狗的命名是極糟的示範,它本身並未包含任何程式意圖。
但這並不是我們的錯,而是因為我們從小到大從未聽過一個有說服力的說法來說服我們遵循這個好原則。
筆者在上一章提到,維持程式的整齊非常重要,而這其中最重要的一小步,同時也是我們維持整齊的一大步,那就是好的命名。
這關係到我們構築程式碼的品質(zhì),材料的質(zhì)感跟品質(zhì)不好(也就是命名),就算結構再好,都不可能構築一個好的程式碼。
介紹
好的命名確實有難度,而且必需高度認真看待命名這件事,才能得到成效,而這一切的作為將花掉我們不少時間,但是替換命名的代價遠比我們事後的檢修代價來得小,因為良好的命名可以徹底表達程式的意圖跟概念。
你可能會直截了當?shù)姆瘩g這一點,並且義正嚴辭的說:我們可以用註解來代替程式碼表達不清處的部份,但我必需說,如果一個程式碼本身需要靠註解才能完整表達本身的意圖,那它就是不合格的程式碼。
例如筆者前陣子寫的多項式加法就可以拿來當實例,筆者只截取前面幾段供參考,剩下的段落由讀者自行感受:
i1 = 0;
i2 = 0;
i3 = 0;
while( (i1 < p1) || (i2 < p2) )
{
while( (A[1][i1] == B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
{
C[0][i3] = A[0][i1] + B[0][i2];
C[1][i3] = A[1][i1];
i3++;
i2++;
i1++;
}
while( (A[1][i1] > B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
{
C[0][i3] = B[0][i2];
C[1][i3] = B[1][i2];
i3++;
i2++;
}
while( (A[1][i1] < B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
{
C[0][i3] = A[0][i1];
C[1][i3] = A[1][i1];
i3++;
i1++;
}
}
這段程式碼讓我不清楚它的本身意圖,它的邏輯安排還算有秩序,演算法也很簡易,但是它的程式碼隱晦度讓我們被迫去理解以下4件事:
1. A, B, C 陣列存放的資料內(nèi)容代表的是什麼東西?
2. A, B, C 行標號數(shù)字代表什麼?比如:i1,i2,i3
3. A, B, C 列標號中的 0 跟 1 有什麼意義?
4. p1,p2代表什麼?
我們常常會認為,程式碼本身就是很難理解的,並且毫無道理地相信一件不完全正確的事:程式碼無法正確而完整的解釋它們所應當讓人理解的概念,你可能會說,既然程式碼無法確實解釋我們要的想法,那就想辦法多加一些註解吧。
錯了,我們大多數(shù)的人都看小了程式碼的表達功力,程式碼某種程度上就應該負責大部份的表達工作,事實上註解極少但是擁有良好命名的程式碼遠比註解很多命名很糟的程式碼來的優(yōu)。
如果你發(fā)現(xiàn)你的程式碼需要大量的註解輔助你對程式的理解,那你就要相當程度的去質(zhì)疑你是否有發(fā)揮程式本身應該具有的表達力,請牢記在心:註解無法彌補失職的程式碼,而這一切失職往往出於程式設計師的不用心,與其想著如何寫註解,不如多花幾秒去想,如何讓你手上的程式碼更好理解。
未避免開空頭支票,我必需誠實對讀者兌現(xiàn),就拿上面的例子來說好了,我們看到這段程式碼,直覺會認定不好理解,所以最普通的作法是,我們將我們的意圖用註解闡明,以便我們?nèi)蔗崂斫膺@一段可以回答上述4個大哉問:
i1 = 0; /* 代表多相式 A 第i1相 */
i2 = 0; /* 代表多相式 B 第i2相 */
i3 = 0; /* 代表多相式 C 第i3相 */
while( (i1 < p1) || (i2 < p2) ) /* p1, p2分別為 A 多項式和 B 多項式的陣列長度 */
{
while( (A[1][i1] == B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
/* A[1][i1] 代表 A 多相式的第 i1 項的次方數(shù) */
/* B[1][i2] 代表 B 多相式的第 i2 項的次方數(shù) */
{
C[0][i3] = A[0][i1] + B[0][i2]; /* A[0][i1] 代表 A 多相式的第 i1 項的係數(shù),B[0][i2] 代表 B 多相式的第 i2 項的係數(shù),C[0][i3] 代表 B 多相式的第 i3 項的係數(shù) */
C[1][i3] = A[1][i1]; /* C[1][i3] 代表 C 多相式的第 i3 項的次方數(shù) */
i3++;
i2++;
i1++;
}
while( (A[1][i1] > B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
{
C[0][i3] = B[0][i2];
C[1][i3] = B[1][i2];
i3++;
i2++;
}
while( (A[1][i1] < B[1][i2]) && ((i1 < p1) || (i2 < p2)) )
{
C[0][i3] = A[0][i1];
C[1][i3] = A[1][i1];
i3++;
i1++;
}
}
各位在這裡應該察覺到註解變得非常凌亂,而且光是理解註解的難度可能就比程式碼還來的複雜,更不用說註解還有空間限制,它並不能包辦你每一個空間的程式碼,但是不適當?shù)拿赡苌言谀阏麄€程式碼的範圍,而這個範圍是註解很難管轄到的。
再來是時間限制,誰知道我程式碼會不會將概念跟內(nèi)容通通變更?到時後註解若是沒同步更新,我們又更難確知程式碼本身的意圖了。
請記住:只有程式碼能忠實的告訴你程式本身的作用,它是唯一即時且本質(zhì)上是準確的資訊。
我們再將上面的範例改一改,這次我們直接改命名,將多項式 A,B,C 依序變更名稱為 firstPoly, secondPoly, resultPoly,我們也依序?qū)?/font> i1,i2,i3 改為caseFirst, caseSecond, caseResult ,甚至我們可以將列標號 0 跟 1 包裝成 coef 和 pow。
你們很可能會有一些抱怨,這個命名真的太長了,但是命名的原則是,寧可取用表達力足夠的命名,也不要簡潔卻不明確的命名,而且最好我的命名是可以發(fā)音的,以免與專案成員討論的時後無法順利進行程式內(nèi)容的討論,這是便於我日後維護工作。
我們來Demo一下命名更改以後的成果吧......
首先看前3行
i1 = 0; /* 代表多相式 A 第i1相 */
i2 = 0; /* 代表多相式 B 第i2相 */
i3 = 0; /* 代表多相式 C 第i3相 */
這邊我們發(fā)現(xiàn)一件很妙的事,修改命名以後我的註解自然也被新的命名給詮釋掉了,所以我們用程式碼來取代這些不需要的註解......
caseFirst = 0;
caseSecond = 0;
caseResult = 0;
接下去我們將直接了當野蠻的幹下去,當然只截取部份的更改,剩下的留給讀者思考......
coef = 0;
pow = 1;
.
.
.
resultPoly[ coef ][ caseResult ] = firstPoly[ coef ][ caseFirst ] + secondPoly[ coef ][ caseSecond ];
resultPoly[ pow ][ caseResult ] = firstPoly[ pow ][ caseFirst ];
caseResult++;
caseSecond++;
caseFirst++;
在這裡大家應該很清楚了吧,一個好的命名閱讀起來,程式碼就會像是一段文字陳述式,就像是在看報章雜誌,你甚至能夠光靠直覺就去判定,這個演算法的長相,而這一切都可歸於一個結論:好的函數(shù)名稱和變數(shù)名稱就是註解的載體。
記住喔,我是說光靠直覺喔,當然這一部份筆者未盡到完善的地方便是while敘述後面的條件式,在這裡我應該用一個簡易的名稱把條件式整個包裝起來,只是這部份我留給讀者去思考。
命名的大方向
1.必需能被搜尋
單一字母變數(shù)的缺陷在這裡將會展露無遺,我們可能會搜尋到很多結果,但是結果都不盡然是我們想要的,增加後續(xù)維護的功夫。
2.函數(shù)的命名用動詞或動詞片語
比方說我有一個函數(shù)要把球踢出去,我會這樣命名......
kickBall(......)
{
......
}
即便再怎麼直覺,命名都是用英文的方式去陳述,但老實說英文程度不好的人看起來可能還是有障礙,基本上這樣命名對老外本身就非常容易讀懂,這是為什麼我們會比別人更傾向依靠註解的原因,但我還是必需鼓勵你試著少用。
3.類別與變數(shù)的命名用名詞或名詞片語
記住,類別的命名對向是一個實體或物件,所以你必需應著你的實體或物件的特性去命名,或者應著你的類別職責去命名。
4.全域變數(shù)越特別越好,有的時後會用很長的一串字命名,區(qū)域變數(shù)則視其範圍可以簡潔有力,變數(shù)的包含範圍越大,就越容易重複或誤用甚至誤導,所以一旦是全域變數(shù),命名方面必需非常謹慎;至於區(qū)域型的變數(shù),因為使用範圍小,所以原則上以簡單易懂為主,表達概略意圖即可。
按照含括範圍的大小來給定命名,依狀況不同作微調(diào)。
至於特殊情況,像 for 迴圈裡的 i 基本上是慣用語法,主要是用來代表迴圈執(zhí)行次數(shù),除非別有用途,不然這部份的變數(shù)宣告成 i 是可以讓人接受的......
int i;
for( i = 0; i <= 5 i++ )
{
printf( "\n" );
}
總結
寫程式就像寫作文一樣,程式語言必需表達完整的程式概念,使用註解前,先停下來思考一下,是否可以靠命名來取代註解?再來就是,永遠別滿足於你現(xiàn)在的命名,當你發(fā)現(xiàn)你的命名正在誤導你理解程式的時後,試著狠狠改掉它,給它一個更好的詮釋。