一、總述
每個人都可能有自己的代碼風格和格式,但如果一個項目中的所有人都遵循同一風格的話,這個項目就能更順利地進行。每個人未必能同意下述的每一處格式規則,而且其中的不少規則需要一定時間的適應,但整個項目服從統一的編程風格是很重要的,只有這樣才能讓所有人輕鬆地閱讀和理解代碼。
二、行
2.1 行長度
每一行代碼字符數不超過 120
註釋行可以超過 120
個字符,但最大不超過 150
2.2 行寬度
函數體行寬原則上不超過 80
列
80
行限制事實上有助於避免代碼可讀性失控,比如超多重嵌套塊,超多重函數調用等等。
三、非ASCII字符
儘量不使用非 ASCII
字符,使用時必須使用 UTF-8
編碼。
即使是英文,也不應將用戶界面的文本硬編碼到源代碼中,因此非 ASCII 字符應當很少被用到。特殊情況下可以適當包含此類字符。例如,代碼分析外部數據文件時,可以適當硬編碼數據文件中作爲分隔符的非 ASCII 字符串;更常見的是 (不需要本地化的) 單元測試代碼可能包含非 ASCII 字符串。此類情況下,應使用 UTF-8 編碼,因爲很多工具都可以理解和處理 UTF-8
編碼。
四、空格還是製表位
只使用空格,每次縮進 4
個空格。(使用記事本打開檢查是否使用空格還是製表符)
如果要在代碼中使用製表符。你應該設置編輯器將製表符轉爲空格。
五、函數聲明與定義
返回類型和函數名在同一行,參數也儘量放在同一行,如果放不下就對形參分行,分行方式如下 六、函數調用
一致。
函數看上去像這樣:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2)
{
DoSomething(); // 4 space indent
...
}
如果同一行文本太多,放不下所有參數:
ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2,
Type par_name3)
{
DoSomething(); // 4 space indent
...
}
甚至連第一個參數都放不下:
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(
Type par_name1, // 8 space indent
Type par_name2,
Type par_name3)
{
DoSomething(); // 4 space indent
...
}
注意以下幾點:
- 使用好的參數名。
- 只有在參數未被使用或者其用途非常明顯時,才能省略參數名。
- 如果返回類型和函數名在一行放不下,分行。
- 如果返回類型與函數聲明或定義分行了,不要縮進。
- 左圓括號總是和函數名在同一行。
- 函數名和左圓括號間永遠沒有空格。
- 圓括號與參數間沒有空格。
- 左大括號
另起新行
。- 右大括號總是單獨位於函數
最後一行
。- 所有形參應儘可能對齊。
- 缺省縮進爲 4 個空格。
- 換行後的參數保持 8 個空格的縮進。或儘量對齊上一行參數。
屬性,和展開爲屬性的宏,寫在函數聲明或定義的最前面,即返回類型之前:
MUST_USE_RESULT bool IsOK();
六、函數調用
要麼一行寫完函數調用,要麼在圓括號裏對參數分行,要麼參數另起一行且縮進 8
格。如果沒有其它顧慮的話,儘可能精簡行數,比如把多個參數適當地放在同一行裏。
函數調用遵循如下形式:
bool retval = DoSomething(argument1, argument2, argument3);
如果同一行放不下,可斷爲多行,後面每一行都和第一個實參對齊,左圓括號後和右圓括號前不要留空格:
bool retval = DoSomething(averyveryveryverylongargument1,
argument2, argument3);
參數也可以放在次行,縮進8格:
if(...)
{
DoSomething(
argument1, argument2, // 8 空格縮進
argument3, argument4);
}
七、Lambda 表達式
Lambda 表達式對形參和函數體的格式化和其他函數一致;捕獲列表同理,表項用逗號隔開。
若用引用捕獲,在變量名和 &
之間不留空格。
int x = 0;
auto add_to_x = [&x](int n) { x += n; };
短 lambda 就寫得和內聯函數一樣。
std::set<int> blacklist = {7, 8, 9};
std::vector<int> digits = {3, 9, 1, 8, 4, 7, 1};
digits.erase(std::remove_if(digits.begin(), digits.end(), [&blacklist](int i)
{
return blacklist.find(i) != blacklist.end();
}), digits.end());
八、列表初始化格式
如果列表初始化伴隨着名字,比如類型或變量名,格式化時將將名字視作函數調用名,{} 視作函數調用的括號。如果沒有名字,就視作名字長度爲零。
// 一行列表初始化示範.
return {foo, bar};
functioncall({foo, bar});
pair<int, int> p{foo, bar};
// 當不得不斷行時.
SomeFunction(
{"assume a zero-length name before {"}, // 假設在 { 前有長度爲零的名字.
some_other_function_parameter);
SomeType variable
{
some, other, values,
{"assume a zero-length name before {"}, // 假設在 { 前有長度爲零的名字.
SomeOtherType
{
"Very long string requiring the surrounding breaks.", // 非常長的字符串, 前後都需要斷行.
some, other values
},
SomeOtherType
{
"Slightly shorter string",
some, other, values
}
};
SomeType variable
{
"This is too long to fit all in one line"
};
MyType m =
{
superlongvariablename1,
superlongvariablename2,
{short, interior, list},
{
interiorwrappinglist,
interiorwrappinglist2
}
};
九、條件語句
9.1 if語句格式
if(condition) // '('左邊和後邊都沒有空格,')'左邊沒有空格
{ // '{'換行
... // 4 空格縮進
}
else // else 換行
{
...
}
如果能增強可讀性,簡短的條件語句允許寫在同一行。只有當語句簡單並且沒有使用 else
子句時使用:
if(x == kFoo) return new Foo();
if(x == kBar) return new Bar();
但最好還是使用大括號:
if(x == kFoo)
{
return new Foo();
}
如果語句有 else
分支則不允許:
// 不允許 - 當有 ELSE 分支時 IF 塊卻寫在同一行
if (x) DoThis();
else DoThat();
9.2 布爾變量與零值比較
不可將布爾變量直接與 TRUE、FALSE 或者 1、0 進行比較。
根據布爾類型的語義,零值爲“假”(記爲 FALSE),任何非零值都是“真”(記爲TRUE)。TRUE 的值究竟是什麼並沒有統一的標準。例如 Visual C++ 將 TRUE 定義爲1,而 Visual Basic 則將 TRUE 定義爲 -1。
假設布爾變量名字爲 flag,它與零值比較的標準 if 語句如下:
if (flag) // 表示 flag 爲真
if (!flag) // 表示 flag 爲假
其它的用法都屬於不良風格,例如:
if (flag == TRUE)
if (flag == 1)
if (flag == FALSE)
if (flag == 0)
9.3 浮點變量與零值比較
不可將浮點變量用“==”或“!=”與任何數字比較。
千萬要留意,無論是 float 還是 double 類型的變量,都有精度限制。所以一定要避
免將浮點變量用“==”或“!=”與數字比較,應該設法轉化成“>=”或“<=”形式。
假設浮點變量的名字爲 x,應當將
if(x == 0.0) // 隱含錯誤的比較
轉化爲
if((x>=-EPSINON) && (x<=EPSINON))
其中 EPSINON 是允許的誤差(即精度)。
9.4 指針變量與零值比較
應當將指針變量用“==”或“!=”與 NULL 比較。
指針變量的零值是“空”(記爲 NULL)。儘管 NULL 的值與 0 相同,但是兩者意義不
同。假設指針變量的名字爲 p,它與零值比較的標準 if 語句如下:
if(p == NULL) // p 與 NULL 顯式比較,強調 p 是指針變量
if(p != NULL)
不要寫成
if(p == 0) // 容易讓人誤解 p 是整型變量
if (p != 0)
或者
if(p) // 容易讓人誤解 p 是布爾變量
if(!p)
十、循環和開關選擇語句
switch
語句可以使用大括號分段,以表明 cases 之間不是連在一起的。在單語句循環裏,括號可用可不用。空循環體應使用 {}
或 continue
。
switch
語句中的 case
塊可以使用大括號也可以不用,取決於你的個人喜好。如果用的話,要按照下文所述的方法。
如果有不滿足 case
條件的枚舉值,switch
應該總是包含一個 default
匹配 (如果有輸入值沒有 case 去處理, 編譯器將給出 warning)。如果 default
應該永遠執行不到,簡單的加條 assert
:
switch(var) // '('左邊和右邊沒有空格,')'左邊沒有空格
{
case 0: // 無空格
{
... // 8 空格縮進
break;
}
case 1:
{
...
break;
}
default:
assert(false);
}
循環語句:
for(int i = 0; i < kSomeNumber; ++i)
{
printf("I take it back\n");
}
空循環體應使用 {}
或 continue
,而不是一個簡單的分號。
while(condition)
{
// 反覆循環直到條件失效.
}
for(int i = 0; i < kSomeNumber; ++i) // 可 - 空循環體.
{
}
while(condition) continue; // 可 - contunue 表明沒有邏輯.
while(condition); // 差 - 看起來僅僅只是 while/loop 的部分之一.
十一、指針和引用表達式
句點或箭頭前後不要有空格。指針/地址操作符 (*, &
) 之後不能有空格。
下面是指針和引用表達式的正確使用範例:
x = *p;
p = &x;
x = r.y;
x = r->y;
注意:
- 在訪問成員時,句點或箭頭前後沒有空格。
- 指針操作符
*
或&
後沒有空格。
在聲明指針變量或參數時,星號與類型或變量名緊挨都可以:
// 好, 空格前置,推薦使用
char *c;
const string &str;
// 好, 空格後置.
char* c;
const string& str;
int x, *y; // 不允許 - 在多重聲明中不能使用 & 或 *
char * c; // 差 - * 兩邊都有空格
const string & str; // 差 - & 兩邊都有空格.
在單個文件內要保持風格一致,所以,如果是修改現有文件,要遵照該文件的風格。
十二、布爾表達式
如果一個布爾表達式超過 標準行寬 120個字符
,斷行方式要統一一下。
下例中,邏輯與 (&&
) 操作符總位於行尾:
if(this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
yet_another && last_one)
{
...
}
注意,上例的邏輯與 (&&
) 操作符均位於行尾。
十三、函數返回值
不要在 return
表達式里加上非必須的圓括號。
只有在寫 x = expr
要加上括號的時候纔在 return expr
;裏使用括號。
return result; // 返回值很簡單, 沒有圓括號.
// 可以用圓括號把複雜表達式圈起來, 改善可讀性.
return (some_long_condition &&
another_condition);
return (value); // 差 - 畢竟您從來不會寫 var = (value);
return(result); // 差 - return 可不是函數!
十四、預處理指令
預處理指令不要縮進,從行首開始。
即使預處理指令位於縮進代碼塊中,指令也應從行首開始。
// 好 - 指令從行首開始
if(lopsided_score)
{
#if DISASTER_PENDING // 正確 - 從行首開始
DropEverything();
# if NOTIFY // 非必要 - # 後跟空格
NotifyClient();
# endif
#endif
BackToNormal();
}
// 差 - 指令縮進
if (lopsided_score)
{
#if DISASTER_PENDING // 差 - "#if" 應該放在行開頭
DropEverything();
#endif // 差 - "#endif" 不要縮進
BackToNormal();
}
十五、類格式
訪問控制塊的聲明依次序是 public:
,protected:
,private:
,每個都縮進 2 個空格。
class MyClass : public OtherClass
{
public: // 注意有2個空格的縮進
MyClass(); // 標準的4空格縮進
explicit MyClass(int var);
~MyClass() {}
void SomeFunction();
void SomeFunctionThatDoesNothing() {}
void set_some_var(int var) { some_var_ = var; }
int some_var() const { return some_var_; }
private:
bool SomeInternalFunction();
int some_var_;
int some_other_var_;
};
注意事項:
- 所有基類名應在 80 列限制下儘量與子類名放在同一行。
- 關鍵詞
public:
,protected:
,private:
要縮進 1 個空格。 - 除第一個關鍵詞 (一般是
public
) 外,其他關鍵詞前要空一行。如果類比較小的話也可以不空。 - 這些關鍵詞後不要保留空行。
public
放在最前面,然後是protected
,最後是private
。
十六、構造函數初始值列表
構造函數初始化列表放在同一行或按8格縮進並排多行。
// 如果所有變量能放在同一行:
MyClass::MyClass(int var) : some_var_(var)
{
DoSomething();
}
// 如果不能放在同一行,
// 必須置於冒號後, 並縮進 4 個空格
MyClass::MyClass(int var)
: some_var_(var), some_other_var_(var + 1)
{
DoSomething();
}
// 如果初始化列表需要置於多行, 將每一個成員放在單獨的一行
// 並逐行對齊
MyClass::MyClass(int var)
: some_var_(var), // 8 space indent
some_other_var_(var + 1)
{
DoSomething();
}
// 右大括號 } 可以和左大括號 { 放在同一行
// 如果這樣做合適的話
MyClass::MyClass(int var)
: some_var_(var) {}
十七、命名空間格式化
命名空間內容不縮進。
命名空間不要增加額外的縮進層次,例如:
namespace
{
void foo() // 正確. 命名空間內沒有額外的縮進.
{
...
}
} // namespace
不要在命名空間內縮進:
namespace
{
// 錯, 縮進多餘了.
void foo()
{
...
}
} // namespace
聲明嵌套命名空間時,每個命名空間都獨立成行。
namespace foo {
namespace bar {
十八、水平留白
18.1 通用
水平留白的使用根據在代碼中的位置決定。永遠不要在行尾添加沒意義的留白。
void f(bool b)
{
...
int i = 0; // 分號前不加空格.
// 列表初始化中大括號內的空格是可選的.
// 如果加了空格, 那麼兩邊都要加上.
int x[] = { 0 };
int x[] = {0};
// 繼承與初始化列表中的冒號前後恆有空格.
class Foo : public Bar
{
public:
// 對於單行函數的實現, 在大括號內加上空格
// 然後是函數實現
Foo(int b) : Bar(), baz_(b) {} // 大括號裏面是空的話, 不加空格.
void Reset() { baz_ = 0; } // 用括號把大括號與實現分開.
...
18.2 循環和條件語句
if(b) // 圓括號內部不緊鄰空格.
{
}
else
{
}
while(test) {}
switch(i)
{
for(int i = 0; i < 5; ++i)
{
switch(i)
{
if(test)
{
for(int i = 0; i < 5; ++i)
{
for( ; i < 5 ; ++i) // 循環裏內 ; 後恆有空格, ; 前可以加個空格.
{
switch(i)
{
case 1: // switch case 的冒號前無空格.
...
case 2:
break;
18.3 操作符
// 賦值運算符前後總是有空格.
x = 0;
// 其它二元操作符也前後恆有空格, 不過對於表達式的子式可以不加空格.
// 圓括號內部沒有緊鄰空格.
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);
// 在參數和一元操作符之間不加空格.
x = -5;
++x;
if(x && !y)
...
18.4 模板和轉換
// 尖括號(< and >) 不與空格緊鄰, < 前沒有空格, > 和 ( 之間也沒有.
vector<string> x;
y = static_cast<char *>(x);
// 在類型與指針操作符之間留空格也可以, 但要保持一致.
vector<char *> x;
十九、垂直留白
這不僅僅是規則而是原則問題了:不在萬不得已,不要使用空行。尤其是:兩個函數定義之間的空行不要超過 2 行,函數體首尾不要留空行。函數體中也不要隨意添加空行。
基本原則是:同一屏可以顯示的代碼越多,越容易理解程序的控制流。當然,過於密集的代碼塊和過於疏鬆的代碼塊同樣難看,這取決於你的判斷。但通常是垂直留白越少越好。
下面的規則可以讓加入的空行更有效:
- 用於區別概念層次,如函數體內變量定義與調用函數用空行區分開。
- 在多重 if-else 塊里加空行或許有點可讀性。
19.1 概念間垂直方向上的區隔
幾乎所有的代碼都是 從上往下
讀,從左往右
讀。每行展現一個表達式或一個子句,每組代碼行展示一條完整的思路。這些思路用空白行區隔開來。
如下。在封包聲明/導入聲明和每個函數之間,都有空白行隔開。這條極其簡單的規則極大地影響到代碼的視覺外觀。每個空白行都是一條線索,標識出新的獨立概念。往下讀代碼,你的目光總會停留於空白行之後那一行。
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
Pattern.MULTILINE + Pattern.DOTALL
);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("</b>");
return html.toString();
}
}
如下。抽掉這些空白行,代碼可讀性減弱了不少。
package fitnesse.wikitext.widgets;
import java.util.regex.*;
public class BoldWidget extends ParentWidget {
public static final String REGEXP = "'''.+?'''";
private static final Pattern pattern = Pattern.compile("'''(.+?)'''",
Pattern.MULTILINE + Pattern.DOTALL
);
public BoldWidget(ParentWidget parent, String text) throws Exception {
super(parent);
Matcher match = pattern.matcher(text);
match.find();
addChildWidgets(match.group(1));
}
public String render() throws Exception {
StringBuffer html = new StringBuffer("</b>");
return html.toString();
}
}
在你不特意注視時,後果就更嚴重了。在第一個例子中,代碼組會跳到你眼中,而第二個例子就像一堆亂麻。兩段代碼的區別,展示了垂直方向上區隔的作用。
二十、附錄
20.1 .c文件
/**===========================================================================
Copyright (C) XXX Technologies Co., Ltd. All rights reserved.
@file user_udp.c
@brief 本文件用於UDP通訊接口
@author Leung Man-Wah
@version r0.1
@date 2019/07/04
@license XXX
----------------------------------------------------------------------------
Remark: (備註描述)
----------------------------------------------------------------------------
History
----------------------------------------------------------------------------
<Date> | <Version> | <Author> | <Description>
-------------|-----------|----------------|---------------------------------
2019/07/04 | r0.1 | Leung Man-Wah | 創建
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
============================================================================*/
/*********************************************************************
* INCLUDES
*/
// 系統/協議棧/庫頭文件(使用時去掉此行註釋)
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
// 自定義頭文件(使用時去掉此行註釋)
#include "user_udp.h"
#include "user_httpserver.h"
#include "cJSON.h"
#include "wb_protocol.h"
#include "common.h"
// 靜態函數聲明(使用時去掉此行註釋)
static void udpDeviceInfoEspconn(void);
static void udpBleDataEspconn(void);
static void udpSendDataCallback(void *arg);
/*********************************************************************
* CONSTANT
*/
const uint16 UDP_DEV_INFO_PERIOD = 1000;
/*********************************************************************
* GLOBAL VARIABLES
*/
uint8 g_bleDeviceRssiStandrdValue = BLE_DEV_RSSi_DEFAULT_VALUE;
bool g_httpClientConnectCloudFlag = false; // 聯網標誌,0 - 斷網;1 - 聯網
bool g_httpClientHeartbeatFlag = false; // 心跳標誌,0 - 非發心跳中;1 - 發心跳中
uint8 g_httpClientRenewalRecordFlag = 0; // 續傳標誌,0 - 非續傳中;1 - 續傳中;>=2 - 續傳失敗
/*********************************************************************
* LOCAL VARIABLES
*/
static struct espconn s_devInfoUdpEspconn; // 設備信息UDP廣播結構體
static struct espconn s_bleDataUdpEspconn; // 藍牙數據UDP廣播結構體
static os_timer_t s_udpDevInfoTimer; // UDP廣播設備信息的定時器
static uint8 s_udpDevInfoSendCount; // UDP廣播設備信息次數
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/**
@brief UDP通訊初始化
@param 無
@return 無
*/
void UdpInit(void)
{
udpDeviceInfoEspconn();
udpBleDataEspconn();
}
/*********************************************************************
* LOCAL FUNCTIONS
*/
/**
@brief UDP發送繼電器狀態
@param deviceType -[in] 設備類型
@param pMacAddr -[in] MAC地址
@param relayStatus -[in] 繼電器狀態
@return 無
*/
static void udpSendRelayStatus(uint8 deviceType, uint8 *pMacAddr, uint8 relayStatus)
{
char sendData[UDP_SEND_BUFFER_SIZE] = {0};
jsonPackageRelayStatus(sendData, deviceType, pMacAddr, relayStatus);
espconn_send(&s_devInfoUdpEspconn, sendData, strlen(sendData)); // UDP發送數據
}
/****************************************************END OF FILE****************************************************/
20.2 .h文件
/**===========================================================================
Copyright (C) XXX Technologies Co., Ltd. All rights reserved.
@file user_udp.h
@brief 本文件用於UDP通訊接口
@author Leung Man-Wah
@version r0.1
@date 2019/07/04
@license XXX
----------------------------------------------------------------------------
Remark: (備註描述)
----------------------------------------------------------------------------
History
----------------------------------------------------------------------------
<Date> | <Version> | <Author> | <Description>
-------------|-----------|----------------|---------------------------------
2019/07/04 | r0.1 | Leung Man-Wah | 創建
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
-------------|-----------|----------------|---------------------------------
| | |
============================================================================*/
#ifndef _USER_UDP_H_
#define _USER_UDP_H_
/*********************************************************************
* INCLUDES
*/
/*********************************************************************
* DEFINITIONS
*/
#define UDP_JSON_ERROR 0x00
#define UDP_START_SEND_DEV_INFO 0x01
#define UDP_STOP_SEND_DEV_INFO 0x02
#define UDP_CONFIG_CLOUD_ERROR 0x03
#define UDP_SEND_RELAY_STATUS 0x04
#define UDP_RESTART_SEND_DEV_INFO 0x05
#define UDP_RESEND_RELAY_STATUS 0x06
#define DEV_TYPE_BLE 0x01
#define DEV_TYPE_WIFI 0x02
#define UDP_SEND_BUFFER_SIZE 1024
/*********************************************************************
* TYPEDEFS
*/
typedef struct
{
uint8 devkey[40];
uint8 token[40];
uint8 activeStatus;
ip_addr_t tcpServerIp;
uint16 tcpServerPort;
char tcpServerDomain[32];
ip_addr_t httpServerIp;
uint16 httpServerPort;
char httpServerDomain[32];
char httpServerPath[32];
ip_addr_t dhcpIp;
ip_addr_t dhcpNetmask;
ip_addr_t dhcpGateway;
uint8 dhcpFlag;
uint8 bleDeviceRssiStandrdValue;
uint8 productType;
} FlahSavedParam_t;
/*********************************************************************
* GLOBAL VARIABLES
*/
extern uint8 g_bleDeviceRssiStandrdValue;
extern bool g_httpClientConnectCloudFlag;
extern bool g_httpClientHeartbeatFlag;
extern uint8 g_httpClientRenewalRecordFlag;
/*********************************************************************
* API FUNCTIONS
*/
void UdpInit(void);
void UdpSendDeviceInfo(void);
void UdpSendRelayStatus(uint8 deviceType, uint8 *pMacAddr, uint8 relayStatus);
void UdpSendBleScanData(uint8 *pSendData);
#endif /* _USER_UDP_H_ */
• 由 Leung 寫於 2019 年 10 月 30 日
• 參考:Google 開源項目風格指南——9. 格式
[代碼整潔之道]