这篇文章是我第一次(认真)阅读《阿里巴巴 Java 开发手册(终极版)》的笔记。手册自己对规范的讲解已经很是详细了,若是你已经有必定的开发经验而且有良好的编码习惯和意识,会发现大部分规范是符合常识的。因此本文不会再去作重复的说明,只是对其中一些可能没留意到的或者说不在(个人)常识以内的一些规范进行整理记录。固然每家公司都有本身的一套规范标准,因此你们也不必过度追究。html
其中或许会有遗漏或者理解错误,但愿各位担待提点。java
ide插件已发布:《阿里巴巴Java开发手册》IDEA插件与Eclipse插件使用指南正则表达式
8.【强制】POJO 类中布尔类型的变量,都不要加 is,不然部分框架解析会引发序列化错误。spring
反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC框架在反向解析的时候,“觉得”对应的属性名称是 deleted,致使属性获取不到,进而抛出异常。数据库
16.【参考】各层命名规约:
A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 作前缀。
2) 获取多个对象的方法用 list 作前缀。(我习惯写成 getXxxList)
3) 获取统计值的方法用 count 作前缀。
4) 插入的方法用 save/insert 作前缀。
5) 删除的方法用 remove/delete 作前缀。
6) 修改的方法用 update 作前缀。编程
1.【强制】不容许任何魔法值(即未经定义的常量)直接出如今代码中。
反例:String key = "Id#taobao_" + tradeId;
cache.put(key, value);
api
魔法值:是指在代码中直接出现的数值,而只有在这个数值记述的那部分代码中才能明确了解其含义。
也就是咱们常说的[硬编码]或者[写死],这类代码须要定义常量来明确其含义。数组
5.【强制】采用 4 个空格缩进,禁止使用 tab 字符。
说明:若是使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。安全
有些同窗可能会对这一条不觉得然。若是是协调开发,两个工程师的格式化规则不一致极可能A同窗无心把B同窗的代码从新格式化并提交,致使后边查看svn变动记录时傻逼了。服务器
7.【强制】单行字符数限制不超过 120 个,超出须要换行,换行时遵循以下原则:
1) 第二行相对第一行缩进 4 个空格,从第三行开始,再也不继续缩进,参考示例。
2) 运算符与下文一块儿换行。
3) 方法调用的点符号与下文一块儿换行。
4) 方法调用时,多个参数,须要换行时,在逗号后进行。
5) 在括号前不要换行,见反例。
120这个长度限制颇有意思,如图:
这个长度大概是15寸笔记本1080分辨率字体14号左右的最佳可视长度。固然应该也不必定非要这么精准吧。。
7.【强制】全部的相同类型的包装类对象之间值的比较,所有使用 equals 方法比较。
说明:对于 Integer var = ? 在-128 至 127 范围内的赋值,Integer 对象是在IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值能够直接使用==进行判断,可是这个区间以外的全部数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
12.【强制】POJO 类必须写 toString 方法。使用 IDE 的中工具:source> generate toString时,若是继承了另外一个 POJO 类,注意在前面加一下 super.toString。
说明:在方法执行抛出异常时,能够直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
吐槽:"使用 IDE 的中工具" 码字错误哦!
13.【推荐】使用索引访问用 String 的 split 方法获得的数组时,需作最后一个分隔符后有无内容的检查,不然会有抛 IndexOutOfBoundsException 的风险。
说明:String str = "a,b,c,,";
String[] ary = str.split(",");
// 预期大于 3,结果是 3
System.out.println(ary.length);
最好的作法是对集合类型的变量自己进行判空校验或者大小判断,不要想固然。
2.【强制】ArrayList的subList结果不可强转成ArrayList,不然会抛出ClassCastException异常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
说明:subList 返回的是 ArrayList 的内部类 SubList,并非 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的全部操做最终会反映到原列表上。
5.【强制】使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
说明:asList 的返回对象是一个 Arrays 内部类,并无实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据还是数组。String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一种状况:list.add("yangguanbao"); 运行时异常。
第二种状况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。
10.【推荐】使用 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 值组合集合。
java8 是个好东西~
5.【强制】SimpleDateFormat 是线程不安全的类,通常不要定义为 static 变量,若是定义为static,必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐以下处理:private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:若是是 JDK8 的应用,可使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
再说一遍,java8 是个好东西!LocalDateTime相关API
附赠一个java.util.Date和LocalDateTime互转的例子:
private static Date localDateTimeToUDate(LocalDateTime localDateTime) { ZoneId zone = ZoneId.systemDefault(); Instant instant = localDateTime.atZone(zone).toInstant(); return Date.from(instant); } private static LocalDateTime uDateToLocalDate(Date date) { Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); return localDateTime; }
14.【参考】 HashMap 在容量不够进行 resize 时因为高并发可能出现死链,致使 CPU 飙升,在开发过程当中可使用其它数据结构或加锁来规避此风险。
3.【推荐】表达异常的分支时,少用 if-else 方式,这种方式能够改写成:
if (condition) {
...
return obj;
}
// 接着写 else 的业务逻辑代码;
说明:若是非得使用 if()...else if()...else...方式表达逻辑,【强制】避免后续代码维护困难,请勿超过 3 层。
正例:超过 3 层的 if-else 的逻辑判断代码可使用卫语句、策略模式、状态模式等来实现...
咱们公司codeReview时常常看到有些同窗的代码是if(){}else if(){} else if(){}else{}
除了看上去low更主要的缘由是过多的大括号层级不便于阅读很容易搞混,尤为是跳出代码块的时候,连续几个}}}
基本就不知道跳到哪了完全懵逼,还得折叠代码或者滚上去从新回忆一下。
6.【推荐】接口入参保护,这种场景常见的是用于作批量操做的接口。
解释一下,接口入参保护
就是对入参进行校验,包括容许的最大值或者其余范围或边界。防止请求大量数据致使接口“爆炸”。好比限制返回数据最大条数,超过限制直接return或者抛异常。
感受没啥好说的。。
1.【强制】在使用正则表达式时,利用好其预编译功能,能够有效加快正则匹配速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);
就是说定义成全局变量。
3.【强制】对大段代码进行try-catch,这是不负责任的表现。catch时请分清稳定代码和非稳定代码,稳定代码指的是不管如何不会出错的代码。对于非稳定代码的catch尽量进行区分异常类型,再作对应的异常处理。
9.【推荐】方法的返回值能够为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么状况下会返回null值。调用方须要进行null判断防止NPE问题。说明:本手册明确防止NPE是调用者的责任。即便被调用方法返回空集合或者空对象,对调用者来讲,也并不是高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null的状况。
须要说明的是是否能够返回null是须要根据接口约定来判断的,若是明确的返回对象的结构类型,必定要返回这个对象,但他的属性值能够是null,好比page对象:{data:null,pageNum:0,count:0}
4.【强制】对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明:logger.debug("Processingtradewithid: " + id+ "andsymbol: " + symbol);若是日志级别是warn,上述日志不会打印,可是会执行字符串拼接操做,若是symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操做,最终日志却没有打印。
正例:(条件)if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); }
正例:(占位符)logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
4.【强制】单元测试是能够重复执行的,不能受到外界环境的影响。
说明:单元测试一般会被放到持续集成中,每次有代码check in时单元测试都会被执行。若是单测对外部环境(网络、服务、中间件等)有依赖,容易致使持续集成机制的不可用。
正例:为了避免受外界环境影响,要求设计代码时就把SUT的依赖改为注入,在测试时用spring这样的DI框架注入一个本地(内存)实现或者Mock实现。
15.【参考】为了更方便地进行单元测试,业务代码应避免如下状况:
构造方法中作的事情过多。存在过多的全局变量和静态方法。
存在过多的外部依赖。
存在过多的条件语句。说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
和第一节if-else提到的同样,避免多层代码块嵌套
16.【参考】不要对单元测试存在以下误解:
那是测试同窗干的事情。本文是开发手册,凡是本文内容都是与开发同窗强相关的。
单元测试代码是多余的。汽车的总体功能与各单元部件的测试正常与否是强相关的。
单元测试代码不须要维护。一年半载后,那么单元测试几乎处于废弃状态。
单元测试与线上故障没有辩证关系。好的单元测试可以最大限度地规避线上故障。
测试开发相亲相爱是一家~
4.【强制】用户请求传入的任何参数必须作有效性验证。
说明:忽略参数校验可能致使:
pagesize过大致使内存溢出
恶意orderby致使数据库慢查询
任意重定向SQL注入
反序列化注入
正则输入源串拒绝服务ReDoS
说明:Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,可是若是攻击人员使用的是特殊构造的字符串来验证,有可能致使死循环的结果。
老生常谈的问题,但在工做中有时会忽略。
8.【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,若是存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
大字段建外连表,避免影响索引效率。难点在于如何说服主工程师(滑稽)。
13.【推荐】字段容许适当冗余,以提升查询性能,但必须考虑数据一致。
冗余字段应遵循:
1)不是频繁修改的字段。
2)不是varchar超长字段,更不能是text字段。
正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。
讲一个笑话。公司老王出差去拉项目,对方博士生问“这个数据库设计为何不符合三范式?”
真事。
14.【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
说明:若是预计三年后的数据量根本达不到这个级别,请不要在建立表时就分库分表。
3.【强制】在varchar字段上创建索引时,必须指定索引长度,不必对全字段创建索引,根据实际文本区分度决定索引长度便可。
说明:索引的长度与区分度是一对矛盾体,通常对字符串类型数据,长度为20的索引,区分度会高达90%以上,可使用count(distinctleft(列名, 索引长度))/count(*)的区分度来肯定。
5.【推荐】若是有orderby的场景,请注意利用索引的有序性。orderby最后的字段是组合索引的一部分,而且放在索引组合顺序的最后,避免出现file_sort的状况,影响查询性能。
正例:wherea=? andb=? orderbyc;索引:a_b_c
反例:索引中有范围查找,那么索引有序性没法利用,如:WHEREa>10 ORDERBYb;索引a_b没法排序。
6.【推荐】利用覆盖索引来进行查询操做,避免回表。
说明:若是一本书须要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的做用。
正例:可以创建索引的种类:主键索引、惟一索引、普通索引,而覆盖索引是一种查询的一种效果,用explain的结果,extra列会出现:usingindex。
1.【强制】不要使用count(列名)或count(常量)来替代count(*),count(*)是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
乖乖滚回count(*)
【推荐】in操做能避免则避免,若实在避免不了,须要仔细评估in后边的集合元素数量,控制在1000个以内。
3.【强制】不要用resultClass当返回参数,即便全部类属性名与数据库字段一一对应,也须要定义;反过来,每个表也必然有一个与之对应。
说明:配置映射关系,使字段与DO类解耦,方便维护。
编程一时爽,维护两行泪~
5.【强制】iBATIS自带的queryForList(StringstatementName,intstart,intsize)不推荐使用。
说明:其实现方式是在数据库取到statementName对应的SQL语句的全部记录,再经过subList取start,size的子集合。
正例:Map<String, Object> map = new HashMap<String, Object>();
map.put("start", start);
map.put("size", size);
没想到你是这样的iBATIS!
2.【参考】(分层异常处理规约)在DAO层,产生的异常类型有不少,没法用细粒度的异常进行catch,使用catch(Exceptione)方式,并thrownewDAOException(e),不须要打印日志,由于日志在Manager/Service层必定须要捕获并打到日志文件中去,若是同台服务器再打日志,浪费性能和存储。在Service层出现异常时,必须记录出错日志到磁盘,尽量带上参数信息,至关于保护案发现场。若是Manager层与Service同机部署,日志方式与DAO层处理一致,若是是单独部署,则采用与Service一致的处理方式。Web层毫不应该继续往上抛异常,由于已经处于顶层,若是意识到这个异常将致使页面没法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。
10.【参考】为避免应用二方库的依赖冲突问题,二方库发布者应当遵循如下原则:
1)精简可控原则。移除一切没必要要的API和依赖,只包含ServiceAPI、必要的领域模型对象、Utils类、常量、枚举等。若是依赖其它二方库,尽可能是provided引入,让二方库使用者去依赖具体版本号;无log具体实现,只依赖日志框架。
2)稳定可追溯原则。每一个版本的变化应该被记录,二方库由谁维护,源码在哪里,都须要能方便查到。除非用户主动升级版本,不然公共二方库的行为不该该发生变化。
4.【推荐】在线上生产环境,JVM的Xms和Xmx设置同样大小的内存容量,避免在GC后调整堆大小带来的压力。