命名、格式规范
|
|-类名使用UpperCamelCase风格 |--普通命名:MarcoPolo / XmlService / TcpUdpDeal / TaPromotion
| |--领域模型相关命名(DO / BO / DTO / VO):UserDO
|-方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase风格:localValue / getHttpMessage() / inputUserId
|
|-常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
|-不允许任何魔法值(即未经定义的常量)直接出现在代码中:(反例) String key =" Id # taobao _"+ tradeId;
|
|-类名前后缀 |--抽象类命名使用 Abstract 或 Base 开头
| |--异常类命名使用 Exception 结尾
| |--测试类命名以它要测试的类的名称开始,以 Test 结尾
| |--枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开
| |--POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误
|
|-Service / DAO 层方法命名规约 |--获取单个的方法用 get 做前缀
| |--获取多个的方法用 list 做后缀(习惯:getXXXList)
| |--取统计值的方法用 count 做前缀
| |--插入的方法用 save( 推荐 ) 或 insert 做前缀
| |--删除的方法用 remove( 推荐 ) 或 delete 做前缀
| |--修改的方法用 update 做前缀(或modify)
|
|-领域模型命名规约|--数据对象 : xxxDO , xxx 即为数据表名
| |--数据传输对象: xxxDTO, xxx 为业务领域相关的名称
| |--展示对象 : xxxVO , xxx 一般为网页名称
| |--POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO
|
|-格式规约|--单行太长需换行
| |--方法体内的变量的定义语句组、执行语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行
| |--相同业务逻辑和语义之间不需要插入空行
|
|-OOP规约 |--避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可
| |--所有的覆写方法,必须加@Override 注解
| |--对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响
| |--接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么
| |--Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals,如:"test" .equals(object);
| |--所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较(IntegerA == IntegerB 只能比对-128 ~ 127之间)
| | (对于 Integer 在-128 至 127 之间的赋值, Integer 对象是在IntegerCache.cache 产生,会复用已有对象)
| |--关于基本数据类型与包装数据类型的使用标准如下 |---所有的 POJO 类属性必须使用包装数据类型
| | |---RPC 方法的返回值和参数必须使用包装数据类型
| | |---所有的局部变量【推荐】使用基本数据类型
| |
| |--序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败
| | (如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值)
| |
| |--构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
| |
| |--使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查
| | (split方法会判断末尾元素是否为空字符串,如果是则去掉)
| |
| |--当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读
| | (类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法)
| |
| |--final 可提高程序响应效率,声明成 final 的情况 |---不需要重新赋值的变量,包括类属性、局部变量
| | |---对象参数前加 final,表示不允许修改引用的指向
| | |---类方法确定不允许被重写
集合处理规范
|
|-关于 hashCode 和 equals 的处理 |--只要重写 equals,就必须重写 hashCode
| |--Set元素不重复,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法
| |--如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals
|
|-不要在 foreach 循环里进行元素的 remove / add 操作 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁
| |--Iterator<String> it = a.iterator();
| |--while(it.hasNext()){
| |-- String temp = it.next();
| |-- if(删除元素的条件){
| |-- it.remove();
| |-- }
| |--}
|
|-集合初始化时,尽量指定集合初始值大小:ArrayList 尽量使用 ArrayList(int initialCapacity) 初始化
|
|-使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历(如果是 JDK8,使用 Map.foreach 方法)
并发处理规范
|
|-获取单例对象需要保证线程安全,其中的方法也要保证线程安全:资源驱动类、工具类、单例工厂类都需要注意
|
|-创建线程或线程池时请指定有意义的线程名称,方便出错时回溯 |--public class TimerTaskThread extends Thread {
| |-- public TimerTaskThread(){
| |-- super.setName("TimerTaskThread"); ... }
| |--}
|
|-线程资源 必须通过 线程池 提供 |--使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,
| 不允许在应用中自行显式创建线程 |--解决资源不足的问题如果不使用线程池,
| |--有可能造成系统创建大量同类线程而导致消耗完内存或者"过度切换"的问题
|
|-线程池不允许使用 Executors 去创建 |--这样的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险
| 而是通过 ThreadPoolExecutor 的方式 |--Executors 返回的线程池对象的弊端如下:
| | |---1)FixedThreadPool 和 SingleThreadPool:
| | |---允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
| | |---2)CachedThreadPool 和 ScheduledThreadPool:
| | |---允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
|
|-高并发时,同步调用应该去考量锁的性能损耗 |---能用无锁数据结构,就不要用锁
| |---能 锁区块,就不要锁整个方法体
| |---能用对象锁,就不要用类锁
|
|-对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁
| (线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序 也必须是 A、B、C,否则可能出现死锁)
|
|-并发修改同一记录时,避免更新丢失 |--要么在应用层加锁
| |--要么在缓存加锁
| |--要么在数据库层使用乐观锁,使用 version 作为更新依据
| | 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁乐观锁的重试次 数不得小于 3 次
|
|-TimeTask异常未捕获时 |--多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,其它任务便会自动终止运行
| |--使用ScheduledExecutorService 则没有这个问题
|
|-HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在 开发过程中注意规避此风险
控制语句规范
|
|-在一个 switch 块内,每个 case 要么通过 break/return 等来终止(要么注释说明程序将继续执行到哪一个 case 为止)
|-在一个 switch 块内,都必须包含一个 default 语句并且 放在最后,即使它什么代码也没有
|
|-在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;
|
|-尽量少用 else |--if-else 的方式可以改写成 |---if(condition){
| | |--- ...
| | |--- return obj;
| | |---}
| | |---// 接着写 else 的业务逻辑代码;
| |
| |--如果非得使用if()...else if()...else...方式表达逻辑,【强制】请勿超过3层
| |--超过三层请使用状态设计模式 或者 卫语句:
| | |---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 Guidelines.");
| | |--- return;
| | |---}
|
|-将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
|-除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句
|
|-需要进行参数校验的场景 |--1) 调用频次低的方法
| |--2) 执行时间开销很大的方法,参数校验时间几乎可以忽略不计
| |--3) 需要极高稳定性和可用性的方法
| |--4) 对外提供的开放接口,不管是RPC/API/HTTP接口
| |--5) 敏感权限入口
|
|方法中不需要参数校验的场景 |--1) 极有可能被循环调用的方法,不建议对参数进行校验。
| |-- 但在方法说明里必须注明外部参数检查。
| |--2) 底层的方法调用频度都比较高,一般不校验。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。
| |-- 一般 DAO 层与 Service 层都在同一个应用中,部署在同一 台服务器中,所以 DAO 的参数校验,可以省略。
| |--3) 被声明成private只会被自己代码所调用的方法,
| |-- 如果能够确定调用方法的代码传入参 数已经做过检查或者肯定不会有问题,此时可以不校验参数。
注释规范
|
|-类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用 //xxx 方式
|
|-所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、 异常说明外,
| 还必须指出该方法做什么事情,实现什么功能,对子类的实现要求,或者调用注意事项,请一并说明
|
|-方法内部|--单行:在语句上方另起一行,使用//注释
| |--多行:使用/* */注释,注意与代码对齐
|
|-所有的枚举类型字段必须要有注释,说明每个数据项的用途
|
|-代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑 等的修改
|
|-注释掉的代码尽量要配合说明|--1)后续可能会恢复此段代码逻辑
| |--2)永久不用(可直接删掉)
|
|-特殊注释标记,请注明标记人与标记时间,
| 注意及时处理这些标记,
| 通过标记扫描, 经常清理此类标记|--1) 待办事宜(TODO)
| |--2) 错误,不能工作(FIXME):在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况
异常规范
|
|-异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低
|
|-对于非稳定代码的 catch 尽可能进行区分 异常类型,再做对应的异常处理
|
|-不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句
|
|-有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回 滚事务
|
|-方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分 说明什么情况下会返回 null 值,调用方需要进行 null 判断防止 NPE 问题
|
|-注意 NPE 产生的场景|--1) 返回类型为包装数据类型,有可能是null,返回int值时注意判空;反例:public int f(){ return Integer 对象}; 如果为 null,自动解箱抛 NPE
| |--2) 数据库的查询结果可能为null
| |--3) 集合里的元素即使isNotEmpty,取出的数据元素也可能为null
| |--4) 远程调用返回对象,一律要求进行NPE判断
| |--5) 对于Session中获取的数据,建议NPE检查,避免空指针
| |--6) 级联调用obj.getA().getB().getC();一连串调用,易产生NPE
|
|-在代码中使用"抛异常"还是"返回错误码"|--对于公司外的 http/api 开放接口必须 使用"错误码"
| |--而应用内部推荐异常抛出
| |--跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess、"错误码"、"错误简短信息"
日志规范
|
|-应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一
| import org.slf4j.Logger;
| import org.slf4j.LoggerFactory;
| private static final Logger logger = LoggerFactory.getLogger(Abc.class);
|
|-日志文件推荐至少保存 15 天,因为有些异常具备以"周"为频次发生的特点
|
|-应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log
| |
| |-logType:日志类型,推荐分类有 stats/desc/monitor/visit 等
| |-logName:日志描述。这种命名的好处:通过文件名就可知 道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找
| |-正例:mppserver 应用中单独监控时区转换异常,如: mppserver_monitor_timeZoneConvert.log
| |-推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于 通过日志对系统进行及时监控
|
|-对 debug/info/trace 级别的日志输出,必须使用条件输出形式及使用占位符的方式
| if (logger.isDebugEnabled()) {
| logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
| }
|
|-可以使用warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适 从
| 注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必 要,请不要在此场景打出 error 级别
|
|-谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;
| 如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘 撑爆,并记得及时删除这些观察日志。
其它规范
|
|-在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
| 不要在方法体内定义:Pattern pattern = Pattern.compile(规则)
|
|-获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime();
| 如果想获取更加精确的纳秒级时间值,用 System.nanoTime()
| 在 JDK8 中,针对统计 时间等场景,推荐使用Instant 类
|
|-对于"明确停止使用的代码和配置",如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾