陳碩 (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_ << "
";
@@ -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_ << "
";
- }
+ // ~Printer()
+ // {
+ // std::cout << "Final count is " << count_ << "
";
+ // }
- void print1()
- {
- muduo::MutexLockGuard lock(mutex_);
- if (count_ < 10)
- {
- std::cout << "Timer 1: " << count_ << "
";
- ++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_ << "
";
+ // ++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_ << "
";
++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_ << "
";
}
void print1()
{
muduo::MutexLockGuard lock(mutex_);
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << "
";
++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_ << "
";
++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(EventLo