青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

陳碩的Blog

C++ 工程實踐(3):采用有利于版本管理的代碼格式

陳碩 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

版本管理(version controlling)是每個程序員的基本技能,C++ 程序員也不例外。版本管理的基本功能之一是追蹤代碼變化,讓你能清楚地知道代碼是如何一步步變成現在的這個樣子,以及每次 check-in 都具體改動了哪些內部。無論是傳統的集中式版本管理工具,如 Subversion,還是新型的分布式管理工具,如 Git/Hg,比較兩個版本(revision)的差異都是其基本功能,即俗稱“做一下 diff”。

diff 的輸出是個窺孔(peephole),它的上下文有限(diff –u 默認顯示前后 3 行)。在做 code review 的時候,如果能憑這“一孔之見”就能發現代碼改動有問題,那就再好也不過了。

 

C 和 C++ 都是自由格式的語言,代碼中的換行符被當做 white space 來對待。(當然,我們說的是預處理(preprocess)之后的情況)。對編譯器來說一模一樣的代碼可以有多種寫法,比如

foo(1, 2, 3, 4);

foo(1,

    2,

    3,

    4);

詞法分析的結果是一樣的,語意也完全一樣。

對人來說,這兩種寫法讀起來不一樣,對與版本管理工具來說,同樣功能的修改造成的差異(diff)也往往不一樣。所謂“有利于版本管理”,就是指在代碼中合理使用換行符,對 diff 工具友好,讓 diff 的結果清晰明了地表達代碼的改動。(diff 一般以行為單位,也可以以單詞為單位,本文只考慮最常見的 diff by lines。)

這里舉一些例子。

對 diff 友好的代碼格式

1. 多行注釋也用 //,不用 /* */

Scott Meyers 寫的《Effective C++》第二版第 4 條建議使用 C++ 風格,我這里為他補充一條理由:對 diff 友好。比如,我要注釋一大段代碼(其實這不是個好的做法,但是在實踐中有時會遇到),如果用 /* */,那么得到的 diff 是:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -18,6 +18,7 @@ class Printer : boost::noncopyable
loop2_->runAfter(1, boost::bind(&Printer::print2, this));
}
+  /*
~Printer()
{
std::cout << "Final count is " << count_ << "\n";
@@ -38,6 +39,7 @@ class Printer : boost::noncopyable
loop1_->quit();
}
}
+  */

void print2()
{

從這樣的 diff output 能看出注釋了哪些代碼嗎?

如果用 //,結果會清晰很多:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -18,26 +18,26 @@ class Printer : boost::noncopyable
loop2_->runAfter(1, boost::bind(&Printer::print2, this));
}
-  ~Printer()
-  {
-    std::cout << "Final count is " << count_ << "\n";
-  }
+  // ~Printer()
+  // {
+  //   std::cout << "Final count is " << count_ << "\n";
+  // }
-  void print1()
-  {
-    muduo::MutexLockGuard lock(mutex_);
-    if (count_ < 10)
-    {
-      std::cout << "Timer 1: " << count_ << "\n";
-      ++count_;
-
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
-    }
-    else
-    {
-      loop1_->quit();
-    }
-  }
+  // void print1()
+  // {
+  //   muduo::MutexLockGuard lock(mutex_);
+  //   if (count_ < 10)
+  //   {
+  //     std::cout << "Timer 1: " << count_ << "\n";
+  //     ++count_;
+  //
+  //     loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+  //   }
+  //   else
+  //   {
+  //     loop1_->quit();
+  //   }
+  // }
void print2()
{

同樣的道理,取消注釋的時候 // 也比 /* */ 更清晰。

另外,如果用 /* */ 來做多行注釋,從 diff 不一定能看出來你是在修改代碼還是修改注釋。比如以下 diff 似乎修改了 muduo::EventLoop::runAfter 的調用參數:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -32,7 +32,7 @@ class Printer : boost::noncopyable
std::cout << "Timer 1: " << count_ << "\n";
++count_;
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+      loop1_->runAfter(2, boost::bind(&Printer::print1, this));
}
else
{

其實這個修改發生在注釋里邊 (要增加上下文才能看到, diff -U 20,多一道手續,降低了工作效率),對代碼行為沒有影響:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -20,31 +20,31 @@ class Printer : boost::noncopyable
   /*
   ~Printer()
{
std::cout << "Final count is " << count_ << "\n";
}
void print1()
{
muduo::MutexLockGuard lock(mutex_);
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << "\n";
++count_;
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+      loop1_->runAfter(2, boost::bind(&Printer::print1, this));
}
else
{
loop1_->quit();
}
}
   */

void print2()
{
muduo::MutexLockGuard lock(mutex_);
if (count_ < 10)
{
std::cout << "Timer 2: " << count_ << "\n";
++count_;

總之,不要用 /* */ 來注釋多行代碼。

或許是時過境遷,大家都在用 // 注釋了,《Effective C++》第三版去掉了這一條建議。

2. 局部變量與成員變量的定義

基本原則是,一行代碼只定義一個變量,比如

double x;

double y;

將來代碼增加一個 double z 的時候,diff 輸出一眼就能看出改了什么:

@@ -63,6 +63,7 @@ private:
int count_;
double x;
double y;
+  double z;
};
int main()

如果把 x 和 y 寫在一行,diff 的輸出就得多看幾眼才知道。

@@ -61,7 +61,7 @@ private:
muduo::net::EventLoop* loop1_;
muduo::net::EventLoop* loop2_;
int count_;
-  double x, y;
+  double x, y, z;
 };
int main()

所以,一行只定義一個變量更利于版本管理。同樣的道理適用于 enum 成員的定義,數組的初始化列表等等。

3. 函數聲明中的參數

如果函數的參數大于 3 個,那么在逗號后面換行,這樣每個參數占一行,便于 diff。以 muduo::net::TcpClient 為例:

class TcpClient : boost::noncopyable
{
public:
 TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const string& name);

如果將來 TcpClient 的構造函數增加或修改一個參數,那么很容易從 diff 看出來。這恐怕比在一行長代碼里數逗號要高效一些。

4. 函數調用時的參數

在函數調用的時候,如果參數大于 3 個,那么把實參分行寫。以 muduo::net::EPollPoller 為例:

Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
int numEvents = ::epoll_wait(epollfd_,
&*events_.begin(),
static_cast<int>(events_.size()),
timeoutMs);
Timestamp now(Timestamp::now());

這樣一來,如果將來重構引入了一個新參數(好吧,epoll_wait 不會有這個問題),那么函數定義和函數調用的地方的 diff 具有相同的形式(比方說都是在倒數第二行加了一行內容),很容易肉眼驗證有沒有錯位。如果參數寫在一行里邊,就得睜大眼睛數逗號了。

5. class 初始化列表的寫法

同樣的道理,class 初始化列表(initializer list)也遵循一行一個的原則,這樣將來如果加入新的成員變量,那么兩處(class 定義和 ctor 定義)的 diff 具有相同的形式,讓錯誤無所遁形。以 muduo::net::Buffer 為例:

class Buffer : public muduo::copyable
{
public:
static const size_t kCheapPrepend = 8;
static const size_t kInitialSize = 1024;
Buffer()
    : buffer_(kCheapPrepend + kInitialSize),
readerIndex_(kCheapPrepend),
writerIndex_(kCheapPrepend)
{
}
// 省略
 private:
   std::vector<char> buffer_;
size_t readerIndex_;
size_t writerIndex_;
static const char kCRLF[];
};

注意,初始化列表的順序必須和數據成員聲明的順序相同。

6. 與 namespace 有關的縮進

Google 的 C++ 編程規范明確指出,namespace 不增加縮進。這么做非常有道理,方便 diff –p 把函數名顯示在每個 diff chunk 的頭上。

如果對函數實現做 diff,chunk name 是函數名,讓人一眼就能看出改的是哪個函數。如下圖,紅色劃線部分。

diff_function

如果對 class 做 diff,那么 chunk name 就是 class name。

diff_class

diff 原本是為 C 語言設計的,C 語言沒有 namespace 縮進一說,所以它默認會找到“頂格寫”的函數作為一個 diff chunk 的名字,如果函數名前面有空格,它就不認得了。muduo 的代碼都遵循這一規則,例如:

namespace muduo
{
///
/// Time stamp in UTC, in microseconds resolution.
///
/// This class is immutable.
/// It's recommended to pass it by value, since it's passed in register on x64.
///
class Timestamp : public muduo::copyable,
public boost::less_than_comparable<Timestamp>
{
// class 從第一列開始寫,不縮進
// 函數的實現也從第一列開始寫,不縮進。
Timestamp Timestamp::now()
{
struct timeval tv;
gettimeofday(&tv, NULL);
int64_t seconds = tv.tv_sec;
return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
}

相反,boost 中的某些庫的代碼是按 namespace 來縮進的,這樣的話看 diff 往往不知道改動的是哪個 class 的哪個成員函數。

這個或許可以通過設置 diff 取函數名的正則表達式來解決,但是如果我們寫代碼的時候就注意把函數“頂格寫”,那么就不用去動 diff 的默認設置了。另外,正則表達式不能完全匹配函數名,因為函數名是上下文無關語法(context-free syntax),你沒辦法寫一個正則語法去匹配上下文無關語法。我總能寫出某種函數聲明,讓你的正則表達式失效(想想函數的返回類型,它可能是一個非常復雜的東西,更別說參數了)。更何況 C++ 的語法是上下文相關的,比如你猜 Foo<Bar> qux; 是個表達式還是變量定義?

7. public 與 private

我認為這是 C++ 語法的一個缺陷,如果我把一個成員函數從 public 區移到 private 區,那么從 diff 上看不出來我干了什么,例如:

diff --git a/muduo/net/TcpClient.h b/muduo/net/TcpClient.h
--- a/muduo/net/TcpClient.h
+++ b/muduo/net/TcpClient.h
@@ -37,7 +37,6 @@ class TcpClient : boost::noncopyable
void connect();
void disconnect();
-  bool retry() const;
   void enableRetry() { retry_ = true; }
/// Set connection callback.
@@ -60,6 +59,7 @@ class TcpClient : boost::noncopyable
void newConnection(int sockfd);
/// Not thread safe, but in loop
void removeConnection(const TcpConnectionPtr& conn);
+  bool retry() const;
EventLoop* loop_;
boost::scoped_ptr<Connector> connector_; // avoid revealing Connector

從上面的 diff 能看出我把 retry() 變成 private 了嗎?對此我也沒有好的解決辦法,總不能每個函數前面都寫上 public: 或 private: 吧?

對此 Java 和 C# 都做得比較好,它們把 public/private 等修飾符放到每個成員函數的定義中。這么做增加了信息的冗余度,讓 diff 的結果更直觀。

 

歡迎補充。

對 grep 友好的代碼風格

操作符重載

C++工具匱乏,在一個項目里,要找到一個函數的定義或許不算太難(最多就是分析一下重載和模板特化),但是要找到一個函數的使用就難多了。不比 Java,在 Eclipse 里 Ctrl+Shift+G 就能找到所有的引用點。

假如我要做一個重構,想先找到代碼里所有用到 muduo::timeDifference 的地方,判斷一下工作是否可行,基本上惟一的辦法是grep。用 grep 還不能排除同名的函數和注釋里的內容。這也說明為什么要用 // 來引導注釋,因為在 grep 的時候,一眼就能看出這行代碼是在注釋里的。

在我看來,operator overloading 應僅限于和 STL algorithm/container 配合時使用,比如 transform() 和 map<T,U>,其他情況都用具名函數為宜。原因之一是,我根本用 grep 找不到在哪兒用到了 operator-()。這也是 muduo::Timestamp 只提供 operator<() 而不提供 operator+() operator-() 的原因,我提供了兩個函數 timeDifference 和 addTime 來實現所需的功能。

又比如,Google Protocol Buffers 的回調是 class Closure,它的接口用的是 virtual function Run() 而不是 virtual operator()()。

static_cast 與 C-style cast

為什么 C++ 要引入 static_cast 之類的轉型操作符,原因之一就是像 (int*) pBuffer 這樣的表達式基本上沒辦法用 grep 判斷出它是個強制類型轉換,寫不出一個剛好只匹配類型轉換的正則表達式。(again,語法是上下文無關的,無法用正則搞定。)

如果類型轉換都用 *_cast,那只要 grep 一下我就能知道代碼里哪兒用了 reinterpret_cast 轉換,便于迅速地檢查有沒有用錯。為了強調這一點,muduo 開啟了編譯選項 -Wold-style-cast 來幫助查找 C-style cast,這樣在編譯時就能幫我們找到問題。

 一切為了效率

如果用圖形化的文件比較工具,似乎能避免上面列舉的問題。但無論是 web 還是客戶端,無論是 inline diff 還是 diff by lines 都不能解決全部問題,效率也不一定更高。

對于(2),如果想知道是誰在什么時候增加的 double z,在分行寫的情況下,用 git blame 或 svn blame 立刻就能找到始作俑者。如果寫成一行,那就得把文件的 revisions 拿來一個個人工比較,因為這一行 double x = 0.0, y = 1.0, z = -1.0; 可能修改過多次,你得一個個看才知道什么時候加入了變量 z。這個 blame 的 case 也適用于 3、4、5。

比如(6)改動了一行代碼,你還是要 scroll up 去找改的是哪個 function,人眼看的話還有“看走眼”的可能,又得再定睛觀瞧。這一切都是浪費人的時間,使用更好的圖形化工具并不能減少浪費,相反,我認為增加了浪費。

另外一個常見的工作場景,早上來到辦公室,update 一下代碼,然后掃一眼 diff output 看看別人昨天動了哪些文件,改了哪些代碼,這就是一兩條命令的事,幾秒鐘就能解決戰斗。如果用圖形化的工具,得一個個點開文件 diff 的鏈接或點開新 tab 來看文件的 side-by-side 比較(不這么做的話看不到足夠多的上下文,跟看 diff output 無異),然后點擊鼠標滾動頁面去看別人到底改了什么。說實話我覺得這么做效率不比 diff 高。

(待續)

posted on 2011-03-05 15:16 陳碩 閱讀(3360) 評論(7)  編輯 收藏 引用

評論

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-05 21:43 陳梓瀚(vczh)

工具不行,就換一個/自己做一個嘛。這樣才能進步。  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-06 18:38 classyk

我建議多行還是用/* */來注釋,盡管現在很多的編輯器已經很方便的可以多行//注釋。

因為 / * */框住的代碼,意味著是一個相關聯的段落
而多行//則表示不了這種意思。

  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-06 19:52 陳碩

@classyk
這個好吧,用縮進。注釋函數的時候把 // 放第一列,注釋 for 循環的時候把 // 與 for 上面一行語句對齊。  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-10 17:01 violet

聲明變量時分行寫,代碼很不好看啊。
為了diff方便而降低了代碼可讀性,不太好吧  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-10 19:48 陳碩

@violet
你的意思是
string name;
string address;

string name, address;
要難看?  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-21 22:03 3tgame

@@ -37,7 +37,6 @@ class TcpClient : boost::noncopyable
-37,7 +37,6 表示啥意思?怎么跟默認的linux的diff輸出信息不一樣  回復  更多評論   

# re: C++ 工程實踐(3):采用有利于版本管理的代碼格式 2011-03-22 08:20 陳碩

diff -u
http://en.wikipedia.org/wiki/Diff#Unified_format  回復  更多評論   

<2025年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            国产精品久久久久一区二区三区共| 欧美制服第一页| 久久中文在线| 久久久一本精品99久久精品66| 亚洲日本乱码在线观看| 国产精品视频最多的网站| 欧美区一区二| 欧美成人免费在线| 免费中文字幕日韩欧美| 久久婷婷国产综合尤物精品| 久久av二区| 欧美在线视频一区| 久久久久久一区二区三区| 久久精品夜色噜噜亚洲a∨ | 亚洲日本va午夜在线影院| 国产午夜精品麻豆| 狠狠干狠狠久久| 蜜桃av一区| 欧美激情综合亚洲一二区| 欧美国产第一页| 欧美日本一道本| 国产精品高潮呻吟久久av黑人| 国产精品国产三级国产aⅴ无密码| 久久一综合视频| 欧美黄色片免费观看| 欧美日韩在线播放三区| 欧美激情中文字幕一区二区| 欧美日韩在线免费| 国产精品婷婷午夜在线观看| 国产综合色产| 国内精品久久久久久| 亚洲国产三级网| 在线亚洲免费视频| 欧美中文字幕不卡| 日韩视频第一页| 亚洲欧美影院| 毛片一区二区三区| 一本色道久久综合狠狠躁的推荐| 欧美国产在线视频| 日韩一区二区电影网| 欧美一区午夜精品| 欧美激情黄色片| 国产美女一区二区| 日韩午夜av电影| 在线播放亚洲一区| 先锋资源久久| 美国成人直播| 亚洲激情电影在线| 亚洲精品欧美一区二区三区| 亚洲综合清纯丝袜自拍| 久久夜色精品一区| 国产精品地址| 亚洲精品字幕| 久久九九免费视频| 99精品久久久| 噜噜噜在线观看免费视频日韩| 国产精品扒开腿做爽爽爽软件 | 亚洲一区精彩视频| 美女国内精品自产拍在线播放| 亚洲精品国产精品乱码不99按摩| 久久精品国产v日韩v亚洲 | 欧美图区在线视频| 亚洲高清在线播放| 久久精品水蜜桃av综合天堂| 免费黄网站欧美| 欧美一区亚洲一区| 国产精品毛片在线看| 国产一级一区二区| 亚洲综合日韩在线| 日韩视频免费大全中文字幕| 久久久精品国产99久久精品芒果| 久久久久www| 亚洲愉拍自拍另类高清精品| 免费不卡亚洲欧美| 亚洲一区二区三区在线| 国产精品三级久久久久久电影| 欧美视频在线播放| 欧美噜噜久久久xxx| 国产亚洲成av人在线观看导航| 亚洲少妇在线| 亚洲午夜视频在线观看| 国产精品亚洲激情| 久久久av毛片精品| 久久久久免费观看| 亚洲精品日韩激情在线电影| 亚洲精品影视| 国产伦精品一区二区三区免费迷 | 一本在线高清不卡dvd| 国语自产精品视频在线看抢先版结局 | 欧美激情2020午夜免费观看| 91久久精品一区| 亚洲精品黄色| 欧美色综合天天久久综合精品| 亚洲欧美日韩一区二区在线| 性欧美长视频| 亚洲美洲欧洲综合国产一区| 亚洲一区激情| 亚洲国产精品高清久久久| 亚洲乱码国产乱码精品精98午夜| 欧美性大战久久久久| 久久久亚洲人| 欧美了一区在线观看| 欧美自拍偷拍| 欧美日韩精品在线| 美女免费视频一区| 欧美婷婷久久| 欧美高清视频一区二区三区在线观看| 欧美日韩系列| 欧美a级片网站| 国产精品亚洲综合一区在线观看| 欧美不卡福利| 国产午夜一区二区三区| 日韩亚洲欧美一区| 亚洲国产精品综合| 欧美中文字幕第一页| 一区二区免费看| 美女诱惑黄网站一区| 欧美资源在线| 国产精品另类一区| 亚洲精选一区二区| 亚洲国产美女精品久久久久∴| 亚洲尤物在线视频观看| 在线亚洲高清视频| 欧美精品一区二区三区在线看午夜 | 亚洲新中文字幕| 亚洲精品中文字| 麻豆精品在线视频| 久久这里只有| 国产亚洲毛片在线| 亚洲欧美色一区| 亚洲欧美色婷婷| 欧美视频二区| 99国产精品一区| 亚洲婷婷在线| 欧美午夜视频在线| av成人动漫| 亚洲一区二区三区在线播放| 欧美激情视频一区二区三区在线播放| 久久精品人人爽| 国产日韩高清一区二区三区在线| 亚洲一区二区三区国产| 亚洲一区二区三区激情| 欧美日韩午夜视频在线观看| 日韩一区二区久久| 亚洲一二三区精品| 国产精品久久久久久久7电影| 国产精品videosex极品| 99爱精品视频| 欧美肥婆bbw| 亚洲国产成人久久综合一区| 亚洲国产成人久久综合| 久久人人看视频| 美女精品在线观看| 亚洲国产精品999| 欧美精品黄色| 亚洲免费观看高清完整版在线观看| aaa亚洲精品一二三区| 欧美日韩国产首页| 亚洲五月六月| 久久久久国产免费免费| 激情六月婷婷综合| 免播放器亚洲一区| 日韩一级成人av| 久久不见久久见免费视频1| 国产一区二区你懂的| 理论片一区二区在线| 亚洲精品一区二区三区在线观看| 亚洲女同精品视频| 国产综合久久久久久| 欧美电影专区| 亚洲一区激情| 亚洲大片在线观看| 性感少妇一区| 亚洲国产精品久久人人爱蜜臀 | 欧美肥婆在线| 亚洲视频你懂的| 欧美高清视频一区二区三区在线观看| 99视频精品全国免费| 国产亚洲欧美一级| 欧美紧缚bdsm在线视频| 香蕉成人久久| 日韩亚洲欧美一区二区三区| 久久综合五月天婷婷伊人| 一区二区三区久久| 在线观看欧美精品| 国产精品一二三视频| 欧美交受高潮1| 性做久久久久久久免费看| 亚洲人成在线观看| 免费在线看成人av| 亚洲免费在线电影| 亚洲精品视频啊美女在线直播| 国产亚洲网站| 国产精品日韩一区二区三区| 欧美精品在线观看91| 蜜臀91精品一区二区三区| 欧美在线一区二区| 亚洲主播在线| 亚洲一级网站| 一区二区三区日韩在线观看|