开发手册
名词解释
POJO(Plain Ordinary Java Object):专指setter / getter / toString的简单类,包括:DO / DTO / BO / VO
GAV(GroupId ArtifactctId Version):Maven坐标,用来唯一标识jar包
OOP(Object Oriented Programming):泛指类、对象的基本编程处理方式
ORM(Object Relation Mapper):对象关系映射、对象领域模型与底层数据之间的转换
NPE(java.lang.NullPointerException):空指针异常
SOA(Service-Oriented Architecture):面向服务架构,根据需求通过网络对松散耦合的粗粒度应用软件进行分布式部署、组合使用
一方库:子项目模块依赖的库
二方库:Maven依赖的库
三方库:公司之外开源的库
IDE检测插件:github.com/alibaba/p3c
命名风格
- 命名不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 反例:_name $name name_ name$
- 禁止拼音与英文混合方式,更不允许使用中文。通用名称,可是同英文:alibaba\taobao\youku\hangzhou
- 类名使用UpperCamelCase(首字母大写)风格,但DO、BO、DTO、VO、AO、PO等情形例外
- 方法名、参数名、成员变量、局部变量统一使用lowerCamelCase风格,必须遵守驼峰。例如:localValue/getHttpMessage()
- 常量命名全部大写,单词下划线分开,力求语义表达完整
- 抽象类命名使用Abstract或Base开头。异常类命名使用Exception结尾。测试类命名以它要测试的类名开始,Test结尾。
- 类型与中括号之间无空格相连定义数组。例如:int[] arrayDemo;
- POJO类中布尔类型的变量都不要加is前缀。否则部分框架解析会引起序列化错误
- 包名统一使用小写,点分隔符之间仅有一个自然语义的英文单词。包名统一使用单数形式,但类名如果有复数含义,则类名可以使用复数形式。例如:com.alibaba.ai.util。类名MessageUtils
- 避免在子父类的成员变量之间或不同代码块的局部变量之间采用完全相同的命名方式,使其降低可读性
- 杜绝完全不规范的缩写,避免词义不达标:例如:AbstractClass 缩写 AbsClass
- 任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。例如:对某个对象引用的volatile字段进行原子,AtomicReferenceFiledUpdater
- 常量与变量命名时,表达类型的名词放在词尾,以提升辨识度。例如:startTime\workQueue\nameList
- 如果模块、接口、类、方法使用设计模式,应该在命名时体现出具体模式。例如:LoginProxy、OrderFactory、ResourceObserver
- 接口类中的方法和属性不要加任何修饰符号(public 也不要加)保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,必须时与接口方法相关的,并且是整个应用的基础常量。
- 接口和实现类的命名有两套规则:
- 对于Service和DAO类,基于SOA的理念暴露出来的服务一定是接口,内部的实现类用Impl后缀与接口区别。例如:CacheServiceImpl 实现 CacheService接口
- 如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形式)。例如:AbstractTranslator 实现 Translatable
- 枚举类名建议带上Enum后缀,成员名称需要全大写,单词下划线分割。例如:ProcessStatusEnum、SUCCESS
- 各层命名规约:
- Service/DAO层方法命名规约如下。
- 获取单个对象的方法用get作为前缀
- 获取多个对象的方法用list作为前缀,复数结尾,如listObjects。
- 获取统计值的方法用count作为前缀
- 插入的方法用save / insert作为前缀
- 删除的方法用remove / delete 作为前缀
- 修改的方法用update作为前缀
- 领域模型命名规约如下
- 数据对象:xxxDO,xxx为数据表名
- 数据传输对象:xxxDTO,xxx为业务领域相关名称
- 展示对象:xxxVO, xxx一般为网页名称
- POJO是统称,禁止命名
常量定义
- 不允许任何魔法值(未经预先定义的常量)直接出现在代码中
- long或者Long初始化赋值时,数值后使用大写的L,不能是小写。
- 不要使用一个常量类维护所有常量,要按照常量功能进行归类,分开维护
- 常量的复用层次有5层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量
- 跨应用共享:放置在二方库中,通常时client.jar中的constant目录下
- 应用内共享:放置在一方库中,通常实在子模块中的constant目录下
- 子工程内共享:当前子工程的constant目录下
- 包内共享:当前包下单独的constant目录下
- 类内共享:provate static final
- 如果变量值仅在一个范围内变化,则用enum类型来定义。例如下面代码
代码格式
- 大括号的使用约定,如果大括号内为空,则简洁地写成{}即可,大括号中间无须换行和空格。如果是非空代码块,则
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 右大括号后还有else等代码不换行。终止的话必须换行
- 左小括号和字符间不出现空格,右小括号和字符间也不出现空格。左大括号前需要加空格
- if / for / while / switch / do 等保留关键字与括号之间必须加空格
- 任何二目、三目运算符左右两边都要加空格
- 采用4个空格缩进,禁止使用Tab控制符
- 注释的双斜线与注释内容之间有且仅有一个空格
- 进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开
- 单行字符数不超过120个,超出则需要换行,换行时遵循如下原则
- 第二行相对第一行缩进4个空格,第三行开始不再持续缩进
- 运算符与下文一起换行
- 方法调用的点符号与下文一起换行
- 方法调用中的多个参数需要换行时,在逗号后进行
- 括号前不要换行
- 方法参数在定义和传入时,多个参数逗号后边必须加空格
- IDE的text file encoding设置为UTF-8,IDE中文件的换行符使用UNIX格式,不要使用Windows格式
- 没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐
- 单个方法的总行数不超过80行
- 不同逻辑、不同语义、不同业务的代码之间插入一个空行来分隔开,以提升可读性
OOP规约
- 避免通过一个类的对象引用方法此类的静态变量或静态方法。直接用类名来访问即可
- 所有的覆写方法,必须加@Override注解
- 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object
- 对外部正在调用或二方库依赖的接口,不允许修改方法签名,以避免对接口调用方产生影响。若接口过时,必须加@Deprecated注解,并告知新接口或服务是什么
- 不能使用过时的类或方法
- Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
- 所有整型包装类对象之间值的比较,全部使用equals方法
- 浮点数之间的等值判断,基本数据类型不能用 == 进行比较,包括数据类型不能用equals方法进行判断。需要指定一个误差范围,两个浮点数的差值在此范围之内则认为是相等的。
- 例如:float diff = 1e-6f; Math.abs(a-b) < diff
- BigDecimal a,BigDecimal b; a.equalsb
- 定义数据对象DO类时,属性类型要与数据库字段类型相匹配。例如:数据库字段bigint类型必须与类属性Long类型相对应
- 禁止使用构造方法BigDecimal(double)的方式把 double 值转换为BigDecimal对象。会存在损失精度的风险。推荐:入参为String的构造、或BigDecimal.valueOf
- 基本数据类型与包装数据类型的使用标准如下
- 所有的POJO类属性必须使用包装数据类型。数据库查询结果可能为null,因为自动拆箱,基本数据类型有NPE风险
- RPC方法的返回值和参数必须使用包装数据类型
- 所有的局部变量使用基本数据类型。
- 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值
- 序列化类新增属性时,请不要修改serialVersionUID字段,以避免反序列化失败;如果不兼容升级,避免反序列化混乱,需要修改
- 构造方法禁止加入任何业务逻辑,如果有初始化逻辑,放在init方法中
- POJO类必须写toString方法。如果继承另一个POJO类,注意添加super.toString()。解释:当方法抛出异常时,可以直接调用POJO的toString方法打印其属性值,便于排查
- 禁止在POJO类中,同时存在对应属性xxx的isxxx() 和 getXXX() 方法。框架在调用属性的提取方法时,并不能确定哪个方法优先调用到
- 当使用索引访问String的split方法得到的数组时,需要在最后一个分隔符后做有无内容的检查。否则会抛出IndexOutOfBoundsException的风险
- 当一个类有多个构造方法,或多个同名方式时,应该按照顺序放置在一起
- 类内方法定义顺序:公共方法或保护方法 > 私有方法 > getter / setter
- 在setter方法中,参数名称与类成员变量名称一致,this.成员名 = 参数名。不要增加业务逻辑,否则会增加排查问题的难度
- 循环体内,字符串的连接方式使用StringBuilder的append方法进行扩展。不要使用字符串
- final可以声明类、成员变量、本地方法及本地变量。推荐使用情况如下
- 不允许被继承的类
- 不允许修改引用的域对象
- 不允许被重写的方法
- 不允许运行过程中重新赋值的局部变量
- 避免上下文重复使用一个变量,使用final描述可以强制重新定义一个变量,方便更好地进行重构
- 慎用Object的clone方法来拷贝对象。因为是浅拷贝
- 类成员与方法访问控制从严
- 如果不允许外部直接通过new来创建对象,那么构造必须限制private
- 工具类不允许有Public或default构造方法
- 非静态成员变量并且与子类共享,必须限制为protected
- 非静态成员变量并且仅在本类使用,必须限制为private
- 静态成员变量如果仅在本类使用,必须限制为private
- 若是static成员变量,必须考虑是否为final
- 类成员方法只供类内部调用,必须限制为private
- 类成员方法只对继承类公开,限制为protected
集合处理
- 关于hashCode和equals的处理,遵循如下规则:
- 只要重写equals,就必须重写hashCode
- 因为Set存储的是不重复的对象,依据hashCode和euqals进行判断,所以Set存储的对象必须重写这两种方法
- 如果自定义对象作为Map的键,那么必须重写hashCode和equals
- 使用Map的方法keySet()/values()/entrySet(),返回集合对象时,不可以对其添加元素,否则回抛出异常
- Collections类返回的对象,如emptyList()/singletonList()等都是immutable list,不可以对其添加或者删除元素
- ArrayList的subList结果不可强转成ArrayList,否则回抛出异常。解释:subList返回的时ArrayList的内部类,并不是ArrayList
- 在subList场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、增加、删除均产生异常
- 使用集合转数组的方式,必须使用集合的toArray(T[] array),传入的类型是完全一样的数组,大小就是list.size()。
- 在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断
- 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add / remove / clear方法会抛出异常。
- asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组
- 泛型通配符<? extends T>用来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T> 不能使用get方法,因为其作为接口调用赋值很容易出错
- PECS(Producer Extends Consumer Super)原则:第一,频繁往外读取内容的,适用于<? extends T>;第二,经常往里插入的,适用于<? super T>
- 无泛型限制定义的集合赋值给泛型限制的集合时,当使用集合元素时,需要进行性instanceof判断,避免抛出ClassCastException异常
- 不要再foreach循环里进行元素的remove / add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
-
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()){ String item = iterator.next(); if (删除元素的条件){ iterator.remove(); } }
¥