// 其实,漂泊在外的日子真的不好过,我一定会回国,早晚的事情
谈谈代码风格
by zhlmmc
前言
刚刚阅读完《Java Puzzlers》,是一本好书。看完你会发现自己对Java的了解是多么的匮乏。书中一共有95个puzzlers,详细记得的不多。我回忆一下,总结出以下三点:
<[if !supportLists]>1. 注意数字类型的溢出和精度问题<[endif]>
<[if !supportLists]>2. 能用Java Library提供的函数就不要自己重复劳动<[endif]>
<[if !supportLists]>3. 代码要简明易懂,不要用一些不常用的语法和trick<[endif]>
还有很多技术细节的问题记不起来了,大概碰上了能想起来。不过这和我这篇文章要讲的内容关系不大,我要讲的是代码风格,而不同于我们平时所说的代码风格。我不是要讲“format”比如大括号的位置,缩进,空行等等。我要讲的是如何让自己的代码逻辑更清楚,更容易阅读。
<[if !supportLists]>一、 方法的名字应当准确表述方法的功能
给方法取名很重要,特别是在别人要调用你的代码的情况下。如果方法的名字不能准确描述方法的功能,调用者会很迷惑。比如Java Thread类的一个反面教材“Thread.interrupted()”。这个函数会返回当前线程是否interrupted,并且清除当前的interrupted状态。所以你如果调用这个函数两次的话,返回的值有可能是不一样的。这个方法的名字应该叫做“Thread.getAndClearInterrupedState()”或者“Thread.clearInterrupedState()”。因为这里“clear”是主要操作,“get”省略并不影响使用。况且这个函数的返回值告诉调用者有“get”操作。
再来看一个例子:
public boolean connectToServer(){
// Code for connecting to server
// Code for reading message from server
// Current thread is blocked until reading is finished.
return true;
}
public boolean connectToServerAndWaitResponse(){
// Code for connecting to server
// Code for reading message from server
// Current thread is blocked until reading is finished.
return true;
}
“connectToServer”这个名字并没有告诉调用者这个函数会block当前线程。所以调用者会以为这个函数在连接上服务器以后会马上返回,然后执行后面的代码。但是真的调用的时候这个函数有可能被block,如果调用者没有这个函数的源代码的话就会很迷惑,找不到bug所在。“connectToServerandWaitResponse”这个名字明显的告诉调用者,“我会block,你可能需要一个新的线程去执行其他操作”。
虽然方法的名字变长了,但是我们有现代化的IDE帮忙,再长的方法也不用自己输入,都是自动补全的。一个好的方法名字可以省去查看API的麻烦,何乐而不为?
<[if !supportLists]>二、 把逻辑简单的“if”放在前面
我们先来看两段代码:
public boolean checkValid(String name){
if (name.contains("%")) {
// 50 lines of code
}
else if (name.contains("?")){
// 20 lines of code
}
else if (name.contains("$")){
// 5 lines of code
}
return false;
}
public boolean checkValid2(String name){
if (name.contains("$")) {
// 5 lines of code
}
else if (name.contains("?")){
// 20 lines of code
}
else if (name.contains("%")){
// 50 lines of code
}
return false;
}
这两段代码的区别是三个“if”的顺序。第一段代码把复杂的“if”放在前面,第二段相反。哪段代码更清晰易懂呢?初一看觉得差不多,其实第二段代码比第一段更容易阅读。人在阅读条件代码的时候脑子里始终要记得当前代码的条件是什么,也就是现在在哪个“if”下面。如果把复杂的“if”放在前面,人读到后面可能都忘记了自己还在“if”语句里面。等读到“else”的时候可能会很想“恩…前面那个条件是什么?”所以把简单的“if”放在前面能够减轻阅读代码的负担。先处理简单的逻辑,在读后面复杂逻辑的时候回忆简单的逻辑比较容易。
<[if !supportLists]>三、 能“return”的地方就“return”
我们还是来看两段代码:
public boolean checkValid3(String name){
boolean valid = false;
if (name.contains("$")) {
// 5 lines of code
valid = true;
}
else if (name.contains("?")){
// 20 lines of code
valid = true;
}
else if (name.contains("%")){
// 50 lines of code
valid = true;
}
return valid;
}
public boolean checkValid4(String name){
if (name.contains("$")) {
// 5 lines of code
return true;
}
else if (name.contains("?")){
// 20 lines of code
return true;
}
else if (name.contains("%")){
// 50 lines of code
return true;
}
return false;
}
这两段代码的区别是第一段代码用了一个临时变量还保存返回值,第二段代码在每个“if”里面都直接返回。那个更好?显然第二个。先不说代码行数减少了,关键是阅读代码的时候脑子的负担减少了。第一段代码的每个“if”以“valid = true;”结尾,人在读到这里的时候心里会想,“后面还会对这个valid做什么操作呢?”。而第二段代码的每个“if”以“return true;”结尾,人在读到这里的时候心里的包袱就放下了,一个case结束,可以集中精力在下面的cases了。其实不光是“if”条件语句,在“try catch”,“for”等等语句都一样的,在能return的地方就return能够让代码的逻辑更简单(有时候“continue”,“break”也能达到一样的效果),能减轻阅读的时候的思想包袱,因为return一个,逻辑就简单了一层。
<[if !supportLists]>四、 删掉不必要的“else”语句
你可能已经发现了,我们前面的例子可以更加简化一点:
public boolean checkValid5(String name){
if (name.contains("$")) {
// 5 lines of code
return true;
}
if (name.contains("?")){
// 20 lines of code
return true;
}
if (name.contains("%")){
// 50 lines of code
return true;
}
return false;
}
我对“checkValid4”做了一些简单的修改,把所有的“else”去掉了。很明显,这些“else”都是多余的,因为我们在每个“if”中都“return”了。这样代码更清晰,一眼望去,三个“if”,表示有这里有三个cases。然后分别阅读每个case,读完一个扔一个,因为每个case之间都没有关联。加了“else”虽然不影响代码的逻辑,但是让人潜意识中感觉到这几个cases好像有关联,拖泥带水。
<[if !supportLists]>五、 尽量在声明成员变量的时候进行初始化
我们继续看代码先:
class Demo1{
private List nodes;
// 100 lines of other logic
public Object getNode(int index){
return nodes.get(index);
}
// 100 lines of other logic
public void addNode(Object node){
if (nodes == null) {
nodes = new LinkedList();
}
nodes.add(node);
}
}
class Demo2{
private List nodes = new LinkedList();
// 100 lines of other logic
public Object getNode(int index){
return nodes.get(index);
}
// 100 lines of other logic
public void addNode(Object node){
nodes.add(node);
}
}
这两段代码的区别在于“nodes”成员变量的初始化的位置。我们假设这个类有200+行代码。先看Demo1,首先你看到这个类声明了一个成员变量“nodes”,但是没有被初始化。当你看到“getNode”这个函数的时候,你一定在想,“nodes”在哪里被初始化了?为什么这里可以直接用?然后你一定打断当前逻辑,去找“nodes”的初始化代码。为什么不在声明的时候直接初始化呢?这样就没有后顾之忧了。而且可以减少bug,保证在调用“nodes”之前,它一定已经被初始化,否则你有可能会遇到“NullPointerException”。当然,你可以说Demo1的这种做法叫做“Lazy Initialize”。不错,但是在这个例子中没有任何必要这么做。如果你的应用的初始化开销特别大,那另当别论。
结束语
好了,现在来总结一下:
<[if !supportLists]>1. 方法的名字应当准确表述方法的功能<[endif]>
<[if !supportLists]>2. 把逻辑简单的“if”放在前面<[endif]>
<[if !supportLists]>3. 能“return”的地方就“return”<[endif]>
<[if !supportLists]>4. 删掉不必要的“else”语句<[endif]>
<[if !supportLists]>5. 尽量在声明成员变量的时候进行初始化<[endif]>
好像还有很多可以写,一时半会儿想不起来了,等以后想到了再补充吧。
心情: 一般