阿里巴巴Java开发手册(纪念版 1.3.1)记录

阿里巴巴Java开发手册(纪念版 1.3.1)

一、OOP规约

1.【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

2.【强制】不能使用过时的类或方法。
​ 说明: java.net.URLDecoder中的方法 decode(String encodeStr) 这个方法已经过时,应该使用双参数 decode(String source, String encode)

3.【强制】 Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
​ 正例: "test".equals(object);
​ 反例: object.equals("test");

4.【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较
​ 说明: 对于 Integer var = ? 在-128 至 127 范围内的赋值, Integer 对象是在IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行
判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,
推荐使用 equals 方法进行判断。

5.关于基本数据类型与包装数据类型的使用标准如下:

1) 【强制】 所有的 POJO 类属性必须使用包装数据类型。
2) 【强制】 RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】 所有的局部变量使用基本数据类型

6.【强制】定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
​ 反例: POJO 类的 gmtCreate 默认值为 new Date();但是这个属性在数据提取时并没有置入具体值,在更新其它字段时又附带更新了此字段,导致创建时间被修改成当前时间

7.【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
​ 说明: 注意 serialVersionUID 不一致会抛出序列化运行时异常

8.【推荐**】使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException 的风险。**
​ 说明:
String str = “a,b,c,”;
String[] ary = str.split(",");
//预期大于 3,结果是 3
System.out.println(ary.length);

9.【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

10.【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。
​ 说明: 反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

11.【推荐】慎用 Object 的 clone 方法来拷贝对象。
​ 说明: 对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象的拷贝。


二、命名风格

1.【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
​ 反例: _name / __name / O b j e c t / n a m e / n a m e Object / name_ / name Object/name/name / Object$

2.【强制】 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式

​ 说明: 正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式
也要避免采用。

3.【强制】类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外: DO / BO /
DTO / VO / AO
​ 正例: MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
​ 反例: macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

4.【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从
驼峰形式。
正例: localValue / getHttpMessage() / inputUserId

5.【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

6.【强制】抽象类命名使用 Abstract 或 Base 开头异常类命名使用 Exception 结尾测试类
命名以它要测试的类的名称开始,以 Test 结尾

7.【强制】 POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误
​ 反例: 定义为基本数据类型 Boolean isDeleted; 的属性,它的方法也是 isDeleted(), RPC框架在反向解析的时候, “以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异
常。

8.【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。
​ 说明: 将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

正例: public class OrderFactory;
​ public class LoginProxy;
​ public class ResourceObserver;

9.【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
​ 正例: CacheServiceImpl 实现 CacheService 接口。

10.【参考】枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明: 枚举其实就是特殊的常量类,且构造方法被默认强制是私有
正例: 枚举名字: DealStatusEnum, 成员名称: SUCCESS / UNKOWN_REASON。


三、常量定义

1.【强制】 long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l,小写容易跟数字1 混淆,造成误解。
​ 说明: Long a = 2l; 写的是数字的 21,还是 Long 型的 2?

2.【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护(Consts)。如:缓存相关的常量放在类: CacheConsts 下; 系统配置相关的常量放在类: ConfigConsts 下。
​ 说明: 大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

3.【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量
​ 1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下
​ 2) 应用内共享常量:放置在一方库的 modules 中的 constant 目录下
​ 反例: 易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:
​ 类 A 中: public static final String YES = “yes”;
​ 类 B 中: public static final String YES = “y”;
​ A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。
​ 3) 子工程内部共享常量:即在当前子工程的 constant 目录下
​ 4) 包内共享常量:即在当前包下单独的 constant 目录下
​ 5) 类内共享常量:直接在类内部 private static final 定义

4.【推荐】如果变量值仅在一个范围内变化,且带有名称之外的延伸属性, 定义为枚举类。下面正例中的数字就是延伸信息,表示星期几。
​ 正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6),SUNDAY(7);}


四、集合处理

  1. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:

​ 1) 只要重写 equals,就必须重写 hashCode
​ 2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法
​ 3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals
​ 说明: String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。

2.【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList
​ 说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。

  1. 【强制】 在 subList 场景中, 高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生 ConcurrentModificationException 异常

  2. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。

​ 说明: 使用 toArray 带参方法,入参分配的数组空间不够大时, toArray 方法内部将重新分配内存空间,并返回新数组地址; 如果数组元素大于实际所需,下标为[ list.size() ]的数组
元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。
​ 正例:
​ List list = new ArrayList(2);
​ list.add(“guan”);
​ list.add(“bao”);
​ String[] array = new String[list.size()];
​ array = list.toArray(array);
​ 反例: 直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

  1. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

​ 说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList
​ 体现的是适配器模式,只是转换接口,后台的数据仍是数组。
​ String[] str = new String[] { “a”, “b” };
​ List list = Arrays.asList(str);
​ 第一种情况: list.add(“c”); 运行时异常。
​ 第二种情况: str[0] = “gujin”; 那么 list.get(0)也会随之修改。

  1. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁

​ 正例:

Iterator<String> it = a.iterator();while (it.hasNext()) {
​      String temp = it.next();if (删除元素的条件) {
​        it.remove();}}

​ 反例:

​    List<String> a = new ArrayList<String>();
​    a.add("1");
​    a.add("2");for (String temp : a) {if ("1".equals(temp)) {
​         a.remove(temp);}}
  1. 【推荐】集合初始化时, 指定集合初始值大小

    ​ 说明: HashMap 使用 HashMap(int initialCapacity) 初始化,
    ​ 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loaderfactor) 默认为 0.75, 如果暂时无法确定初始值大小, 请设置为 16
    ​ 反例: HashMap 需要放置 1024 个元素, 由于没有设置容量初始大小,随着元素不断增加,容量 7 次被迫扩大, resize 需要重建 hash 表,严重影响性能。

  2. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历

​ 说明: keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
​ 正例: values()返回的是 V 值集合,是一个 list 集合对象; keySet()返回的是 K 值集合,是一个 Set 集合对象; entrySet()返回的是 K-V 值组合集合

  1. 【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains 方法进行遍历、对比、 去重操作。

五、控制语句

  1. 【强制】在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止; 在一个 switch 块内, 都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。

  2. 【强制】在 if/else/for/while/do 语句中必须使用大括号。 即使只有一行代码,避免使用单行的形式: if (condition) statements;

  3. 【推荐】 表达异常的分支时, 少用 if-else 方式, 这种方式可以改写成卫语句的形式:

    if (condition) {
		 return;}

然后接着使用卫语句的形式写 else 的业务逻辑代码;
如果非得使用 if()…else if()…else…方式表达逻辑,【强制】 避免后续代码维护困难, 请勿超过 3 层

案例:逻辑上超过 3 层的 if-else 代码可以使用卫语句,或者状态模式来实现。 卫语句示例如下

		public void today() {
           if (isBusy()) {
             System.out.println(“change time.);
              return;
            }
           if (isFree()) {
             System.out.println(“go to travel.);
              return;
           }
           System.out.println(“stay at home to learn Alibaba Java Coding Guideline.);
           return;
        }
  1. 【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
    说明: 很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
    正例:
//伪代码如下final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);if (existed) {...}

反例:

		if ((file.open(fileName, "w") != null) && (...) || (...)) {...
		}
  1. 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch 操作(这个 try-catch 是否可以移至循环体外) 。

  2. 【推荐】接口入参保护,这种场景常见的是用于做批量操作的接口。

六、注释规约

  1. 【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用//xxx 方式。
    ​ 说明: 在 IDE 编辑窗口中, Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注
    释; 在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

  2. 【强制】所有的抽象方法(包括接口中的方法) 必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
    ​ 说明: 对子类的实现要求,或者调用注意事项,请一并说明。

  3. 【强制】所有的类都必须添加创建者和创建日期。

  4. 【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。

  5. 【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。

  6. 【推荐】与其“半吊子”英文来注释,不如用中文注释把问题说清楚。专有名词与关键字保持英文原文即可。
    ​ 反例: “TCP 连接超时”解释成“传输控制协议连接超时”,理解反而费脑筋。

  7. 【推荐】代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
    ​ 说明: 代码与注释更新不同步,就像路网与导航软件更新不同步一样,如果导航软件严重滞后,
    ​ 就失去了导航的意义。

  8. 【参考】合理处理注释掉的代码。 在上方详细说明,而不是简单的注释掉。 如果无用,则删除。
    ​ 说明: 代码被注释掉有两种可能性: 1) 后续会恢复此段代码逻辑。 2) 永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉(代码仓库保存了历史代码) 。

  9. 【参考】对于注释的要求:第一、能够准确反应设计思想和代码逻辑; 第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路; 注释也是给继任者看的,使其能够快速接替自己的工作

七、日志规约

  1. 【强制】应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
		  import org.slf4j.Logger;import org.slf4j.LoggerFactory;private static final Logger logger = LoggerFactory.getLogger(Abc.class);
  1. 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

  2. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:appName_logType_logName.log。 logType:日志类型,推荐分类有
    ​ stats/desc/monitor/visit 等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
    ​ 正例: mppserver 应用中单独监控时区转换异常,如:mppserver_monitor_timeZoneConvert.log
    ​ 说明: 推荐对日志进行分类, 如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。

  3. 【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
    ​ 说明: logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
    ​ 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,
    ​ 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
    ​ 反例: (条件)

		if (logger.isDebugEnabled()) {
    ​         logger.debug("Processing trade with id: " + id + " symbol: " + symbol);}

正例: (使用占位符 {}

logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
  1. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
    正例: <logger name="com.taobao.dubbo.config" additivity="false">

  2. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
    ​ 正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

  3. 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
    ​ 说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 记录日志时请

  4. 【参考】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别, error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必 要,请不要在此场景打出 error 级别。

八、其它

1.【强制】 velocity 调用 POJO 类的属性时,建议直接使用属性名取值即可, 模板引擎会自动按规范调用 POJO 的 getXxx(),如果是 boolean 基本数据类型变量(boolean 命名不需要加 is前缀) ,会自动调用 isXxx()方法。
​ 说明: 注意如果是 Boolean 包装类对象,优先调用 getXxx()的方法。

2.【强制】注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0≤x<1(能够取到零值,注意除零异常) , 如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后
​ 取整,直接使用 Random 对象的 nextInt 或者 nextLong 方法。

3.【强制】获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
​ 说明: 如果想获取更加精确的纳秒级时间值, 使用 System.nanoTime()的方式。在 JDK8 中,
​ 针对统计时间等场景,推荐使用 Instant 类。

  • 170
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值