编程规范

Posted by 盈盈冲哥 on July 25, 2020

阿里巴巴Java开发规范

阿里巴巴 Alibaba Java开发规范 总结版 快速阅读版

命名风格

  • 类名:UpperCamelCase,DO / BO / DTO / VO / AO / PO / UID 例外。

  • 抽象类:Abstract 或 Base 开头;异常类:使用 Exception 结尾;测试类:以 Test 结尾。

  • POJO 类中的布尔变量不要加 is 前缀,部分框架解析会引起序列化错误。

  • 包名小写,点之间一个单词。包名用单数,类名可以用复数。

  • 子类和父类之间、不同代码块之间变量不要用一样的命名。

  • 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。

  • 接口类方法和属性不加修饰符,包括public。定义变量要与所有接口方法相关,JDK8中的默认实现要对所有实现都有价值。

  • 基于SOA的理念,Service和DAO类暴露出来的服务要是接口,实现类用Impl后缀。借口取名可以用-able形容词。

  • Service/DAO 层方法命名:get, list, count, save/insert, remove/delete, update.

  • 领域模型命名规约:数据对象xxxDO;数据传输对象xxxDTO;展示对象xxxVO;POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO.

常量定义

  • 魔法值(未经预先定义的常量)不要直接出现在代码中。

  • 数值后面用大写L,防止l看成1.

OOP规约

  • 用类名访问静态方法和静态变量,不要通过对象访问。

  • 用常量或有确定值的对象调用equals,防止出现空指针异常。

  • 整形包装类值比较用equals方法。

  • 金额用最小货币单位。

  • 浮点数积分类型不能用==比较,浮点数包装不能用equals比较(使用 BigDecimal 来定义值,再进行浮点数运算)。

  • DO类中的属性类型要与数据库字段匹配。

  • 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。

  • 所有的 POJO 类属性必须使用包装数据类型。

  • RPC 方法的返回值和参数必须使用包装数据类型。

  • 所有的局部变量使用基本数据类型。

  • 定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。

  • 序列化类新增属性时,不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,要修改 serialVersionUID 值。

  • POJO 类必须写 toString 方法。

  • 类中构造方法应该在一起,同名方法应该在一起;类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法。

  • setter 方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。在getter/setter 方法中,不要增加业务逻辑。

  • 字符串的连接用 StringBuilder 的 append 方法。

  • 慎用 Object 的 clone 方法来拷贝对象。对象 clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝。

  • https://blog.csdn.net/zhangjg_blog/article/details/18369201 clone只深拷贝一层域变量,不进行递归的深拷贝。

  • 方法访问控制从严:优先用private,如果对继承类公开用protected,对外公开用public。

集合处理

  • 判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。(前者的时间复杂度为 O(1),而且可读性更好。)

  • 在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,防止出现相同 key 值。

  • 在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,注意当 value 为 null 时会抛 NPE 异常。

  • Map的方法keySet()/values()/entrySet()返回集合对象,不能新增元素。

  • Collections类返回的对象不可变,如:emptyList()/singletonList()。

  • 集合转数组用toArray(T[] array)方法,传入类型一致,长度为0的空数组。

  • Arrays.asList将数组转换成集合时,不能修改集合。

  • forEach里不要对元素进行remove/add操作。remove 元素使用 Iterator 方式,并发操作需要对 Iterator 对象加锁。

  • 集合初始化时,指定集合初始值大小。大小为元素个数/HashMap负载因子,不确定设为16。

  • 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式。

  • ConcurrentHashMap键和值都不能为空(根据key获取value时得到null,由于是在并发的情况下,难以根据containsKey知道key是否存在),TreeMap键不能为空。

并发处理

  • 获取单例保证线程安全。

  • 线程资源必须通过线程池提供。

  • 不用Executors创建线程池,因为FixThreadPool和SingleThreadPool允许请求队列长度为Integer.MAX_VALUE,CachedThreadPool允许创建线程数为Integer.MAX_VALUE导致OOM;用ThreadPoolExecutor。

  • 在try-finally中回收自定义的ThreadLocal变量,在线程池中会导致内存泄漏的情况。

  • 能不用锁就不用锁,能锁块就不要锁方法,能锁对象就不要锁类。

  • 保证一定的加锁顺序防止死锁。

  • 并发修改应该加锁:应用层加锁,缓存加锁,数据库层加乐观锁。

  • 用ScheduledExecutorService替代Timer:Timer运行多个TimeTask,有一个没有捕获异常,其他任务就会终止。

  • 资金相关的金融敏感信息用悲观锁。

  • Random实例被多线程使用,会导致竞争同一种子而性能下降。

  • volatile解决内存不可见的问题,一写多读情况下可以解决线程同步问题。count++用原子变量,JDK8中的LongAdder比AtomicLong性能好。

控制语句

  • switch里,case要么通过comtinue/break/return终止,要么用注释说明执行到那个case,一定要有default。

  • switch括号里的变量如果是String,先判断null。

  • 一行代码也要大括号。

  • 三目运算符condition? expr1: expr2中,两个表达式如果有一个是原始类型,或者类型不一致,会触发对齐拆箱操作。

  • 高并发时不要用==,应该用>或者<。

  • 超过3层if-else使用卫语句。

  • if里的复杂逻辑判断赋值给一个有意义的变量名。

  • 赋值语句单独一行,不要在条件表达式等其他位置插入赋值语句。

  • 尽量避免取反逻辑。

  • 批量操作,接口入参保护。

  • 对外提供的开放接口(RPC/API/HTTP),执行时间开销大的接口,需要参数校验;很有可能被循环调用的方法,底层调用频率高的方法,private只会被自己代码调用的方法,可以不校验参数。

异常处理

  • 可以规避的RuntimeException异常不应该通过catch来处理。

  • 不要用异常来做条件控制。

  • 捕获异常是为了处理,如果不处理,应该将异常抛给调用者

整洁代码

  • 用collection.isEmpty() 代替 collection.size() == 0,可读性强而且代码效率高。

  • 用return Collections.emptyList()代替return null.

  • 设计原则SOLID

    • 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
    • 开放封闭原则:软件实体(类、模块、函数等等)对于扩展是开放的,对于更改是封闭的。
    • 里氏替换原则:子类型必须能够替换掉它们的父类型
    • 接口隔离:类不应该依赖不需要的接口
    • 依赖倒转原则:A. 高层模块不应该依赖低层模块。两个都应该依赖抽象。B. 抽象不应该依赖细节。细节应该依赖抽象。
    • 合成/聚合原则:尽量使用合成/聚合(has-a),尽量不要使用类继承(is-a)
    • 迪米特法则:一个类应该对其他对象尽可能少地了解
  • 命名的简洁

    • 变量命名

      • 尽量避免使用单字母变量,即使在for循环中,pos/index也比i/j/k优雅
      • 变量名长度适中,8~20
      • 见名知义:例如布尔变量用isDone代替status,状态变量用need代替flag,常量VERSION,集合变量List students
      • 利用Codelf辅助工具来命名
    • 方法命名

      • 动词开头,规避get/set开头
      • 避免Data/Info等无意义的词
      • 主动创造命名机会,例如用静态工厂方法代替构造方法
    • 包命名规则:com.公司名.{业务线}.项目名.模块名

  • 包的简洁/类的整洁

    • 类应有且仅有一条被修改的理由,即单一职责原则
    • 功能复用优先考虑组合,而非继承
  • 函数的的整洁

    • 尽量使用零参数和一参数的的函数,尽量避免三参数以上的函数,应该将参数封装起来
    • 函数只做一件事,函数中的语句在同一抽象层级上
    • 避免副作用函数,还有除了函数名所说的其他的副作用
  • 异常的整洁

    • 分层处理,链式传播:异常信息链传播与函数栈调用顺序相反
    • 正确使用受检异常与非受检异常
      • Exception 运行期可预知异常,通常可恢复。程序运行时出现是正常现象。(受检异常)
      • Error 编程错误,通常不可恢复。一旦发生通常意味着bug,应极力避免。
      • RuntimeException 编程错误,通常不可恢复。一旦发生通常意味着bug,应极力避免。(非受检异常)
    • 总是要做一些清理工作
    • 不要使用异常来控制流程
    • 不要忽略异常,至少要写一个注释
    • 不要捕获Throwable类:在应用中不应捕获 当应用抛出Errors的时候,一般都是不可恢复的情况。
    • 要在方法定义分句中定义具体的异常
  • 注释的整洁/格式的整洁

    • 能用函数或变量时就别用注释
    • 见文知意时不需要注释
    • 对意图的解释、警示、提供有用信息的注释