C/C++ coding style 以及 clean code 教學 (基礎版)

Han-Ru Chen
7 min readMar 26, 2023

--

目標:

  1. 幫助初學者們(特別指沒被code review,只寫過能動的code的人),了解coding style 跟 clean code 的好處

筆者背景:

  1. Synolgoy RD Intern
  2. LeetCode 500 題目左右 (主要easy 跟 medium)
  3. LeetCode Contest Rank 前 25% (knight badges)
  4. 2022 年底有面過台灣GOOGLE SWE Intern (Rejected)

緣起:

1.到Synology RD Intern 後 真正地感受到 coding style 跟 clean code 的重要性,想記錄下來並且幫助到有需要的人

進入正題:
我希望大家能帶走的點

  1. 如果你在function 中發現需要編寫冗長的代碼來完成某個任務,那麼應將其封裝為一個獨立的function
  2. 利用好的變數和函數命名讓code更好讀
  3. 換行(new line) 的重要性

那我們就從 1 開始說起

以 LeetCode 752 題為例 (不需要會這題,請把重點著重在格式上)

這是沒有使用 將 冗長的代碼 封裝成獨立的function 的樣子

class Solution {
public:
int openLock(vector<string>& deadends, string target) {
unordered_map<string, int> dead;
unordered_map<string, int> vis;

for (auto &s: deadends)
dead[s]++;

if(dead["0000"]) return -1;
// bfs
queue<string> q;
q.push("0000");
int step = 0;
while (!q.empty()) {
int size = q.size();
for (int i = 0; i < size; ++i) {
string pwd = q.front(); q.pop();
if (pwd == target) return step;
for (int i = 0; i < 4; ++i){
char org = pwd[i];
// get front
// get back
char front = (pwd[i] == '9') ? '0' : org+1;
char back = (pwd[i] == '0') ? '9' : org-1;

pwd[i] = front;
if (!dead[pwd] && !vis[pwd]) {
q.push(pwd);
vis[pwd] = 1;
}
pwd[i] = back;
if (!dead[pwd] && !vis[pwd]) {
q.push(pwd);
vis[pwd] = 1;
}
pwd[i] = org;
}
}
step++;
}

return -1;
}
};

這是有這樣做的樣子

class Solution {
public:
int openLock(vector<string>& deadends, string target) {
recDeadEnds(deadends);

if (initImpossible()) {
return -1;
}

return bfs(target);
}

void recDeadEnds(vector<string>& deadends) {
for (auto &s: deadends)
block[s]++;
}

bool initImpossible() {
return block.find("0000") != block.end();
}

int bfs(string &target) {
queue<string> q;

q.push("0000");
int step = 0;
while (!q.empty()) {
int size = q.size();
for (int i = 0; i < size; ++i) {
string pwd = q.front(); q.pop();
if (pwd == target) return step;
insertNewPWD(pwd, q);
}
step++;
}

return -1;
}

void insertNewPWD(string &pwd, queue<string> &q) {
for (int i = 0; i < 4; ++i) {
char org = pwd[i];
// front case
pwd[i] = (org == '9') ? '0' : org + 1;
if (!block[pwd] && !vis[pwd]) {
q.push(pwd);
vis[pwd] = 1;
}
// back case
pwd[i] = (org == '0') ? '9' : org - 1;
if (!block[pwd] && !vis[pwd]) {
q.push(pwd);
vis[pwd] = 1;
}
pwd[i] = org;
}
}

private:
unordered_map<string, int> block;
unordered_map<string, int> vis;

};

看第一個例子我們會發現code 需要花時間讀懂,頂多只會注意到有使用bfs

相較之下看第二個例子我們會清楚看到程式碼的架構

recDeadEnds()

initImpossible()

bfs()
...insertNewPWD()

依序執行 recDeadEnds(), initImpossible(), bfs() 這三個 function,

而bfs()裡面有可能會呼叫insertNewPWD()

基本上好讀非常多

接下來我們可以參考上面的例子來感受命名的好壞

對我來說命名的目標是

讓第一次看到這份code的人能夠看懂8成在幹嘛

在這邊大家可以先參考這篇文章

而就我個人經驗,我看過最多人使用的是

第一個字母小寫的駝峰式命名

在看到一些JavaScript的教學中很常看到

而在C++的業界中也很常看到 推薦給大家

而基本上命名其實可以舉出很多細節的例子,也可以探討很多地方

之後會獨立發一篇文章探討

接下來我們要探討 換行(new line) 的重要性

舉這個例子來說

int openLock(vector<string>& deadends, string target) {
recDeadEnds(deadends);

if (initImpossible()) {
return -1;
}

return bfs(target);
}

我在recDeadEnds(), initImpossible() 跟 bfs() 三個function間隔中

都有刻意換行

原因是因為我想表達我在這個函數中

呼叫了三個function

做了三件事情

如果今天我要在一個超過百行的程式碼debug時

藉由清楚地把架構寫出來

能夠更清晰地去分析問題出在哪

這是很重要的細節

當有一天你遇到超過百行的code時

你就可以明顯感受到這樣分行的好處

搭配 1 的 技巧

它會讓你很方便地去debug 思路

而當你遇到上千行以上的程式碼時

這樣做已經是一件必須的事情了

最後在幫大家做一個重點複習

以後寫程式記得

  1. 遇到冗長的code 包成 function
  2. 多花時間用心想怎麼命名
  3. 善用換行表達自己的思路

如果這篇文章有幫到你的話

請幫我claps 加分享給你認為可以受惠的朋友們

也歡迎來到我的Medium文章列表

以及Youtube頻道逛逛

謝謝大家的觀看

參考資料:

常見重點整理 — 命名慣例 & 開發時注意事項 — HackMD

https://leetcode.com/

在Synology 開發的自己跟前輩們交流以及自己悟出的經驗

ChatGPT 幫忙潤飾句子

--

--

Han-Ru Chen
Han-Ru Chen

Written by Han-Ru Chen

如果能夠重新投胎選擇任一人的人生,我想選擇我自己。 \ linktr: https://linktr.ee/future_outlier\ ted talk: https://youtu.be/aV-Pvb-qmC0?si=lvCFpZde5erQH-wZ \

No responses yet