蘭陵N梓記

一指流沙,程序年华


  • 首页

  • 归档

  • 关于

  • 搜索
close

飞哥讲代码12:好代码应表意直白

时间: 2020-08-15   |   分类: 技术     |   阅读: 2214 字 ~5分钟

案例

下面代码都来源于部门某一中间件产品(java)的源码,代码风格(此风格非格式风格而是逻辑思维风格)并且在整个源码中具有普遍性。

代码一:

public void run() {
    while(!(this.stopped.get())) {
        try {
            synchronized (this.lock) {
                while (this.endTime - System.currentTimeMills() > 0L) {
                    try {
                        if (this.stopped.get()) {
                            return;
                        }
                        this.lock.wait(this.endTime - System.currentTimeMillis());
                        // 省略主要业务逻辑
                    } catch (IntrruptedExecption e) {
                        // 省略日志打印
                    }
                } // 注意:这个while之后并没有其它的逻辑
            }
        } catch(Throwable e) {
            // 省略日志打印
        }
    }
}

代码意思是可能在等待的最大时间内,中间可以被通知执行主逻辑,然后再进入等待下次通知。在易读性上的问题:

  • 双重while, 双重try/catch,增加代码嵌套层次,代码有六层,由于跨度较大,掩盖了要表达的业务逻辑,不容易看懂。
  • 第一个while判断是 否定之否定 判断,不够直接,stoppped不需采AtomicBoolean,使用volatile变量即可。
  • 代码有bug(嵌套太深隐藏了bug),当超过最大时间时,若没有设置stopped标识位,空循环占CPU。

建议优化:

  • 由于synchronized是对lock的wait方法同步,wait后面的逻辑并不需要再同步保护,不应该锁整个while,减少锁的粒度。可以对wait逻辑单独抽取一个方法,直白表示是要waitNotify。
  • 去掉否定之否定。把AtomicBoolean stopped变成volatile boolean running,判断更直白,running表示还得继续。
public void run () {
    while(this.running) {
        try {
            if ( waitNotify() ) {
                // 省略主要业务逻辑
            }
        } catch(Throwable e) {
            // 省略日志打印
        }
    }
}

// 新增方法,表意是要等通知
private boolean waitNotify() {
    if (this.endTime - System.currentTimeMills() <= 0L) {
        this.running = false; // 超时,避免空循环
        return false;
    }

    if (!this.running) {
        return false;
    }

    try {
        synchronized (this.lock) {
            this.lock.wait(this.endTime - System.currentTimeMillis());
        }
        return true;
    } catch (IntrruptedExecption e) {
        // 省略日志打印 
        return false;
    }
}

代码二:

public boolean deleteFile(File file)
    if (file.isFile() && file.exists()) {
        bool flag = file.delete()
        if (flag) {
            return true;
        } else {
            return false;
        }
    } else {
        return false;
    }
}

上面的代码的问题比较明显:

  • 存在废话

建议优化:

public boolean deleteFile(File file)
    if (file.isFile() && file.exists()) {
        return file.delete()  
    }
    return false; // 其实这个false与上面file.delete()返回false含义不一样,本文不深究这个问题
}

代码三:

    switch(getMessageResult().getStatus()) {
        case FOUND: {
            while (iter.hasNext()) {
                SelectMapedBufferResult selectMapedBuffer = iter.next();
                try {
                     // 省略其它业务逻辑
                } finally {
                    selectMapedBuffer.release();
                }
            }

            messageList.addAll(mqMessageList);
            break;
        }
        case OFFSET_TOO_SMALL:
        // 省略其它的CASE处理逻辑
    }

上面的代码问题主要是:

  • switch中嵌套while,若while循环中一个break,它到底是break while还是break switch,不一小心就是产生一个bug。

解决上述问题也比较简单:

  • 对于switch分支,每个分支的处理逻辑应该提取函数。

建议优化:

    switch(getMessageResult().getStatus()) {
        case FOUND: 
            processFound(...)
            break;
        case OFFSET_TOO_SMALL:
        // 省略其它的CASE处理逻辑
    }

背后的知识

上述代码表面上的共性问题:

  • 代码层次嵌套比较深,容易产生bug;也不容易读懂代码,甚至隐藏问题。
  • 代码表达意图不够简洁,代码一写得比较绕,代码二存在废话,代码三存在break错误风险。

减少嵌套层次,降低代码复杂度是老生常谈的问题,也是大牛们经常提到的问题:

If you need more than 3 levels of indentation, you’re screwed anyway, and should fix your program.

Linus曾经说过:如果你的代码里需要有超过三层的缩进,那么你已经搞砸了,应该修改你的代码。

在C语言中,Linus的话应该是没有问题的,对于其它的语言要求过高。在我司的编程规范,也建议嵌套层次不要超过4层。

控制与逻辑分离

嵌套层次过深,表面上可能业务逻辑复杂,表现为控制流程太多过深;深层次可能的原因是我们思维方式引起的,正常的人思维肯定按顺序思考方式:先考虑正常情况,再考虑异常情况,依次递进地再思考。

我把嵌套过深分为四类,从代码现象来看,都是由于控制语句引起的复杂。

  • if/else引嵌套。
  • for/while嵌套。
  • try/catch嵌套。
  • 上述的组合嵌套。

总结的好处是可以给我带来新的思考,我们写代码的思维逻辑可不可以不是顺序的,而是站在更高一层来看,总体原则是否可能把控制与逻辑分离:

  • 控制: 即上述说的各个控制语名,它只是控制你的主要逻辑的走向。
  • 逻辑: 才是程度真实要执行的代码,是业务主要完成的功能。

如果我们能把 控制 与 逻辑 部分有效的分开,那么代码将会变得更加容易看懂、维护和改进。

对于 控制 的优化,则方法要根据不同控制语句而论:

if/else 的优化

  • 使用卫语句,异常条件优先返回
if () {
    if () {
        if () {
            // ...
        }
    } else if() {

    }
}

可优化为:

if (!...) {
    return ;
}

if (...) {
    return ;
}

if (...) {
    ....
}
  • 把嵌套的 if 换成 if {} else if {} 语句
  • 将 if 换成 case 语句
  • 优先使用&&,在可以使用&&条件判断的地方要避免使用连续嵌套的if
  • 将深层嵌套抽出来放到子函数

for/while优化

  • 采用使用break,continue改变控制流程
for(int a : list) {
    if (...) {
       //
    }
}

优化为:

for(int a : list) {
    if (!...) { // 反着写
        continue; 
    }
}

Switch优化

  • 不要根据类型标签进行分支,而要优先使用多态函数。
  • 采用表驱动查找,每个类型标签分支对应一个。

try/catch优化:

  • 嵌套的try/catch表明你没有干净地编码,说明他们不在一个层次,肯定要把内部的try/catch的逻辑提取函数了。

对于 逻辑 的分开,最直接办法就是提取函数:

  • 复杂的if/else内的逻辑提取函数
  • for/while循环体内的逻辑提取函数
  • switch的分支处理逻辑提到函数

更复杂的 逻辑 分离,则需要采用一些设计手段,如状态机模式与策略模式的核心思维都是把逻辑分散化,不同的处理逻辑分布到各个子类中。

结语

干净的代码首先肯定是易读的代码,直接白了的对话大家都喜欢,同样代码不应该隐藏其所表达的意图,而是表意直白,不需要让读者太多的思考。代码嵌套非常影响易读性,还会带来退出分支过多,需要人思考的方面也就越多,稍不留神就会搞出大Bug。减少嵌套层次,降低代码复杂度,值得你去追求。

#软件开发# #java#
飞哥讲代码13:好代码须匠心打磨
飞哥讲代码11:通过封装降低耦合
微信扫一扫交流

标题:飞哥讲代码12:好代码应表意直白
作者:兰陵子
关注:lanlingthink(览聆时刻)
声明:自由转载-非商用-非衍生-保持署名(创作共享3.0许可证)

  • 文章目录
  • 站点概览
兰陵子

兰陵子

Programmer & Architect

164 日志
4 分类
57 标签
GitHub 知乎
  • 案例
    • 背后的知识
  • 控制与逻辑分离
  • 结语
© 2009 - 2022 蘭陵N梓記
Powered by - Hugo v0.101.0
Theme by - NexT
0%