Friday, June 28, 2019

Alibaba Java Coding Guidelines



https://alibaba.github.io/Alibaba-Java-Coding-Guidelines/
https://www.hollischuang.com/archives/3000

4. [Recommended] Do not use complicated statements in conditional statements (except for frequently used methods like getXxx/isXxx). Use boolean variables to store results of complicated statements temporarily will increase the code’s readability.
Note:
Logic within many if statements are very complicated. Readers need to analyze the final results of the conditional expression to decide what statement will be executed in certain conditions.
Positive example:
// please refer to the pseudo-code as follows 
boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
    ...
}  
Counter example:
if ((file.open(fileName, "w") != null) && (...) || (...)) {
    ...
}

3. [Mandatory] Variables must add exclamatory mark when passing to velocity engine from backend, like $!{var}.
Note:
If attribute is null or does not exist, ${var} will be shown directly on web pages.


1. [Mandatory] Do not catch Runtime exceptions defined in JDK, such as NullPointerException and IndexOutOfBoundsException. Instead, pre-check is recommended whenever possible.
Note:
Use try-catch only if it is difficult to deal with pre-check, such as NumberFormatException.
Positive example:
if (obj != null) {...}
Counter example:
try { obj.method() } catch(NullPointerException e){…}
2. [Mandatory] Never use exceptions for ordinary control flow. It is ineffective and unreadable.
3. [Mandatory] It is irresponsible to use a try-catch on a big chunk of code. Be clear about the stable and unstable code when using try-catch. The stable code that means no exception will throw. For the unstable code, catch as specific as possible for exception handling.
4. [Mandatory] Do not suppress or ignore exceptions. If you do not want to handle it, then re-throw it. The top layer must handle the exception and translate it into what the user can understand.
5. [Mandatory] Make sure to invoke the rollback if a method throws an Exception.
6. [Mandatory] Closeable resources (stream, connection, etc.) must be handled in finally block. Never throw any exception from a finally block.
Note:
Use the try-with-resources statement to safely handle closeable resources (Java 7+).
7. [Mandatory] Never use return within a finally block. A return statement in a finally block will cause exceptions or result in a discarded return value in the try-catch block.
8. [Mandatory] The Exception type to be caught needs to be the same class or superclass of the type that has been thrown.

10. [Recommended] One of the most common errors is NullPointerException. Pay attention to the following situations:
  1) If the return type is primitive, return a value of wrapper class may cause NullPointerException.
    Counter example: public int f() { return Integer } Unboxing a null value will throw a NullPointerException.
  2) The return value of a database query might be null.
  3) Elements in collection may be null, even though Collection.isEmpty() returns false.
  4) Return values from an RPC might be null.
  5) Data stored in sessions might by null.
  6) Method chaining, like obj.getA().getB().getC(), is likely to cause NullPointerException.
    Positive example: Use Optional to avoid null check and NPE (Java 8+).

12. [Recommended] Do not throw RuntimeExceptionException, or Throwable directly. It is recommended to use well defined custom exceptions such as DAOExceptionServiceException, etc.
1. 【强制】 Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避,而不应该
通过 catch 来处理,比如: IndexOutOfBoundsException , NullPointerException 等等。
说明:无法通过预检查的异常除外,如在解析一个外部传来的字符串形式数字时,通过 catch
NumberFormatException 来实现。
正例: if (obj != null) {...}
反例: try { obj.method() } catch (NullPointerException e) {...}
https://www.dev-heaven.com/posts/37317.html
  1. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
    例:应用工具类包名为com.alibaba.open.util、类名为MessageUtils(此规则参考 spring 的框架结构)
  2. 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。
    例:BeanFactory,OrderBuilder

12. [Recommended] Do not throw RuntimeExceptionException, or Throwable directly. It is recommended to use well defined custom exceptions such as DAOExceptionServiceException, etc.

6. [Mandatory] Since NullPointerException can possibly be thrown while calling the equals method of Objectequals should be invoked by a constant or an object that is definitely not null.
Positive example:
"test".equals(object);
Counter example:
object.equals("test");
Note:
java.util.Objects#equals (a utility class in JDK7) is recommended.

https://www.ctolib.com/topics-110428.html
  • POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。假设定义一个 boolean 的 isSuccess 属性,它的方法 Getter 被IDE生成为 isSuccess(),RPC等三方框架在反向解析的时候,“以为”对应的属性名称是 success,导致属性获取不到,进而抛出异常。这点也是笔者之前遇到过的,查了很久哪里的错最后发现是这个问题,不过经历一次后基本后面就能避免。
  • 接口类中的方法和属性不要加任何修饰符号。包括在一些开源的代码里,笔者也经常看见在接口方法上声明 public 关键字的,这是冗余的,在Java规范中提到过。关于代码的规范及简洁性诸位可以参考 《重构 改善既有代码的设计》 及 代码整洁之道 。
  • 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。不过没有必要插入多行空格进行隔开。这样可读性会明显提高,笔者经常看到部分开发人员的代码在很长的代码块里完全没有一个空行,没有按逻辑进行换行,这种习惯是不太好的。
  • 所有的覆写方法,必须加 @Override 注解。这样IDE会检查合法性,有错误的话会及时提示。
  • 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。比如 Integer 的-128至127之间被缓存的对象可以直接使用==判断,因为被缓存了,是同一对象,地址相等,而这个区间外的却不能使用==判断,这也是面试时的一个常考点。
  • 关于基本数据类型与包装数据类型:所有的POJO类属性必须使用包装数据类型,以便映射数据库中的NULL,局部变量推荐使用基本数据类型。
  • 关于 hashCode 和 equals 的处理,遵循如下规则:只要重写 equals,就必须重写 hashCode,具体原因可参考 《Effective java 中文版(第2版)》 。
  • 关于 ArrayList 里 subList 结果的注意事项,subList 只是 ArrayList 的一个视图,这部分大家可以参考JDK里的源码。
  • 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
  • 在 JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort, Collections.sort 会报 IllegalArgumentException 异常。这个在《Effective java 中文版(第2版)》中也有说明,虽然笔者之前看过,但在刚实习时的一个用于省份排序的代码里使用 Comparator 时还是忘了处理值相等的情况,所以,还是要实战后才能加深记忆。
  • 集合初始化时,尽量指定集合初始值大小。这在笔者实习面试时也被问到,这块的话主要考察 ArrayList 的原理,内部机制,诸位看看JDK里 ArrayList 的原理就明白了。
  • 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
  • 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。概括为一句话就是:尽量降低锁的粒度。
  • 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。关于并发这块可以参考 《Java并发编程实战》 ,个人认为这本在笔者看过Java并发的书籍里能算上乘之作,另外也可参考 《Java并发编程的艺术》 。
  • 通过双重检查锁(double-checked locking)(在并发场景)实现延迟初始化的优化问题隐患(可参考 The “Double-Checked Locking is Broken” Declaration),推荐问题解决方案中较为简单一种(适用于 JDK5 及以上版本),将目标属性声明为 volatile 型。这部分涉及到两个重点,一是双重检查锁,二是 volatile 的原理及Java的主内存及每个线程的内存之间的关系。volatile只能解决多线程时的内存可见性问题,无法解决线程安全问题。可参考 Double checked locking 及 Initialization on demand holder idiom 。
  • 注释掉的代码尽量要配合说明,而不是简单的注释掉。如果永久不用,建议直接删除,因为Git等版本控制系统保存了历史代码。
  • 好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免无用的注释。
  • 善用 TODO 及 FIXME,IDE可以方便的进行扫描。
  • 获取当前毫秒数使用 System.currentTimeMillis(),System.nanoTime()产生的值仅用于比较,同一时刻不同虚拟机System.nanoTime()返回的值可能不一样并且相差很大,笔者的同事已经踩过一次坑,关于 nanoTime 诸位可以看一看JavaDoc。

异常日志

  • 不要捕获Java类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。说到这里,异常继承结构图也可以看下。
  • 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  • 避免出现重复的代码(Don’t Repeat Yourself),即DRY原则。关于这部分可参考 《程序员修炼之道》 。
  • 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。关于日志把server磁盘撑爆的问题,我司也出现过,后面加了相关监控来避免。

MySQL规约

  • 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1表示是,0表示否),此规则同样适用于odps建表。任何字段如果为非负数,必须是 unsigned。因为这样的话可用容量提升了一倍。
  • 表名不使用复数名词。表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
  • 禁用保留字,如 desc、range、match、delayed 等,禁止在代码里对 SQL 关键字进行单独处理。
  • 唯一索引名为 uk_字段名,普通索引名则为 idx_字段名。这样能让开发人员一眼就知道相关索引。
  • 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  • 表必备三字段:id, gmt_create, gmt_modified。其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmt_create, gmt_modified 的类型均为 date_time 类型。创建时间与修改时间需要记录笔者理解,不理解的为什么要用 gmt 开头,北京时间应该是GMT + 8:00 啊。
  • 字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:不是频繁修改的字段;不是 varchar 超长字段,更不能是 text 字段。比如我司的很多表都冗余了 user_name 这个字段。
  • 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
  • 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
  • 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。关于 MySQL 的知识,诸位可参考 《高性能MySQL》 。
  • 利用延迟关联或者子查询优化超多分页场景。MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
  • 建组合索引的时候,区分度最高的在最左边。
  • 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
  • 不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
  • 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
  • 数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。

工程规约

  • 高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
  • 调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)。
  • 给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。

为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作
https://www.hollischuang.com/archives/3304
要分析这个问题,我们先将增强for循环这个语法糖进行解糖,得到以下代码:
public static void main(String[] args) {
    // 使用ImmutableList初始化一个List
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    Iterator iterator = userNames.iterator();
    do
    {
        if(!iterator.hasNext())
            break;
        String userName = (String)iterator.next();
        if(userName.equals("Hollis"))
            userNames.remove(userName);
    } while(true);
    System.out.println(userNames);
}

1、直接使用普通for循环进行操作
我们说不能在foreach中进行,但是使用普通的for循环还是可以的,因为普通for循环并没有用到Iterator的遍历,所以压根就没有进行fail-fast的检验。
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    for (int i = 0; i < 1; i++) {
        if (userNames.get(i).equals("Hollis")) {
            userNames.remove(i);
        }
    }
    System.out.println(userNames);
这种方案其实存在一个问题,那就是remove操作会改变List中元素的下标,可能存在漏删的情况。
2、直接使用Iterator进行操作
除了直接使用普通for循环以外,我们还可以直接使用Iterator提供的remove方法。
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    Iterator iterator = userNames.iterator();

    while (iterator.hasNext()) {
        if (iterator.next().equals("Hollis")) {
            iterator.remove();
        }
    }
    System.out.println(userNames);
如果直接使用Iterator提供的remove方法,那么就可以修改到expectedModCount的值。那么就不会再抛出异常了。其实现代码如下:
-w375
3、使用Java 8中提供的filter过滤
Java 8中可以把集合转换成流,对于流有一种filter操作, 可以对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    userNames = userNames.stream().filter(userName -> !userName.equals("Hollis")).collect(Collectors.toList());
    System.out.println(userNames);
4、使用增强for循环其实也可以
如果,我们非常确定在一个集合中,某个即将删除的元素只包含一个的话, 比如对Set进行操作,那么其实也是可以使用增强for循环的,只要在删除之后,立刻结束循环体,不要再继续进行遍历就可以了,也就是说不让代码执行到下一次的next方法。
    List<String> userNames = new ArrayList<String>() {{
        add("Hollis");
        add("hollis");
        add("HollisChuang");
        add("H");
    }};

    for (String userName : userNames) {
        if (userName.equals("Hollis")) {
            userNames.remove(userName);
            break;
        }
    }
    System.out.println(userNames);
5、直接使用fail-safe的集合类
在Java中,除了一些普通的集合类以外,还有一些采用了fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>() {{
    add("Hollis");
    add("hollis");
    add("HollisChuang");
    add("H");
}};

for (String userName : userNames) {
    if (userName.equals("Hollis")) {
        userNames.remove();
    }
}
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

8. [Mandatory] Do not add ‘is’ as prefix while defining Boolean variable, since it may cause a serialization exception in some Java frameworks.
Counter example:
boolean isSuccess; The method name will be isSuccess() and then RPC framework will deduce the variable name as ‘success’, resulting in a serialization error since it cannot find the correct attribute.
https://www.hollischuang.com/archives/3111
以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:
  • 基本类型自动生成的getter和setter方法,名称都是isXXX()setXXX()形式的。
  • 包装类型自动生成的getter和setter方法,名称都是getXXX()setXXX()形式的。
既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。
我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是isSuccesssetSuccess
举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。
如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。
如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。
这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。
以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。
但是关于这一点,作者之前也有过不同的看法:对于布尔类型的变量,我认为可以和其他类型区分开来,作者并不认为使用null进而导致NPE是一种最好的实践。因为布尔类型只有true/false两种值,我们完全可以和外部调用方约定好当返回值为false时的明确语义。
后来,作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识,还是尽量使用包装类型
但是,作者还是想强调一个我的观点,尽量避免在你的代码中出现不确定的null值。









Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts