《代码整洁之道》一书中说:当你的代码在做 Code Review 时,审查者要是愤怒地吼道:“What the fuck, is this shit?”、“Dude, What the fuck!”等言辞激烈的词语,那说明你写的代码是 Bad Code,如果审查者只是漫不经心的吐出几个:“What the fuck?”,那说明你写的是 Good Code。衡量代码质量的唯一标准就是每分钟骂出“WTF”的频率。
代码不规范会带来很多负面影响,比如团队之间如果代码不统一规范,对审查阅读都会不方便,后期的维护复杂度也会增加,降低团队效率。另外,不规范的代码可能在不小心中埋下bug,比如参数、异常处理、日志等不规范都会让你背锅。优雅的代码读起来让人赏心悦目、通俗易懂,对后期重构也大有帮助。比如如下一段代码,看的你感觉如何?
if(db.Next()) {
return true;
} else {
return false;
}
这段代码不是我为了文章而编出来的,而是在工作中经常看到,如果这段代码改成如下,效果显而易见。
return db.Next();
关于如何写出优雅的代码,我有以下看法:
如果不格式化,很多字符符号之间连空格都没有,好不容易看到一个空格确是连续三四个空格。格式化代码使阅读更加方便,后期修改代码更容易发现修改处。
记住:代码要格式化!
if ("0000".equals(retCode)) {
sendMsg("0000", "success", result);
} else {
sendMsg("0000", "fail", result);
}
如上一段代码,if/else里面调用的方法一样,真正不一样的仅仅一个参数,我们可以简单优化为如下格式:
String msg = "0000".equals(retCode) ? "success" : "fail";
sendMsg("0000", msg, result);
这段代码中,条件判断真正有影响的区别是获取消息内容,而不是发送消息方法,所以我们要让判断条件条件做真正的选择,将获取消息内容和发送消息方法分开。
这样消除了代码中的冗余,代码也更容易理解,同时未来也更容易扩展。如果未来retCode变量有更多的值返回需要判断,我们只需要调整消息获取相关代码即可,这种情况下封装一下消息获取的相关代码更好,如下:
sendMsg("0000", getMsg(retCode), result);
这是一个长长的判断条件:
if ("DropGroup".equals(operationName)
|| "CancelUserGroup".equals(operationName)
|| "QFUserGroup".equals(operationName)
|| "CancelQFUserGroup".equals(operationName)
|| "QZUserGroup".equals(operationName)
|| "CancelQZUserGroup".equals(operationName)
|| "SQUserGroup".equals(operationName)) {
//业务逻辑代码
}
随着业务的增长,这段代码里的判断条件可能还会增加,代码越来越长。很多人坦然面对这段代码,可能是显示器足够大。如果一个方法里仅有该段if代码尚能接受,但是当一段代码中突然冒出一个十几行的if判断,真的很不优雅。这段代码可以优化如下:
private boolean shouldExecute(String operationName) {
return "DropGroup".equals(operationName)
|| "CancelUserGroup".equals(operationName)
|| "QFUserGroup".equals(operationName)
|| "CancelQFUserGroup".equals(operationName)
|| "QZUserGroup".equals(operationName)
|| "CancelQZUserGroup".equals(operationName)
|| "SQUserGroup".equals(operationName);
}
然后调用
if (shouldExecute(operationName)) {
//业务逻辑代码
}
现在,虽然条件依然还是很多,但比起原来庞大的函数,至少它已经被控制在一个相对较小的函数里了。更重要的是,通过函数名,我们终于有机会告诉世人这段代码判断的是什么了。
其实这段代码还可以进一步进行优化,如下:
private boolean shouldExecute(String operationName) {
String[] executeTypes = {"DropGroup",
"CancelUserGroup",
"QFUserGroup",
"CancelQFUserGroup",
"QZUserGroup",
"CancelQZUserGroup",
"SQUserGroup"};
for (String execute : executeTypes) {
if (execute.equals(operationName)) {
return true;
}
}
return false;
}
这样以后如果我们需要新增一个type,只需要在executeTypes数组中新增一个就可以了。而且将type列表变成声明式,进一步提高了代码的可读性。
通过以上列子,我们可以得出两个可以使代码更加优雅的小方法:
判断条件不准许多个条件的组合(一般不能出现3个条件及其以上),如果有的话建议提炼为一个方法;
参数、方法、变量等命名至关重要,成功的命名简单易懂,甚至注释都不需要;
这是一段你需要来找茬的代码:
if (1 == insertFlag) {
retList.insert(i, newCatalog);
} else {
retList.add(newCatalog);
}
if (1 == insertFlag) {
retList.insert(m, newCatalog);
} else {
retList.add(newCatalog);
}
if (1 == insertFlag) {
retList.insert(j, newPrivNode);
} else {
retList.add(newPrivNode);
}
以上3段代码,除了用到的变量不同之外,其它完全相同!
程序员特别擅长复制粘贴,以上这段代码第二个人复制第一个人写的,然后代码里重复代码特别多,后期修改和重构极易出现错误。复制一时爽,一直复制一时爽,但是缺少了自己的想法,这就是为什么有人说写代码是个体力活。比如以上代码,提出一个新的方法就可以了。
private void addNode(List retList, int insertFlag, int pos, Node node) {
if (1 == insertFlag) {
retList.insert(pos, Node);
} else {
retList.add(node);
}
}
于是,原来那三段代码变成了三个调用:
addNode(retList, insertFlag, i, newCatalog);
addNode(retList, insertFlag, m, newCatalog);
addNode(retList, insertFlag, j, newPrivNode);
重复,是最为常见的坏味道。上面这种重复实际上是非常容易发现的,也是很容易修改。
接到需求分析后,开始梭哈代码,第一行开始梭哈,梭哈第100行的时候还是在一个方法里,这样代码一个方法功能不够明确,看起来眼花缭乱。比如如下一段代码:
//业务代码片段A(假设有30行代码)--分析此人是否满足条件
Person person = new Person();
person.setIdNo(xx);
person.setAddress(xx);
person.setWork(xx);
person.setPhone(xx);
person.setQQ(xx);
person.setBirthday(xx);
person.setCity(xx);
person.setMale(xx);
person.setProvince(xx);
personDAO.insert(person);
//业务代码片段B(假设有30行代码)--返回相关信息
如上,有的人真的会直接写在一个方法里,这个方法大概就有80行左右的代码了。如果还有其他业务,这个代码里不停添加,最后会出现要给小山峰一般的代码。优雅如下:
private void insertPerson() {
Person person = new Person();
person.setIdNo(xx);
person.setAddress(xx);
person.setWork(xx);
person.setPhone(xx);
person.setQQ(xx);
person.setBirthday(xx);
person.setCity(xx);
person.setMale(xx);
person.setProvince(xx);
personDAO.insert(person);
}
private Person addPersonInfo() {
checkInsertPerson();//分析是否满足条件
Person person = insertPerson();//插入
return personInfo(person);//组合其他信息
}
谈论干净代码时,我们总会说,函数应该只做一件事,检查条件归检查条件,增加归增加。函数做的事越多,就会越冗长,也就越难发现不同函数的相似之处。为了一个问题,要在不同的地方改来改去也就难以避免了。面对长长的函数,不要再无动于衷了,不要继续往里塞着“新”代码。
不少人在分析业务的时候就考虑到了需要哪些变量,于是在方法的开头,就依次把需要的变量定义一番,如下:
private void addFruits(List fruits, String type) {
Apple apple = createApple();
Banana banana = createBanana();
if ("fruit".equals(type)) {
fruits.add(apple);
fruits.add(banana);
}
}
这段代码可以优雅如下:
private void addFruits(List fruits, String type) {
if ("fruit".equals(type)) {
fruits.add(createApple());
fruits.add(createBanana());
}
}
省出了变量的声明,如果你追求简单易懂的变量声明,这种方式会帮你节省一定的时间。关于变量我建议两点:在需要的地方才声明,不要开始就声明出来;适当的时候不用声明出来,何必要给自己增加麻烦。
另外增加一个常见的代码:
String gender;
if ("F".equals(sex)) {
gender = "女";
} else {
gender = "男";
}
这样的代码真的特别容易见到,可以优雅如下:
String gender = "F".equals(sex) ? "女":"男";
在我们的代码中,经常会出现一些状态判断,如下:
if ("DONE".equals(status) {
//业务逻辑代码
}
这样的代码,哪一天万一DONE改了个值,或者由于业务改变,状态DONE的时候还增加一个其他判断,所有的地方都要改,那你就有大事情要做了。
首先我们可以用函数封装固定值,如:
private String getDoneStatus() {
return "DONE";
}
if (getDoneStatus().equals(status) {
//业务逻辑代码
}
还有一种方法,既然DONE是固定值,我们判断的就是这个状态,索性直接封装,后面如果这个状态统一增加其他条件也比较方便,如:
private boolean isDoneStatus(String status) {
return "DONE".equals(status);
}
if ( isDoneStatus(status) {
//业务逻辑代码
}
关于如何写出优雅的代码,还有很多的小细节可以补充,这里就不一一列举了,感兴趣的可以自己查阅。