第四章 审美
好的源代码应该看上去就养眼。好多公司在编码规范上都要一致,为的就是能使整个源代码看上去整洁,有条理,这样的代码,阅读起来更加方便。
在对代码进行审美方面的改造时,可以遵循以下几个原则:
a、使用一致的布局
b、让相似的代码看上去相似
c、把相关的代码行分组,形成代码块。
1、重新安排换行来保持一致和紧凑
先来看下下面的这个例子,一个类的构造函数有四个参数:网络连接速度(Kbps)、平均延时(ms)、延时的抖动(ms)、丢包率(%),在这个例子中,需要三个不同的实例。
public class Tester{
public Tcpconnect wifi = new Tcpconnect(
500, /*Kbps*/
80, /*ms latency*/
200, /*jitter*/
1 /*packet loss %*/);
public Tcpconnect t3_fiber =
new Tcpconnect(
450000, /*Kbps*/
10, /*ms latency*/
0, /*jitter*/
0 /*packet loss %*/);
public Tcpconnect cell = new Tcpconnect(
500, /*Kbps*/
80, /*ms latency*/
200, /*jitter*/
1 /*packet loss %*/);
}
这段代码看上去还算整洁,只是t3_fiber的定义与其他两个在形式上不一样,这让它显得很突兀。下面这样写就好更好的保持了一致:
public class Tester{
public Tcpconnect wifi =
new Tcpconnect(
500, /*Kbps*/
80, /*ms latency*/
200, /*jitter*/
1 /*packet loss %*/);
public Tcpconnect t3_fiber =
new Tcpconnect(
450000, /*Kbps*/
10, /*ms latency*/
0, /*jitter*/
0 /*packet loss %*/);
public Tcpconnect cell =
new Tcpconnect(
500, /*Kbps*/
80, /*ms latency*/
200, /*jitter*/
1 /*packet loss %*/);
}
这样是否就可以了呢?
我们重新审视这段代码,这段代码占用了太多的纵向空间,而且它的注释部分有很多重复的地方,我们稍作修改,会让它更加好看。
public class Tester{
// Tcpconnect(throughput, latency, jitter, packet_loss);
// /*Kbps*/ /*ms*/ /*ms*/ /*percent*/
public Tcpconnect wifi =
new Tcpconnect(500, 80, 200, 1);
public Tcpconnect t3_fiber =
new Tcpconnect(450000, 10, 0, 0);
public Tcpconnect cell =
new Tcpconnect(500, 80, 200, 1);
}
这段代码看上去就更加一目了然了。
2、用方法来整理不规则的东西
我们在审视代码过程中,需要注意一点的就是是否有重复的代码可以精炼,是否能通过提取函数的方式,将不规则的代码整理得更加漂亮。
比如,我们有这样的一个API接口,可以将人名扩展地更加正式,如“Doug Adams”扩展为“Mr. Douglas Adams”,匹配关系会从给定的数据库中查找,如果不能扩展的话,就会以出参的形式返回错误码。这个接口是这样的:
string ExpandFullName(Database dc, string partial_name, string *error)
这有一些对这个API的测试:
Database dc;
string error;
assert(ExpandFullName(dc, "Doug Adams", &error)
== "Mr. Douglas Adams");
assert(error == "");
assert(ExpandFullName(dc, "Jake Brown", &error)
== "Mr. Jacob Brown III");
assert(error == "");
assert(ExpandFullName(dc, "No Such Guy", &error) == "");
assert(error == "no match found");
assert(ExpandFullName(dc, "John", &error) == "");
assert(error == "more then one result");
这段测代码中换行不规则,形式也不一致,第一印象会很凌乱。鉴于重新布置换行也达不到更好的效果,我们需要关注这些代码的实现。可以发现,这些代码都是实现相同的功能,我们可以把这些具体的实现细节提取出来,像下面这样:
CheckFullName("Doug Adams", "Mr. Douglas Adams", "");
CheckFullName("Jake Brown", "Mr. Jacob Brown III", "");
CheckFullName("No Such Guy", "", "");
CheckFullName("John", "", "more then one result");
void CheckFullName(string partial_name,
string expected_full_name,
string expect_error)
{
Database dc;
string error;
string full_name = ExpandFullName(dc, partial_name, &error);
assert(error == expect_error);
assert(full_name == expected_full_name);
}
修改后的代码如上,它带来了三个额外效果:
a、消除了大量的重复代码
b、每个测试需要关注的部分更加清晰,expected_error,expected_full_name, partial_name。
c、添加新的测试项很简单。
从这里,就能看出,“看上去很美”的代码不仅会带来表面层次上的改进,还可以帮我们把代码结构做的更好。
3、在需要时使用列对齐
整齐的边和列可以让阅读者更轻松的阅读文本。上面的例子中就使用了这个技巧。
比如下面这个例子:
details = request.POST.get("datails");
location = request.POST.get("location");
phone = equest.POST.get("phone");
email = request.POST.get("email");
url = request.POST.get("url");
通过使用列对齐,我们一眼就可以看出其中的不规则项,进而可以查找到其中的错误,request丢失了首字母‘r’。
另外还有一点需要注意,如果我们在代码中选定了一个有意义的顺序的话,就尽量始终如一的使用它。就像上面这个例子,按照details、location、phone、email、url来描述一个人的信息,如果是后续的代码中,突然采用了别的顺序来描述一个人的话,往往会给人带来思维上的猝不及防。
4、把声明和实现按照块的方式组织起来
就像我们小时候写作文一样,按照步骤或是含义的不同,将代码按照块的形式组织起来。虽然看起来,代码行数变多了,但是却有助于阅读者快速地找出各个层次的段落,更直观地了解每段代码的意图。
这种比较好理解,就不详细解释了。
5、个人风格的一致性
还有一部分代码审美选择可以归结为个人风格,比方说下面的这两种风格
class C{
...
};
class C
{
...
};
大括号的起始位置。这并不会影响代码的可读性,不过如果两种方式混合在一起的话,看起来就不那么对阅读者友好了。
一致的风格往往比“正确”的风格更加重要。