阿里巴巴的 Java 开发手册(黄山版)来了

社区征文

0. 阅读完本文你将会学会

  • 写出更优雅高效的Java代码

1. 前言

周六逛B乎的时候正好刷到这样一个问题 "Java开发手册(黄山版)怎么样?",我仔细一看这不是孤尽老师的著作吗?居然已经更新到了黄山版。

上次看这本小册子的时候还是上次——19年的时候我看的华山版的。再往前那就是17年的第一版了,当时是在阿里的公众号下载的,后来还买了实体的《Java开发手册》和《码出高效》两本书。

其实这本小册子并不是什么深度的内容,但是却让我受益匪浅——你写不出复杂高深的代码,但是至少能写出规范、干净、同事看了不喊“卧槽”而是喊“卧槽牛逼”的代码。

在这篇文章中我将会挑选几条手册中的编程规约做一个简单的导读。

友情提示,文末有手册下载方式哦。

对软件来说,适当的 规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,代码质 量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。

2. 编程规约导读

2.1 禁用魔法值

不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。

// 反例: 开发者 A 定义了缓存的 keyString key = "Id#taobao_" + tradeId; 
cache.put(key, value); 
// 开发者 B 使用缓存时直接复制少了下划线,
// 即 key"Id#taobao" + tradeId,导致出现故障。 
String key = "Id#taobao" + tradeId; 
cache.get(key);

魔法值指的是代码中没有任何定义,直接像魔法一样凭空出现的值,可以是数字、字符串等。

这是我印象中比较深的一条强制性规约。

当我刚入这行的开始写代码的时候,魔法值满天飞,怎么方便怎么来。根本不会考虑这样的问题,但是后来这样做的恶性后果也就出现了。

  • 重复性的魔法值,不够简洁,逼死喜欢复用的强迫症!
  • 容易出现像上面反例一样的错误,比如下划线少了啊或者一个单词拼错了。
  • 魔法值难以简明地阐述其含义。比如,代码中直接出现的"0"和"1",谁知道它的含义呢?

所以,我们是可以通过静态常量或者枚举来定义你的常量,这样就可以把魔法值消灭殆尽。

2.2 访问权限控制从严

类成员与方法访问控制从严。

  • 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
  • 工具类不允许有 public 或 default 构造方法。
  • 类非 static 成员变量并且与子类共享,必须是 protected。
  • 类非 static 成员变量并且仅在本类使用,必须是 private。
  • 类 static 成员变量如果仅在本类使用,必须是 private。
  • 若是 static 成员变量,考虑是否为 final。
  • 类成员方法只供类内部调用,必须是 private。
  • 类成员方法只对继承类公开,那么限制为 protected。

这条是推荐性编程规约,其实这样的规约正是体现了Java的特性之一——封装性。

对于任何类、方法、参数、变量,我们都应该严格控制其访问范围。太过宽泛的访问范围,不利于模块解耦。

我自己写代码的时候,也是private够用就用private。

孤尽在手册里提出了一个很有意思的问题:

如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?

他做了这样一个比喻:

变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。

我觉得这真是说到点子上了。

2.3 for循环中list禁用remove/add

不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式, 如果并发操作,需要对 iterator 对象加锁。

//正例: 
List list = new ArrayList<>(); 
    list.add("1"); 
    list.add("2"); 
    Iterator iterator = list.iterator(); 
    while (iterator.hasNext()) { 
        String item = iterator.next(); 
        if (删除元素的条件) { 
            iterator.remove(); 
        } 
    } 
//反例: 
for (String item : list) { 
    if ("1".equals(item)) { 
        list.remove(item); 
    } 
}

这条强制性规约的坑我也踩过,在反例中,当它执行了remove操作,会报如下错。

java.util.ConcurrentModificationException

具体的原因不在此文赘述,有兴趣的读者朋友可以网上查阅。

2.4 命名复杂布尔表达式

除常用方法(如 getXxx / isXxx)等外不要在条件判断中执行其它复杂的语句,将复杂逻辑判 断的结果赋值给一个有意义的布尔变量名,以提高可读性。

这条推荐性规约也是我推崇备至的。因为业务需要,我们可能在if语句中写出非常复杂的逻辑表达式。与、或、取反混合运算,甚至各种方法调用,理解起来非常难。

如果我们赋予这样一个逻辑表达式一个很好理解的名字(我觉得比注释更简洁易懂方便),则是一件令人赏心悦目的事情。

我们来看一个对比的例子:

正例: 
// 伪代码如下 
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); 
    if (existed) { ... } 
反例: 
public final void acquire(long arg) { 
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
        selfInterrupt();
    } 
}

2.5 异常处理

catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定 代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。

看到这条强制性规约的时候,我老脸一红。

因为我曾经也犯过这样的错——对大段代码进行try-catch,这样做会使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。

用户注册的场景中,如果用户输入非法字符,或用户名称已存在,或用户输入密码过于简单,我们应该在程序上作出分门别类的判断,并提示给用户。

2.6 日志规约

生产环境禁止使用 System.out 或 System.err 输出或使用 e.printStackTrace() 打印异常堆栈。

使用e.printStackTrace() 打印日志容易占用太多内存,造成锁死。

要打印字符串输出到控制台上,需要字符串常量池所在的内存块有足够的空间。然而,因为e.printStackTrace() 语句要产生的字符串记录的是堆栈信息,太长太多,内存被填满了!大量线程产出字符串产出到一半,等待有内存被释放,锁死了,导致整个应用挂掉了。

另外,日志交错混合,不易读。

printStackTrace()默认使用了System.err输出流进行输出,与System.out是两个不同的输出流,那么在打印时自然就形成了交叉。再就是输出流是有缓冲区的,所以对于什么时候具体输出也形成了随机。

一般打印错误日志的时候我们都是用日志框架的log.error("",e),基本够用了。

2.7 数据库

小数类型为 decimal,禁止使用 float 和 double。

这是一条强制性规约,在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。

如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。

3.结语

以上是我从手册中摘录的几条规约,加之一些简单的导读。不知道各位看官老爷们有没有一些似曾相识的感觉呢?

手册一共有七个章节,基本上囊括了Java程序员写代码的方方面面。

如何获取手册,写出更优雅高效的代码,请关注我的公众号 "花园野人",回复 "pdf1",即可获取孤尽老师的Java开发手册。

感谢收看本期的翊君@周一电台。如果你觉得还不错的话,快给我三连支持一下吧,咱们下期不见不散呐。

402
2
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论