写代码就像读书时候写作文一样,虽然试卷上没有要求字迹要多工整、篇幅要多具备艺术气息,但是不可否认的是,字迹工整、篇幅合理的作文总能拿较高的分数,屡试不爽。而好的代码规范能够提高工作效率,降低查找代码和团队交接的时间。如何让代码看起来很“养眼”呢?确切地说有三条原则:
- 使用一致的布局
- 让相似的代码看上去相似
- 把相关的代码行分组,形成代码块
举个坏掉的栗子:
class StatsKeeper{
public:
// A class for keeping track of a series of doubles
void Add(double d); // and methods for quick statistics about them
private: int count; /* how many so far
*/private: double Average();
list<double>
past_items
;double maximum;
};
再举一个好的栗子:
// A class for keeping track of a series of doubles
// and methods for quick statistics about them
class StatsKeeper{
public:
void Add(double d);
double Average();
private:
list<double> past_items;
int count; // how many so far
double minimum;
double maximum;
};
嗯,看到这里你应该才发现第一个例子中少写了一个double minimum;
,下面是一些具体操作的要点。
- 通过换行来保持一致和紧凑
- 在需要时使用列对齐
- 选一个有意义的顺序,并始终一致地使用
- 把声明按块组织起来
- 把代码分成段落
- 用方法来整理不规则的东西
- 个人风格与一致性
1. 通过换行来保持一致和紧凑#####
有的公司编码规范中有明确规定列字数限制,例如Google是80或100,超过规定都必须自动换行。但除此之外,还有一些没有超过的但也期望程序员能选择换行的情况。举个例子:
public class PerformanceTester{
public static final TcpConnectionSimulator wifi = new TcpConnectionSimulator(
500, /* kbps */
80, /*millisecs latency */
200, /* jitter */
1 /* packet loss % */ );
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
4500, /* kbps */
10, /*millisecs latency */
0, /* jitter */
0 /* packet loss % */ );
public static final TcpConnectionSimulator cell = new TcpConnectionSimulator(
100, /* kbps */
400, /*millisecs latency */
250, /* jitter */
5 /* packet loss % */ );
}
虽然都是定义一个TcpConnectionSimulator
对象,但t3_fiber乍一看和它的邻居并不一样,这显然违反了“让相似的代码看上去相似”原则。修改过后是这样的:
public class PerformanceTester{
public static final TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500, /* kbps */
80, /*millisecs latency */
200, /* jitter */
1 /* packet loss % */ );
public static final TcpConnectionSimulator t3_fiber =
new TcpConnectionSimulator(
4500, /* kbps */
10, /*millisecs latency */
0, /* jitter */
0 /* packet loss % */ );
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100, /* kbps */
400, /*millisecs latency */
250, /* jitter */
5 /* packet loss % */ );
}
当然也可以把注释搬到上面去,毕竟同样的注释写了三遍就要考虑“重构”了。
public class PerformanceTester{
// TcpConnectionSimulator(thoughput, latency, jetter, packet_loss)
// [kbps] [ms] [ms] [percent]
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(500,80, 200, 1);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(4500, 10, 0, 0);
public static final TcpConnectionSimulator cell =
new TcpConnectionSimulator(100, 400, 250, 5);
}
2.在需要的时候使用列对齐#####
整齐的边和列让读者可更轻松的浏览文本,有时候可以选择借用“列对齐”来让代码易读。例如:
CherkFullName("Doug Adams" , "Mr. Douglas Adams" , "");
CherkFullName("Jake Brown" , "Mr. Jake Brown III" , "");
CherkFullName("No Such Guy", "" , "no match found");
CherkFullName("John" , "" , "more than one result");
就像Google的代码规范中并不提倡这样的写法一样,笔者也不建议使用这种风格,因为一旦方法中某个参数需要修改,那么你又要花很多时间去排序。但是下面的情况可适当使用:
# Extract POST parameters to local variables
details = request.POST.get('details');
location = request.POST.get('location');
phone = equest.POST.get('phone');
email = request.POST.get('email');
url = request.POST.get('url');
Wait!第四行是不是少了点什么???
3. 选一个有意义的顺序,始终一致地使用#####
在很多时候,代码的顺序不会影响其正确性,例如上文五个变量的定义可以写成任意顺序。在这种情况下,不要随机排序,把他们按有意义的方式排序会有帮助。以下是一些排序的原则:
- 让变量的顺序与对应的HTML表单中<input>字段的顺序相匹配。
- 从“最重要”到“最不重要”排序。
- 按字母顺序排序
一旦你采用了某种排序规则,就要在代码中始终如一的遵循它。
4.把声明按块组织起来#####
由于大脑会很自然地按照分组和层次结构来思考,因此可以通过按块组织方式来使读者更快速的理解你的代码。
举个例子:
class FrontendServer{
public:
FrontendServer();
void ViewProfile(HttpRequest* request);
void OpenDatabase(String location, String user);
void SaveProfile(HttpRequest* request);
String ExtractQueryParam(HttpRequest* request, String param);
void ReplyOk(HttpRequest* request, String html);
void FindFriends(HttpRequest* request);
void ReplyNotFound(HttpRequest* request, String error);
void CloseDatabase(String location);
~FrontendServer();
}
作为读者,要想读懂代码,恐怕你会选择先把它们分成不同的组,所以写这段代码的程序员不妨先将它们按块组织在一起,并加上适当的注释,这样代码的可读性将大大提高。
class FrontendServer{
public:
FrontendServer();
~FrontendServer();
// Handlers
void ViewProfile(HttpRequest* request);
void SaveProfile(HttpRequest* request);
void FindFriends(HttpRequest* request);
// Request/Reply Utilities
String ExtractQueryParam(HttpRequest* request, String param);
void ReplyOk(HttpRequest* request, String html);
void ReplyNotFound(HttpRequest* request, String error);
//Database Helpers
void OpenDatabase(String location, String user);
void CloseDatabase(String location);
}
5. 把代码分成段落#####
字面文字分成段落是由于以下几个原因:
- 它是一种把相似的想法放在一起并与其他想法分开的方法。
- 它提供了可见的“脚印”,如果没有它,会很容易找不到你读到哪里了。
- 它便于段落之间的导航。
出于同样的原因,代码也应该分段,并为每一个段落加上一条总结性的注释。
def suggust_new_friends(user, emaii_password):
# Get the user's friends' email addresses
friends = user.friends();
friend_emails = set(f.email for f in frends)
# Import all email addresses from this user's email account.
contacts = import_contacts(user.email, email_password)
contact_emails = set(c.email for c in contracts)
# Find matching users that they aren't aleady friends with.
non_friend_emails = contact_email - friend_emails
suggested_friends = User.object.select(email_in = non_friend_emails)
# Display these lists on the page
display['user'] = user
display['friends'] = friends
display['suggested_friends'] = suggested_friends
return render("suggested_friends.html", display)
6. 用方法来整理不规则的东西#####
假设有一个个人数据库,它提供了下面这个函数:
// Turn a partial_name like "Doug Adams " into "Mr. Douglas Adams".
// If not possible, 'error' is filled with an explannation.
String ExpandFullName(DatabaseConnection dc, String patial_name, String* error);
并且提供一系列的例子来测试:
DatabaseConnection database_connection;
string error;
assert(ExpandFullName(database_connection, "Doug Adams", &error) == "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(database_connection, "Jake Brown", &error) == "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(database_connection, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(database_connection, "John", &error) == "");
assert(error == "more than one result");
这段代码其实看起来并不“养眼”,因为它没有换行,也没有一致的风格。但更大的问题在于有很多重复的串,例如“assert(ExpandFullName(database_connection,…”, 其中还有很多“error”。要想改进这段代码,可以为它们添加一个辅助方法。就像:
CherkFullName("Doug Adams", "Mr. Douglas Adams", "");
CherkFullName("Jake Brown", "Mr. Jake Brown III", "");
CherkFullName("No Such Guy", "", "no match found");
CherkFullName("John", "", "more than one result");
很明显这里有4个测试,每个使用了不同的参数,并把所有的“脏活”都放在CherkFullName()中:
void CherkFullName(String partial_name,
String expected_full_name,
String expected_error) {
//database_connection is now a class member
String error;
String full_name = ExpandFullName(database_connection, partial_name, &error);
assert(error == expected_error);
assert(full_name == expected_full_name);
}
7. 个人风格与一致性#####
最后想说的是一些个人的编程风格,例如大括号该放在哪里:
public void function
{
…
}
还是:
public void function {
…
}
又比如说"Tabs versus Space" ,笔者的建议是按照团队定好的风格来写,只采取一种,至于是采取哪一种,那就见仁见智了。
网友评论