---
# System prepended metadata

title: Jserv老師-課程的coding style

---

# Jserv老師-課程的coding style
### clang-format 工具和一致的程式撰寫風格

使用一致的 [programming style](https://en.wikipedia.org/wiki/Programming_style) 很重要，我們可透過 [clang-format](https://clang.llvm.org/docs/ClangFormat.html) 這個工具來調整作業程式要求的風格，使用方式如下:
```shell
$ clang-format -i *.[ch]
```

課程要求的 C 程式撰寫風格簡述:
* 使用 ==4 個空白字元==來進行縮排，不用 Tab;
    - 為何不比照 Linux 核心都用 tab 呢？
    - 首先是為了 code review 的便利，4 個空白字元可避免程式碼過寬，從而易於課堂討論，且和共筆系統 (這裡指 [HackMD](https://hackmd.io/)) 預設的縮排方式相符。
    - 再者是考慮到不同的編輯器 (editor) 對於 tab 行為有落差，而且 [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) 也建議使用空白字元而非 tab，這樣的風格被許多開放原始碼專案所採納;
* 將單一行程式碼寬度控制在 80 個字元內
    - 任何一行超過 80 列寬度的敘述都該拆分成多個行
* switch 敘述的縮排方式是讓 case 與 switch 對齊，範例:
    ```cpp
        switch (c) {
        case 'h':
            usage(argv[0]);
            break;
    ```
* 延續 [K&R C](https://en.wikipedia.org/wiki/The_C_Programming_Language) 風格去處理大括號 (`{ }` 英語: curly brackets， 又稱花括號) 與空格
    - Kernighan 和 Ritchie 在撰寫《[The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language)》一書所用的程式範例，是把左大括號 `{` 放在行末，把右大括號 `}` 放在行首，如:
    ```cpp
    if (x == true) {
        do_y();
    } else {
        do_z();
    }
    do {
        /* body of do-loop */
    } while (condition);
    ```
    - 然而有個特殊的例子，就是函式：函式的左大括號應該放在行首，如：
    ```cpp
    int function(int x)
    {
        body of function
    }
    ```
* 在不降低可讀性的前提下，儘可能減少空行的數量。無用的換行一旦減少，那就有更多的空行來寫註解。
* Linux 核心風格對於空格，體現於一些關鍵字的運用，即在關鍵字之後增添一個空格。值得關注的例外是一些長得像函式的關鍵字，如: `sizeof`, `typeof`, `alignof`, `__attribute__` (註: 這是 [gcc 延展的功能](https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html))，在 Linux 核心中，這些關鍵字的使用都會帶上一對括號，儘管在 C 語言的使用上並不需帶上括號
    * `sizeof` 不是函式，而是 operator!
* 在這些關鍵字之後新增一個空格: `if`, `switch`, `case`, `for`, `do`, `while`
    - 但不要新增在 `sizeof`, `typeof`, `alignof`, `__attribute__` 之後
    - 期望見到的用法是
    ```cpp
    s = sizeof(struct file);
    ```
* 不要在括號周圍多此一舉的新增空格
    - 下面這個例子糟透了
    ```cpp
    s = sizeof( struct file );
    ```
* 在宣告指標或者返回值為指標的函式時，指標 (即 `*` 符號) 的位置應該緊靠著變數名稱或函式名稱，而非型別名稱，例如：
    ```cpp
    char *linux_banner;
    unsigned long long memparse(char *ptr, char **retptr);
    char *match_strdup(substring_t *s);
    ```
* 在二元操作符和三元操作符周圍新增一個空格
    - 例如: `= + - < > * / % | & ^ <= >= == != ? :`
    - 但不要在一元操作符之後新增空格: `&*+-~!` `sizeof` `typeof` `alignof` `__attribute__` `defined`
    - 不要在後綴的 `++` `--` 運算子之前新增空格 (即 `i++`)
    - 不要在開頭的 `++` `--`  之後新增空格 (即 `++i`)
    - 不要在結構體成員運算子 (即 `.` 和 `->`) 周圍新增空格
* 不要在行末新增多餘的空格
    - 某些編輯器的「智慧」縮排會幫你在行首新增一些空格，好讓你在下一行可以立即撰寫程式碼。但某些編輯器不會幫你把多餘的空格給刪掉，儘管你已經寫完了一行程式碼。比如你只想留一行空行，但是編輯器卻「好心」地幫你填上了一些空格。這樣一來，你就在行末添加多餘的空格。
* 變數和函式命名力求簡潔且精準
    - C 是種==簡潔粗曠==的語言，因此命名也該簡潔
    - C 程式設計師不會像 Pascal 程式設計師那樣使用 `ThisVariableIsATemporaryCounter` 這種「可愛」的名字，相反地，一個 C 程式設計師會把這種變數命名為 `tmp`，如此簡潔易寫。
    - 全域變數 (只有當你真正需要的時候才用它) 和全域函式 (也就是沒有用 `static` 宣告的函式) 需要使用描述性的名稱。若你有個計算活躍使用者數量的函式，你應該用 count_active_users() 一類的名稱，避免用 `cntusr()` 這樣不易望文生義的名稱。
    - 起一個包含函式型別的名字([匈牙利命名法](https://en.wikipedia.org/wiki/Hungarian_notation))是摧殘大腦的行為，編譯器知道函式的型別並且會檢查型別，這樣的名字不會起到任何幫助，它僅僅會迷惑程式設計師。
    - 區域變數名應該簡短，若你需要寫一個迴圈，定義一個計數器，在不產生歧義的情況下，你大可命名為 `i`。反過來說，命名為 `loop_counter` 是生產力很低的行為。同樣地，`tmp` 可以是任何型別的區域變數。

:::info
:-1: 你可以想像 Apple 和 Google 的工程師隨便安置程式碼，然後不用管合作的議題嗎？
:balloon: 「[Linux 核心設計](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程希望引導學員最終能夠欣然面對 Linux 核心或者有一定規模的軟體專案，不再只是「[舉燭](http://dict.revised.moe.edu.tw/cgi-bin/cbdic/gsweb.cgi?ccd=UOmTQ4&o=e0&sec1=1&op=sid=%22Z00000158282%22.&v=-2)」，而是真正和世界各地的高手協作，上述看似繁雜的程式開發風格就會是相當基本且該貫徹的基本素養。
:+1: 或許你會反問：「只是一個作業，有必要這樣自虐嗎？」不妨這樣想：即便一個人寫作業，其實是三人的參與 —— 過去的你、現在的你，以及未來的你
:::

### [Git Hooks](https://www.atlassian.com/git/tutorials/git-hooks) 進行自動程式碼排版檢查

首次執行 `make` 後，Git pre-commit / pre-push hook 將自動安裝到現行的工作區 (workspace)，之後每次執行 `git commit` 時，Git hook 會檢查 C/C++ 原始程式碼的風格是否一致，並透過  [Cppcheck](http://cppcheck.sourceforge.net/) 進行靜態程式碼檢查。

:::warning
:warning: 任何人都可以寫出機器看得懂的程式碼 (在 Windows 檔案總管裡面，隨便選一個 EXE 檔，按右鍵複製，隨後再貼上即可)，但我們之所以到資訊工程系接受訓練，為了寫出人看得懂、可持續維護和改進的程式
:::

下圖展示 Git pre-commit hook 偵測到開發者的修改並未遵守一致的 coding style，主動回報並提醒開發者: 
![](https://i.imgur.com/g7DQtYF.png)

紅色標注的二行程式碼 (即 `int member1;` 和 `int member2;`) 不符合指定的 4 個空白縮排方式，在 git pre-commit 階段就成功阻擋這樣風格不一致的程式碼變更。

### 撰寫 Git Commit Message 和自動檢查機制

![](https://i.imgur.com/stK5oBN.png)

Git commit message 是什麼呢？在取得 [lab0-c](https://github.com/sysprog21/lab0-c) 程式碼後，執行 `$ git log` 的輸出就是了。你或許會納悶，commit message 又不是程式碼，充其量只能算是「程式開發的軌跡」，為何要特別探討？

:::info
:notebook: 可安裝 `tig` 套件，更便利地瀏覽 git repository 資訊。
安裝方式: `$ sudo apt install tig`
參考執行畫面如下:
![](https://i.imgur.com/pMRybxY.png)
:::

Peter Hutterer 在 [On commit messages](https://who-t.blogspot.com/2009/12/on-commit-messages.html) 說得很精闢：
> 「重新了解一段程式碼更動的脈絡很浪費腦力。雖然這件事情沒辦法完全避免，但是我們可以盡量[降低](https://www.osnews.com/story/19266/wtfsm/)這件事情的複雜度。Commit messages 正可以做到這點，而**我們可以從 commit message 看出一個開發者是否為一位好的合作對象**。」

一個專案是否能長期且成功地運作 (撇除其他影響的因素)，取決於它的可維護性，而在這件事上沒有其他工具比專案本身的開發軌跡更為強大。因此花時間學習如何撰寫與維護專案的開發紀錄，是件值得投資的事。一開始你可能會覺得麻煩，但它很快就會變成一種習慣，甚至能成為你之所以感到自豪及具備生產力的因素。

Chris Beams 在 [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) 一文 (繁體中文翻譯: [如何寫一個 Git Commit Message](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/)) 提出，好的 Git commit message 應符合七條規則:
1. 用一行空白行分隔標題與內容
2. 限制標題最多只有 50 字元
3. 標題開頭要大寫
4. 標題不以句點結尾
5. 以祈使句撰寫標題
6. 內文每行最多 72 字
7. 用內文解釋 what 以及 why vs. how

[lab0-c](https://github.com/sysprog21/lab0-c) 內建 Git pre-commit hook 就包含依循上述七條規則的自動檢查，可阻擋不符合規則的 git commit message，此外還透過 [GNU Aspell](http://aspell.net/) 對 commit message 的標題進行拼字檢查。

:::info
:warning: 既然我們著眼於 Linux 核心，當然要用英語撰寫 commit message，英語不該只是大學畢業門檻，更該要活用，「[Linux 核心設計](http://wiki.csie.ncku.edu.tw/linux/schedule)」課程還兼英語寫作的功效，真划算！
:::

以下實際操作 Git。
事先編輯檔案 `queue.h` 後，執行 `$ git commit` 會發現:
```shell
$ git commit
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
        modified:   queue.h

no changes added to commit
```

原來要指定變更的檔案，用命令 `$ git add`:
```shell
$ git add queue.h
```

:::success
:notebook: 可改用 `$ git commit -a` 這道命令，讓 Git 自動追蹤變更的檔案並提交
:::


重新 `git commit`，這時可發現安裝的 Git hooks 發揮作用:
```shell=
$ git commit -m "add fields to point to the tail"
add fields to point to the tail                                            [line 1]
 - Capitalize the subject line
Proceed with commit? [e/n/?] e
add fields to point to the tail                                            [line 1]
 - Capitalize the subject line
Proceed with commit? [e/n/?] y
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] ?
How to Write a Git Commit Message: https://chris.beams.io/posts/git-commit/
e - edit commit message
n - abort commit
? - print help
Proceed with commit? [e/n/?] e
[master 23f7113] Add fields to point to the tail
```

Git pre-commit hook 提示我們:
* 第 4 行輸入 `e`，可編輯 git commit message;
* 第 7 行輸入 `y`，因為 y 不是有效選項, 所以出現 help;
* 第 17 行輸入 `e`, 再次編輯訊息，因訊息有限制標題開頭要大寫;

注意：請避免用 `$ git commit -m`，而是透過編輯器調整 git commit message。許多網路教學為了行文便利，用 `$ git commit -m` 示範，但這樣很容易讓人留下語焉不詳的訊息，未能提升為 [好的 Git Commit Message](https://blog.louie.lu/2017/03/21/%E5%A6%82%E4%BD%95%E5%AF%AB%E4%B8%80%E5%80%8B-git-commit-message/)。因此，從今以後，不要用 `git commit -m`, 改用 `git commit -a` (或其他參數) 並詳細查驗變更的檔案。

在 Ubuntu Linux 預設組態中，執行 `$ git commit -a` 會呼叫 [GNU nano](https://www.nano-editor.org/) 來編輯訊息，可透過變更 `EDITOR` 環境變數，讓 git 找到你偏好的編輯器，例如指定 vim 就可以這樣做:
```shell
export EDITOR=vim
```

你也可把上述變更加到 bash 設定檔案中，例如:
```shell
$ echo "export EDITOR=vim" >> ~/.bashrc
```

一番折騰後，終於順利提交 (commit) 我們的變更:

``` shell
$ git commit -a
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
nothing to commit, working tree clean
```

需要注意的是，目前提交的變更**僅存在於這台電腦中**，還未發佈到 GitHub 上，於是我們需要執行 `git push`
> 下方的 `jserv` 是示範帳號，請換為你自己的 GitHub 帳號名稱
```shell
$ git push
Username for 'https://github.com': jserv
Password for 'https://jserv@github.com':
Hint: You might want to know why Git is always asking for my password.
      https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password

Running pre push to master check...
Trying to build tests project...
Pre-push check passed!

Counting objects: 4, done.
Delta compression using up to 64 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 709 bytes | 709.00 KiB/s, done.
Total 4 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/sysprog21/lab0-c
   d2d7711..b42797a  master -> master
```

不難發現上面訊息提到 [Why is Git always asking for my password?](https://help.github.com/en/github/using-git/why-is-git-always-asking-for-my-password) 這個超連結，如果你厭倦每次 `$ git push` 都需要輸入帳號及密碼，可參考超連結內容去變更設定。

又因 pre-push hook 使然，每次發布變更到 GitHub 服務時，只要是 `master` branch (主要分支) 就會觸發編譯要求，只有通過編譯的程式碼才能發布，但 `master` 以外的 branch 不受此限，換言之，我們鼓勵學員運用 `git branch` 進行各項實驗，請參照線上中文教材 ==《[為你自己學 Git](https://gitbook.tw/)》==。

### 牛刀小試

若你很有耐心地讀到這裡，恭喜你，終於可著手修改 [lab0-c](https://github.com/sysprog21/lab0-c) 程式碼。

這裡我們嘗試實作函式 `q_size`，首先找出 [`queue.c`](https://github.com/sysprog21/lab0-c/blob/master/queue.c) 檔案中對應的註解:
> Return number of elements in queue.
> Return 0 if q is NULL or empty
> Remember: It should operate in **$O(1)$** time

`q_size(queue_t *)` 由於要在 $O(1)$ 的常數時間內執行完成，所以也不可能每次都走訪整個鏈結串列，以取得佇列的容積。又在  [`queue.h`](https://github.com/sysprog21/lab0-c/blob/master/queue.h) 的註解，我們得知:
> You will need to add more fields to this structure to efficiently implement `q_size` and `q_insert_tail`

因此，我們嘗試新增 **int size** 這個新成員到 `struct queue_t`:
```cpp
typedef struct {
    list_ele_t *head; /* Linked list of elements */
    int size;
} queue_t;
```

接著修改 `q_size` 的傳回值，改成傳回 `q->size`。一切就緒後，提交修改:
```cpp
$ git commit -m "Change q_size return value to q->size"
--- modified queue.c
+++ expected coding style
@@ -70,7 +70,7 @@ bool q_insert_tail(queue_t *q, char *s)
 {
     /* You need to write the complete code for this function */
     /* Remember: It should operate in O(1) time */
-    list_ele_t *newt;    // newt means new tail
+    list_ele_t *newt;  // newt means new tail
     newt = malloc(sizeof(list_ele_t));
     q->tail = newt;
     return true;
[!] queue.c does not follow the consistent coding style.

Make sure you indent as the following:
    clang-format -i queue.c
```

Git pre-commit hook 偵測到程式縮排不符合前述的風格，因而擋住我們的提交，我們要執行 `$ clang-format -i queue.c` 去調整程式縮排，才得以繼續提交。