《程式設計實踐》第一章編碼風格的不完全總結

Gental發表於2018-04-19

名字

全域性變數使用描述性的名字,區域性變數則使用簡潔的名字。根據定義,全域性變數可以出現在整個程式中的任何地方,因此他們需要一個足夠長並且詳細描述的名字讓讀者想起它們的意義。給每個全域性變數宣告附加一個簡短註釋也非常有幫助:

    int npending = 0; // current length of input queue

全域性函式、類和結構應該也要有描述性的名字,以表明它們在程式裡扮演的角色。
對於區域性變數使用簡短的名字就夠了。在函式裡,n 可能就足夠了,npoints 也還可以,用 numberOfPoints 就太過分了。
按常規方式使用的區域性變數可以採用極短的名字。例如用 i、j 作為迴圈變數,p、q 作為指標,s、t 表示字串等。比較:

    for (theElementIndex = 0; theElementIndex < numberOfElements; theElementIndex++)
        elementArray[theElementIndex] = theElementIndex;

to

    for (i = 0; i < nelems; i++)
        elem[i] = i;

對返回布林值的函式命名,應該清楚地反映其返回值情況。因此

    if (checkoctal(c)) ...

沒有表明哪一個返回值是真,哪一個是假,而:

     if (isoctal(c)) ...

說清楚瞭如果引數是八進位制數字則該函式返回真,否則為假。

表示式和語句

使用自然的表示式。含有否定運算的條件表示式比較難以理解:

    if (!(block_id < actblks) || !(block_id >= unblocks))

在兩個測試中都用到了否定運算,有點多餘。應該改變關係運算子的方向:

    if ((block_id >= actblks) || !(block_id < unblocks))

現在程式碼讀起來就自然多了。

用括號解決歧義

    leap_year = y % 4 == 0 && y % 100 != 0 || y % 400 == 0;
    leap_year = ((y%4 == 0) && (y%100 != O)) || (y%400 == 0);

去掉了一些空格:將高優先順序運算子的運算元聚合在一起,幫助讀者更快地看清表示式的結構。

要清晰。程式設計師有時把自己無盡的創造力用到了儘可能地寫最簡短的程式碼,或者尋求巧妙方法去得到一個結果。有時這種技能被誤用了,因為我們的目標是寫出清晰的程式碼,而不是巧妙的程式碼。

運算子 ?: 適用於簡短的表示式,這時它可以把4行的 if-else 程式變成1行。例如這樣:

    max = (a > b) ? a : b;

當心副作用。 I/O 操作有時會帶來難以發現的問題。下面的例子希望從標準輸入讀入兩個相關聯的數:

    scanf("%d %d", &yr, &profit[yr]);

你可能認為答案依賴於引數的求值順序,但是實際上傳入 scanf 的所有引數在函式被呼叫前就已經計算好了,所以 &profit[yr] 實際使用的是舊的 yr 。解決辦法是把語句分解為兩個:

    scanf("%d", &yr);
    scanf("%d", &profit[yr]);

一致性和習慣用法

如果你在一個之前不是你寫的程式上開發,應該保留程式原有的風格。即使你更喜歡你自己的風格,當你需要做修改時,也不要使用。程式的一致性比你個人的習慣更重要,對於後續跟進的人來說更方便。

函式巨集

函式巨集最常見的一個嚴重問題是:如果一個引數在定義中出現多次,它就可能被多次求值。如果呼叫時的實際引數帶有副作用,結果就會產生一個難以捉摸的錯誤。
下面的程式碼段來自<ctype.h>,其意圖是實現對一個字元的測試:

    #define isupper(c) ((c) >= `A` && (c) <= `Z`)

如果 isupper 被這樣子中呼叫:

    while (isupper(c = getchar()))

getchar()函式會被呼叫兩次,那麼,每當遇到一個大於等於 A 的字元,程式就會將它丟掉,而下一個字元將被讀入並去與 Z 做比較,從而導致難以預料的後果。

既然巨集的缺點在很大程度上蓋過了它的優點,使用過程一不小心還會引入問題,那麼我們該在何時使用巨集呢?答案如下:

    //計算出陣列的元素個數
    #define NELEMS(array) sizeof(array)/sizeof(array[0])

    double dbuf[100];
    for (i = 0; i < NELEMS(dbuf); i++)
       ...

在這裡,陣列大小隻在一個地方設定。如果陣列的大小改變,其餘程式碼都不必改動。對函式引數的多次求值在這裡也不會出問題,因為它不會出現任何副作用.事實上,這個計算在程式編譯時就已經做完了。這是巨集的一個恰當使用,因為它做了某種函式無法完成的工作,從陣列宣告計算出它的大小。

魔術數字

給魔術數字命名。魔術數字包括各種常數、陣列的大小、字元位置等等。除了 0 和 1 之外,程式裡出現的任何數大概都可以算是魔術數字,它們應該有自己的名字。

把數字定義為常數,不要定義為巨集。C 程式設計師的傳統方式是用 #define 行來對付神祕的數值。使用巨集進行程式設計是一種危險的方式,因為巨集會在背地裡改變程式的詞法結構,我們應該讓語言去做正確的工作 。在 C 和 C++ 裡,整數常數可以用列舉語句定義。在 C++ 裡任何型別都可使用 const 宣告的常數:

    const int MACROW = 24, MAXCOL = 80;

在 Java 中可以用 final 宣告:

    static final int MACROW = 24, MAXCOL = 80;

C 語言裡也有 const 值,但它們不能用作陣列的邊界。所以 enum 語句仍然是在 C 裡面可選用的方法。

與此類似的還有另一個問題,那就是程式裡許多上下文中經常出現的 0。雖然編譯系統會把它轉換為適當型別,但是,如果我們把每個 0 的型別寫得更明確更清楚,對讀程式的人理解其作用是很有幫助的。例如,用 (v o i d *) 0 或 NULL 表示 C 裡的空指標值,用 ‘ ’ 而不是 0 表示字串結尾的空位元組。也就是說不要寫成:

    str = 0;
    name[i] = 0;
    x = 0;

應該寫成:

    str = NULL;
    name[i] = "
";
    x = 0;

註釋

當你改變程式碼的時候,一定要注意保證對應的註釋是準確的。

 


以上就是我在閱讀完《程式設計實踐》第一章後做的一些筆記和總結,也算是個人部落格的開篇,歡迎交流。

相關文章