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_ << " ";
@@ -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
补充:软件开发 , C++ ,