读源码时的思考

最近在看concurrent包下线程池的源码,当我看到ThreadPoolExecutor类的时候,发现了JDK源码的一个问题。以下是ThreadPoolExecutor类的addWorker方法的代码片段:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;

这段代码的功能是完全没有问题的,但是如果使用卫语句,代码的可读性就会更高了。那么什么是卫语句呢?

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

什么是卫语句?

条件表达式通常有两种表现形式,第一种形式是:所有分支都属于正常行为;第二种形式则是:条件表达式提供的答案中只有一种是正常行为,其他都是不常见的情况。这两类条件表达式有不同的用途,这一点应该通过代码表现出来。

如果两条分支都是正常行为,就应该使用形如if...else...的条件表达式;如果某个条件极其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为“卫语句”(guard clauses)。只看概念干巴巴的,不好理解,我们来举两个例子。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

条件检查替换

这是一个计算员工薪资的方法,其中以特殊规则处理驻外员工和退休员工的薪资。这些情况不常有,但的确会偶尔出现。

public double getSalary() {
double result;
if (this.isSeparated) {//驻外员工
result = this.separatedSalary();
} else {
if (this.isRetired) {//退休员工
result = this.retiredSalary();
} else {//正常员工
result = this.normalSalary();
}
}
return result;
}

这段代码中,非正常情况的检查掩盖了正常情况的检查,所以应该用卫语句来取代这些条件检查,以提高程序清晰度。对于每个检查,放进一个卫语句。卫语句要不就从函数中返回,要不就抛出一个异常。


public double getSalary() {
if (this.isSeparated) {//卫语句
return this.separatedSalary();
}
if (this.isRetired) {//卫语句
return this.retiredSalary();
}
return this.normalSalary();
}

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

反转条件替换

这是一个已知长宽高求长方体体积的方法,但有个特殊的需求:高大于0时,打印万猫学社。(惊不惊喜?意不意外?突不突兀?变不变态?是的,有时候我们接到的需求就是这样的。)代码是这样的:

public double getVolume(double length, double width, double height) {
double result = 0.0;
if (height > 0.0) {
System.out.println("万猫学社");
if (length > 0.0 && width > 0.0) {
result = length * width * height;
}
}
return result;
}

还是用卫语句替换条件检查,但是我们需要将相应的条件反转过来,也就是做逻辑非运算。

public double getVolume(double length, double width, double height) {
if (height <= 0.0) {//卫语句,!(height > 0)
return 0.0;
}
System.out.println("万猫学社");
if (length <= 0.0 || width <= 0.0) {//卫语句,!(length > 0 && width > 0)
return 0.0;
}
return length * width * height;
}

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

为什么要使用卫语句?

卫语句的精髓是:给某一条分支以特别的重视。如果使用if...else...结构,你对if分支和else分支的重视是同等的。这样的代码结构传递给阅读者的消息就是:各个分支有同样的重要性。

卫语句就不同了,它告诉阅读者:“这种情况很罕见,如果它真的发生了,请做一些必要的整理工作,然后退出。”如果对方法剩余部分不再有兴趣,当然应该立刻退出。引导代码的阅读者去看一个没有用的else区段,只会妨碍他们的理解。用了卫语句以后,代码更容易被理解,被维护。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

优化JDK代码

看了上面的讲解,addWorker方法的代码片段就可以优化为:

boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t == null) {//卫语句
return workerStarted;
} final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get()); //卫语句
if (rs > SHUTDOWN || (rs == SHUTDOWN && firstTask != null)) {
return workerStarted;
}
if (t.isAlive())// precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
} } finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;

修改后的代码,理解起来是不是更容易了?假如再加入新的功能,可以更容易修改代码。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

结语

这段JDK源码在功能上没有任何问题,架构设计也堪称完美,不过我认为在可读性上还是可以优化的。类似这样的嵌套条件表达式的代码在JDK源码中不止这一处,可能是作者当时没有考虑到使用卫语句,或者没有像我这样的吹毛求疵。希望大家在自己编码过程中,也可以尽量使用卫语句取代嵌套条件表达式,以提高代码的清晰度和可维护性。

欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。

参考文献:《重构:改善既有代码的设计》

震惊!我竟然发现了JDK源码的问题的更多相关文章

  1. 调试过程中发现按f5无法走进jdk源码

    debug 模式 ,在fis=new FileInputStream(file); 行打断点 调试过程中发现按f5无法走进jdk源码 package com.lzl.spring.test; impo ...

  2. 重新编译jdk源码,启用debug信息

    我有一个不知道是好还是不好的习惯,搞不懂的一些玩意儿,喜欢调试然后单步执行看这玩意儿到底是怎么运行的. 今天看到正则表达式的时候,appendReplacement()这个方法怎么也看不明白它是怎么工 ...

  3. eclipse下导入jdk源码

    一直想好好看看jdk的源码,虽然可以直接解压jdk下的src看,但是终究不方便!后来发现可以导入到eclipse中,就在网上找了一些方法,下面就和大家分共享: step1:打开eclipse选择Win ...

  4. Timer的故事----Jdk源码解读

    咱们今天也来说说定时器Timer Timer是什么? Timer  n. [电子] 定时器:计时器:计时员 从翻译来看,我们可以知道Timer的本意是,定时定点. 而JDK中Timer类也的确是这个本 ...

  5. JDK源码包结构分类

    最近查看JDK源码时,无意间发现几个类在陌生包里:com.sun.*.sun.*.org.*,google了一把总结了下以备他人搜索,如内容有误欢迎指正!   Jre库包含的jar文件(jdk1.6) ...

  6. jdk源码调试功能

    JDK源码重新编译——支持eclipse调试JDK源码--转载 最近在研究jdk源码,发现debug时无法查看源码里的变量值. 因为sun提供的jdk并不能查看运行中的局部变量,需要重新编译一下rt. ...

  7. JDK源码重新编译——支持eclipse调试JDK源码--转载

    最近在研究jdk源码,发现debug时无法查看源码里的变量值. 因为sun提供的jdk并不能查看运行中的局部变量,需要重新编译一下rt.jar. 下面这六步是编译jdk的具体步骤: Step 1:   ...

  8. JDK源码学习--String篇(二) 关于String采用final修饰的思考

    JDK源码学习String篇中,有一处错误,String类用final[不能被改变的]修饰,而我却写成静态的,感谢CTO-淼淼的指正. 风一样的码农提出的String为何采用final的设计,阅读JD ...

  9. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

随机推荐

  1. SpringBootSecurity学习(22)前后端分离版之OAuth2.0自定义授权码

    使用JDBC维护授权码 前面的代码中,测试流程第一步都是获取授权码,然后再携带授权码去申请令牌,授权码示例如下: 产生的授权码默认是 6 位的,产生以后并没有做任何管理,可以说是一个临时性的授权码,o ...

  2. springboot使用百度富文本UEditor遇到的问题一览(springboot controller中request.getInputStream无法读取)

    先吐槽一下UEditor作为一个前端的js类库,非要把4种后端的代码给出来,而实际生产中用的框架不同,其代码并不具有适应性.(通常类似其它项目仅仅是给出数据交互的规范.格式,后端实现就可以自由定制) ...

  3. 有了这套模板,女朋友再也不用担心我刷不动 LeetCode 了

    全文包含 12000+ 字.30 张高清图片,预计阅读时间为 40 分钟,强烈建议先收藏再仔细阅读. 作者 | 李威 整理 | 公众号:五分钟学算法 个人博客 | https://www.cxyxia ...

  4. Redis之安装配置(Windows)

    下载 下载地址:https://github.com/MSOpenTech/redis/releases 解压,文件的名字重命名为redis. 打开文件夹,内容如下: 发送redis-server.e ...

  5. MFC下使用Mysql

    MFC工程需要使用Mysql数据库中遇到的问题记录下. 1.首先去官网下载下Mysql安装包,安装下Mysql. 我在这边下载的是mysql-installer-community-5.7.16.0. ...

  6. Python调用GithubAPI并进行初步的数据分析

    找到一个Github 上的公开api url = 'https://api.github.com/search/repositories?q=language:python&sort=star ...

  7. Python的字符串编码

    本文用实验详细地演示了Python2和Python3在字符串编码上的区别. 在Python2中,字符串字面量对应于8位的字符或面向字节编码的字节字面量.这些字符串的一个重要限制是它们无法完全地支持国际 ...

  8. php服务器有哪些

    服务器按照功能可以分为:文件服务器.数据库服务器.web服务器.邮件服务器.代理服务器..... 而上述所有的服务器,均可以用php做开发,比如说做web服务器,常用的构架是php+Mysql+Apa ...

  9. Twitter-Snowflake:自增ID算法

    简介 Twitter 早期用 MySQL 存储数据,随着用户的增长,单一的 MySQL 实例没法承受海量的数据,后来团队就研究如何产生完美的自增ID,以满足两个基本的要求: 每秒能生成几十万条 ID ...

  10. [系列] go-gin-api 路由中间件 - 签名验证(七)

    目录 概览 MD5 组合 AES 对称加密 RSA 非对称加密 如何调用? 性能测试 PHP 与 Go 加密方法如何互通? 源码地址 go-gin-api 系列文章 概览 首先同步下项目概况: 上篇文 ...