写出优雅的代码

《代码整洁之道》一书中说:当你的代码在做 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();

关于如何写出优雅的代码,我有以下看法:

1、代码格式化

如果不格式化,很多字符符号之间连空格都没有,好不容易看到一个空格确是连续三四个空格。格式化代码使阅读更加方便,后期修改代码更容易发现修改处。

记住:代码要格式化!

2、让判断条件做真正的选择

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);

3、判断条件的简而言之

这是一个长长的判断条件:

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个条件及其以上),如果有的话建议提炼为一个方法;

参数、方法、变量等命名至关重要,成功的命名简单易懂,甚至注释都不需要;

4、copy带来的重复

这是一段你需要来找茬的代码:

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);

重复,是最为常见的坏味道。上面这种重复实际上是非常容易发现的,也是很容易修改。

5、堆代码堆出了一座山

接到需求分析后,开始梭哈代码,第一行开始梭哈,梭哈第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);//组合其他信息
}

谈论干净代码时,我们总会说,函数应该只做一件事,检查条件归检查条件,增加归增加。函数做的事越多,就会越冗长,也就越难发现不同函数的相似之处。为了一个问题,要在不同的地方改来改去也就难以避免了。面对长长的函数,不要再无动于衷了,不要继续往里塞着“新”代码。

6、不要随意定义一个变量

不少人在分析业务的时候就考虑到了需要哪些变量,于是在方法的开头,就依次把需要的变量定义一番,如下:

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) ? "女":"男";

7、封装你的固定值

在我们的代码中,经常会出现一些状态判断,如下:

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) {
    //业务逻辑代码
}

关于如何写出优雅的代码,还有很多的小细节可以补充,这里就不一一列举了,感兴趣的可以自己查阅。

你可能感兴趣的:(java原理)