代碼風格

代碼風格主要分一下5個方面:

  1. 縮進和空白
  2. 註釋
  3. 標誌符命名
  4. 函數
  5. indent工具

 

縮進和空白

  1. 關鍵字ifwhilefor與其後的控制表達式的(括號之間插入一個空格分隔,但括號內的表達式應緊貼括號。例如:

    while␣(1);

     

  2.  雙目運算符的兩側各插入一個空格分隔,單目運算符和操作數之間不加空格,例如i␣=␣i␣+␣1++i!(i␣<␣1)-x&a[1]等。
  3. 後綴運算符和操作數之間也不加空格,例如取結構體成員s.a、函數調用foo(arg1)、取數組成員a[i]
  4. ,號和;號之後要加空格,這是英文的書寫習慣,例如for␣(i␣=␣1;␣i␣<␣10;␣i++)foo(arg1,␣arg2)
  5. 以上關於雙目運算符和後綴運算符的規則並沒有嚴格要求,有時候爲了突出優先級也可以寫得更緊湊一些,例如for␣(i=1;␣i<10;␣i++)distance␣=␣sqrt(x*x␣+␣y*y)等。但是省略的空格一定不要誤導了讀代碼的人,例如a||b␣&&␣c很容易讓人理解成錯誤的優先級
  6. 由於UNIX系統標準的字符終端是24行80列的,接近或大於80個字符的較長語句要折行寫,折行後用空格和上面的表達式或參數對齊,例如:
    if␣(sqrt(x*x␣+␣y*y)␣>␣5.0
        &&␣x␣<␣0.0
        &&␣y␣>␣0.0)

     

  7.  較長的字符串可以斷成多個字符串然後分行書寫,例如:
    printf("This is such a long sentence that "
           "it cannot be held within a line\n");

    C編譯器會自動把相鄰的多個字符串接在一起,以上兩個字符串相當於一個字符串"This is such a long sentence that it cannot be held within a line\n"

  8. 有的人喜歡在變量定義語句中用Tab字符,使變量名對齊,這樣看起來很美觀。

    →int    →a, b;
           →double →c;

     

  9.  內核代碼風格關於縮進的規則有以下幾條

1、要用縮進體現出語句塊的層次關係,使用Tab字符縮進,不能用空格代替Tab。在標準的字符終端上一個Tab看起來是8個空格的寬度,如果你的文本編輯器可以設置Tab的顯示寬度是幾個空格,建議也設成8,這樣大的縮進使代碼看起來非常清晰。如果有的行用空格做縮進,有的行用Tab做縮進,甚至空格和Tab混用,那麼一旦改變了文本編輯器的Tab顯示寬度就會看起來非常混亂,所以內核代碼風格規定只能用Tab做縮進,不能用空格代替Tab。

2、if/elsewhiledo/whileforswitch這些可以帶語句塊的語句,語句塊的{或}應該和關鍵字寫在同一行,用空格隔開,而不是單獨佔一行。例如應該這樣寫:

if␣(...)␣{
       →語句列表
}␣else␣if␣(...)␣{
       →語句列表
}

但很多人習慣這樣寫:

if␣(...)
{
       →語句列表
}
else␣if␣(...)
{
       →語句列表
}

內核的寫法和[K&R]一致,好處是不必佔太多行,使得一屏能顯示更多代碼。這兩種寫法用得都很廣泛,只要在同一個項目中能保持統一就可以了。

3、函數定義的{和}單獨佔一行,這一點和語句塊的規定不同,例如:

int␣foo(int␣a,␣int␣b)
{
       →語句列表
}

4、switch和語句塊裏的casedefault對齊寫,也就是說語句塊裏的casedefault標號相對於switch不往裏縮進,但標號下的語句要往裏縮進。例如:

      →switch␣(c)␣{
      →case 'A':
      →       →語句列表
      →case 'B':
      →       →語句列表
      →default:
      →       →語句列表
      →}

用於goto語句的自定義標號應該頂頭寫不縮進,而不管標號下的語句縮進到第幾層。

5、代碼中每個邏輯段落之間應該用一個空行分隔開。例如每個函數定義之間應該插入一個空行,頭文件、全局變量定義和函數定義之間也應該插入空行,例如:

#include <stdio.h>
#include <stdlib.h>

int g;
double h;

int foo(void)
{
       →語句列表
}

int bar(int a)
{
       →語句列表
}

int main(void)
{
       →語句列表
}

6、一個函數的語句列表如果很長,也可以根據相關性分成若干組,用空行分隔。這條規定不是嚴格要求,通常把變量定義組成一組,後面加空行,return語句之前加空行,例如:

int main(void)
{
       →int    →a, b;
       →double →c;

       →語句組1

       →語句組2

       →return 0;
}

 

註釋

單行註釋應採用/*␣comment␣*/的形式,用空格把界定符和文字分開。多行註釋最常見的是這種形式:

/*
␣*␣Multi-line
␣*␣comment
␣*/

也有更花哨的形式:

/*************\
* Multi-line  *
* comment     *
\*************/

使用註釋的場合主要有以下幾種。

1、整個源文件的頂部註釋。說明此模塊的相關信息,例如文件名、作者和版本歷史等,頂頭寫不縮進。例如內核源代碼目錄下的kernel/sched.c文件的開頭:

/*
 *  kernel/sched.c
 *
 *  Kernel scheduler and related syscalls
 *
 *  Copyright (C) 1991-2002  Linus Torvalds
 *
 *  1996-12-23  Modified by Dave Grothe to fix bugs in semaphores and
 *              make semaphores SMP safe
 *  1998-11-19  Implemented schedule_timeout() and related stuff
 *              by Andrea Arcangeli
 *  2002-01-04  New ultra-scalable O(1) scheduler by Ingo Molnar:
 *              hybrid priority-list and round-robin design with
 *              an array-switch method of distributing timeslices
 *              and per-CPU runqueues.  Cleanups and useful suggestions
 *              by Davide Libenzi, preemptible kernel bits by Robert Love.
 *  2003-09-03  Interactivity tuning by Con Kolivas.
 *  2004-04-02  Scheduler domains code by Nick Piggin
 */

2、函數註釋。說明此函數的功能、參數、返回值、錯誤碼等,寫在函數定義上側,和此函數定義之間不留空行,頂頭寫不縮進。

3、相對獨立的語句組註釋。對這一組語句做特別說明,寫在語句組上側,和此語句組之間不留空行,與當前語句組的縮進一致。

4、代碼行右側的簡短註釋。對當前代碼行做特別說明,一般爲單行註釋,和代碼之間至少用一個空格隔開,一個源文件中所有的右側註釋最好能上下對齊。儘管例 2.1 “帶更多註釋的Hello World”講過註釋可以穿插在一行代碼中間,但不建議這麼寫。內核源代碼目錄下的lib/radix-tree.c文件中的一個函數包含了上述三種註釋:

/**
 *      radix_tree_insert    -    insert into a radix tree
 *      @root:          radix tree root
 *      @index:         index key
 *      @item:          item to insert
 *
 *      Insert an item into the radix tree at position @index.
 */
int radix_tree_insert(struct radix_tree_root *root,
                        unsigned long index, void *item)
{
        struct radix_tree_node *node = NULL, *slot;
        unsigned int height, shift;
        int offset;
        int error;

        /* Make sure the tree is high enough.  */
        if ((!index && !root->rnode) ||
                        index > radix_tree_maxindex(root->height)) {
                error = radix_tree_extend(root, index);
                if (error)
                        return error;
        }

        slot = root->rnode;
        height = root->height;
        shift = (height-1) * RADIX_TREE_MAP_SHIFT;

        offset = 0;                     /* uninitialised var warning */
        do {
                if (slot == NULL) {
                        /* Have to add a child node.  */
                        if (!(slot = radix_tree_node_alloc(root)))
                                return -ENOMEM;
                        if (node) {
                                node->slots[offset] = slot;
                                node->count++;
                        } else
                                root->rnode = slot;
                }

                /* Go a level down */
                offset = (index >> shift) & RADIX_TREE_MAP_MASK;
                node = slot;
                slot = node->slots[offset];
                shift -= RADIX_TREE_MAP_SHIFT;
                height--;
        } while (height > 0);

        if (slot != NULL)
                return -EEXIST;

        BUG_ON(!node);
        node->count++;
        node->slots[offset] = item;
        BUG_ON(tag_get(node, 0, offset));
        BUG_ON(tag_get(node, 1, offset));

        return 0;
}

[CodingStyle]中特別指出,函數內的註釋要儘可能少用。寫註釋主要是爲了說明你的代碼“能做什麼”(比如函數接口定義),而不是爲了說明“怎樣做”,只要代碼寫得足夠清晰,“怎樣做”是一目瞭然的,如果你需要用註釋才能解釋清楚,那就表示你的代碼可讀性很差,除非是特別需要提醒注意的地方纔使用函數內註釋。

5、複雜的結構體定義比函數更需要註釋。例如內核源代碼目錄下的kernel/sched.c文件中定義了這樣一個結構體:

/*
 * This is the main, per-CPU runqueue data structure.
 *
 * Locking rule: those places that want to lock multiple runqueues
 * (such as the load balancing or the thread migration code), lock
 * acquire operations must be ordered by ascending &runqueue.
 */
struct runqueue {
        spinlock_t lock;

        /*
         * nr_running and cpu_load should be in the same cacheline because
         * remote CPUs use both these fields when doing load calculation.
         */
        unsigned long nr_running;
#ifdef CONFIG_SMP
        unsigned long cpu_load[3];
#endif
        unsigned long long nr_switches;

        /*
         * This is part of a global counter where only the total sum
         * over all CPUs matters. A task can increase this counter on
         * one CPU and if it got migrated afterwards it may decrease
         * it on another CPU. Always updated under the runqueue lock:
         */
        unsigned long nr_uninterruptible;

        unsigned long expired_timestamp;
        unsigned long long timestamp_last_tick;
        task_t *curr, *idle;
        struct mm_struct *prev_mm;
        prio_array_t *active, *expired, arrays[2];
        int best_expired_prio;
        atomic_t nr_iowait;

#ifdef CONFIG_SMP
        struct sched_domain *sd;

        /* For active balancing */
        int active_balance;
        int push_cpu;

        task_t *migration_thread;
        struct list_head migration_queue;
        int cpu;
#endif

#ifdef CONFIG_SCHEDSTATS
        /* latency stats */
        struct sched_info rq_sched_info;

        /* sys_sched_yield() stats */
        unsigned long yld_exp_empty;
        unsigned long yld_act_empty;
        unsigned long yld_both_empty;
        unsigned long yld_cnt;

        /* schedule() stats */
        unsigned long sched_switch;
        unsigned long sched_cnt;
        unsigned long sched_goidle;

        /* try_to_wake_up() stats */
        unsigned long ttwu_cnt;
        unsigned long ttwu_local;
#endif
};

6、複雜的宏定義和變量聲明也需要註釋。例如內核源代碼目錄下的include/linux/jiffies.h文件中的定義:

/* TICK_USEC_TO_NSEC is the time between ticks in nsec assuming real ACTHZ and  */
/* a value TUSEC for TICK_USEC (can be set bij adjtimex)                */
#define TICK_USEC_TO_NSEC(TUSEC) (SH_DIV (TUSEC * USER_HZ * 1000, ACTHZ, 8))

/* some arch's have a small-data section that can be accessed register-relative
 * but that can only take up to, say, 4-byte variables. jiffies being part of
 * an 8-byte variable may not be correctly accessed unless we force the issue
 */
#define __jiffy_data  __attribute__((section(".data")))

/*
 * The 64-bit value is not volatile - you MUST NOT read it
 * without sampling the sequence number in xtime_lock.
 * get_jiffies_64() will do this for you as appropriate.
 */
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;

標誌符命名

  1. 標識符命名要清晰明瞭,可以使用完整的單詞和易於理解的縮寫。短的單詞可以通過去元音形成縮寫,較長的單詞可以取單詞的頭幾個字母形成縮寫。看別人的代碼看多了就可以總結出一些縮寫慣例,例如count寫成cntblock寫成blklength寫成lenwindow寫成winmessage寫成msgnumber寫成nrtemporary可以寫成temp,也可以進一步寫成tmp,最有意思的是internationalization寫成i18n,詞根trans經常縮寫成x,例如transmit寫成xmt。我就不多舉例了,請讀者在看代碼時自己注意總結和積累。

  2. 內核編碼風格規定變量、函數和類型採用全小寫加下劃線的方式命名,常量(比如宏定義和枚舉常量)採用全大寫加下劃線的方式命名,比如上一節舉例的函數名radix_tree_insert、類型名struct radix_tree_root、常量名RADIX_TREE_MAP_SHIFT等。

    微軟發明了一種變量命名法叫匈牙利命名法(Hungarian notation),在變量名中用前綴表示類型,例如iCnt(i表示int)、pMsg(p表示pointer)、lpszText(lpsz表示long pointer to a zero-ended string)等。Linus在[CodingStyle]中毫不客氣地諷刺了這種寫法:“Encoding the type of a function into the name (so-called Hungarian notation) is brain damaged - the compiler knows the types anyway and can check those, and it only confuses the programmer. No wonder MicroSoft makes buggy programs.”代碼風格本來就是一個很有爭議的問題,如果你接受本章介紹的內核編碼風格(也是本書所有範例代碼的風格),就不要使用大小寫混合的變量命名方式[19],更不要使用匈牙利命名法。

  3. 全局變量和全局函數的命名一定要詳細,不惜多用幾個單詞多寫幾個下劃線,例如函數名radix_tree_insert,因爲它們在整個項目的許多源文件中都會用到,必須讓使用者明確這個變量或函數是幹什麼用的。局部變量和只在一個源文件中調用的內部函數的命名可以簡略一些,但不能太短。儘量不要使用單個字母做變量名,只有一個例外:用ijk做循環變量是可以的。

  4. 針對中國程序員的一條特別規定:禁止用漢語拼音做標識符,可讀性極差。

函數

每個函數都應該設計得儘可能簡單,簡單的函數才容易維護。應遵循以下原則:

  1. 實現一個函數只是爲了做好一件事情,不要把函數設計成用途廣泛、面面俱到的,這樣的函數肯定會超長,而且往往不可重用,維護困難。

  2. 函數內部的縮進層次不宜過多,一般以少於4層爲宜。如果縮進層次太多就說明設計得太複雜了,應考慮分割成更小的函數(Helper Function)來調用。

  3. 函數不要寫得太長,建議在24行的標準終端上不超過兩屏,太長會造成閱讀困難,如果一個函數超過兩屏就應該考慮分割函數了。[CodingStyle]中特別說明,如果一個函數在概念上是簡單的,只是長度很長,這倒沒關係。例如函數由一個大的switch組成,其中有非常多的case,這是可以的,因爲各case分支互不影響,整個函數的複雜度只等於其中一個case的複雜度,這種情況很常見,例如TCP協議的狀態機實現。

  4. 執行函數就是執行一個動作,函數名通常應包含動詞,例如get_currentradix_tree_insert

  5. 比較重要的函數定義上側必須加註釋,說明此函數的功能、參數、返回值、錯誤碼等。

  6. 另一種度量函數複雜度的辦法是看有多少個局部變量,5到10個局部變量已經很多了,再多就很難維護了,應該考慮分割成多個函數。

indent工具

indent工具可以把代碼格式化成某種風格

-kr選項表示K&R風格,-i8表示縮進8個空格的長度。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章