前言
参考: 阿里巴巴 Java 开发手册 V1.3.0
总结比较重要的, 对面试有用的开发规约
一, 编程规约
(一)命名风格
[强制] POJO 类中布尔类型的变量, 都不要加 is, 否则部分框架解析会引起序列化错误.
反例: 定义为基本数据类型 Boolean isDeleted; 的属性, 它的方法也是 isDeleted(),RPC 框架在反向解析的时候,"以为" 对应的属性名称是 deleted, 导致属性获取不到, 进而抛出异常.
[推荐] 如果模块, 接口, 类, 方法使用了设计模式, 在命名时体现出具体模式.
- public class OrderFactory;
- public class LoginProxy;
- public class ResourceObserver;
(二)常量定义
[强制] 不允许任何魔法值 (即未经定义的常量) 直接出现在代码中.
反例: String key = "Id#taobao_" + tradeId;
cache.put(key, value);
[推荐] 如果变量值仅在一个范围内变化, 且带有名称之外的延伸属性, 定义为枚举类.
正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}
(三)代码格式
(四)OOP 规约
[强制] 避免通过一个类的对象引用访问此类的静态变量或静态方法, 无谓增加编译器解析成本, 直接用类名来访问即可.
[强制] 外部正在调用或者二方库依赖的接口, 不允许修改方法签名, 避免对接口调用方产生影响. 接口过时必须加 @Deprecated 注解, 并清晰地说明采用的新接口或者新服务是什么.
[强制] 所有的相同类型的包装类对象之间值的比较, 全部使用 equals 方法比较.
说明: 对于 Integer var = ? 在 - 128 至 127 范围内的赋值, Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象, 这个区间内的 Integer 值可以直接使用 == 进行判断, 但是这个区间之外的所有数据, 都会在堆上产生, 并不会复用已有对象, 这是一个大坑, 推荐使用 equals 方法进行判断.
[强制] 序列化类新增属性时, 请不要修改 serialVersionUID 字段, 避免反序列失败; 如果完全不兼容升级, 避免反序列化混乱, 那么请修改 serialVersionUID 值.
说明: 注意 serialVersionUID 不一致会抛出序列化运行时异常.
[强制] 构造方法里面禁止加入任何业务逻辑, 如果有初始化逻辑, 请放在 init 方法中.
[强制] POJO 类必须写 toString 方法. 使用 IDE 的中工具: source> generate toString 时, 如果继承了另一个 POJO 类, 注意在前面加一下 super.toString.
说明: 在方法执行抛出异常时, 可以直接调用 POJO 的 toString()方法打印其属性值, 便于排 查问题.
[推荐] 循环体内, 字符串的连接方式, 使用 StringBuilder 的 append 方法进行扩展.
说明: 反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象, 然后进行 append 操作, 最后通过 toString 方法返回 String 对象, 造成内存资源浪费.
反例:
- String str = "start";
- for (int i = 0; i < 100; i++) {
- str = str + "hello";
- }
(五)集合处理
[强制] 关于 hashCode 和 equals 的处理, 遵循如下规则:
1) 只要重写 equals, 就必须重写 hashCode.
2) 因为 Set 存储的是不重复的对象, 依据 hashCode 和 equals 进行判断, 所以 Set 存储的对象必须重写这两个方法.
3) 如果自定义对象做为 Map 的键, 那么必须重写 hashCode 和 equals.
说明: String 重写了 hashCode 和 equals 方法, 所以我们可以非常愉快地使用 String 对象 作为 key 来使用.
[强制] ArrayList 的 subList 结果不可强转成 ArrayList, 否则会抛出 ClassCastException 异常, 即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.
说明: subList 返回的是 ArrayList 的内部类 SubList, 并不是 ArrayList , 而是 ArrayList 的一个视图, 对于 SubList 子列表的所有操作最终会反映到原列表上.
[强制] 使用集合转数组的方法, 必须使用集合的 toArray(T[] array), 传入的是类型完全一样的数组, 大小就是 list.size().
反例: 直接使用 toArray 无参方法存在问题, 此方法返回值只能是 Object[]类, 若强转其它 类型数组将出现 ClassCastException 错误.
[强制] 使用工具类 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)也会随之修改.
[强制] 不要在 foreach 循环里进行元素的 remove/add 操作. remove 元素请使用 Iterator 方式, 如果并发操作, 需要对 Iterator 对象加锁.
[推荐] 高度注意 Map 类集合 K/V 能不能存储 null 值的情况, 如下表格:
(六)并发处理
[强制] 线程资源必须通过线程池提供, 不允许在应用中自行显式创建线程.
说明: 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销, 解决资 源不足的问题. 如果不使用线程池, 有可能造成系统创建大量同类线程而导致消耗完内存或者 "过度切换" 的问题.
[强制] 线程池不允许使用 Executors 去创建, 而是通过 ThreadPoolExecutor 的方式, 这样的处理方式让写的同学更加明确线程池的运行规则, 规避资源耗尽的风险.
[推荐] 避免 Random 实例被多线程使用, 虽然共享该实例是线程安全的, 但会因竞争同一 seed 导致的性能下降.
说明: Random 实例包括 java.util.Random 的实例或者 Math.random()的方式.
正例: 在 JDK7 之后, 可以直接使用 API ThreadLocalRandom, 而在 JDK7 之前, 需要编码保 证每个线程持有一个实例.
[参考] volatile 解决多线程内存不可见问题. 对于一写多读, 是可以解决变量同步问题, 但是如果多写, 同样无法解决线程安全问题. 如果是 count++ 操作, 使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8, 推荐使用 LongAdder 对象, 比 AtomicLong 性能更好(减少乐观锁的重试次数).
(七)控制语句
[推荐] 表达异常的分支时, 少用 if-else 方式
说明: 如果非得使用 if()...else if()...else... 方式表达逻辑,[强制] 避免后续代码维 护困难, 请勿超过 3 层. 正例: 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句, 策略模式, 状态模式等来实现, 其中卫语句示例如下:
- 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;
- }
(八)注释规约
(九)其它
[强制] 在使用正则表达式时, 利用好其预编译功能, 可以有效加快正则匹配速度.
[强制] 后台输送给页面的变量必须加 $!{var}-- 中间的感叹号.
说明: 如果 var=null 或者不存在, 那么 ${var}会直接显示在页面上.
[强制] 注意 Math.random() 这个方法返回是 double 类型, 注意取值的范围 0≤x<1(能够 取到零值, 注意除零异常), 如果想获取整数类型的随机数, 不要将 x 放大 10 的若干倍然后 取整, 直接使用 Random 对象的 nextInt 或者 nextLong 方法.
二, 异常日志
(一)异常处理
[强制] Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避, 而不应该 通过 catch 来处理, 比如: IndexOutOfBoundsException,NullPointerException 等等.
正例: if (obj != null) {...}
反例: try { obj.method() } catch (NullPointerException e) {...}
[强制] 异常不要用来做流程控制, 条件控制, 因为异常的处理效率比条件分支低.
[强制] 对大段代码进行 try-catch, 这是不负责任的表现
[强制] 捕获异常是为了处理它, 不要捕获了却什么都不处理而抛弃之, 如果不想处理它, 请 将该异常抛给它的调用者. 最外层的业务使用者, 必须处理异常, 将其转化为用户可以理解的 内容.
[推荐] 防止 NPE, 是程序员的基本修养.
[参考] 在代码中使用 "抛异常" 还是 "返回错误码", 对于公司外的 http/API 开放接口必须 使用 "错误码"; 而应用内部推荐异常抛出; 跨应用间 RPC 调用优先考虑使用 Result 方式, 封 装 isSuccess()方法,"错误码","错误简短信息"
(二)日志规约
[强制] 日志文件推荐至少保存 15 天, 因为有些异常具备以 "周" 为频次发生的特点.
[强制] 应用中不可直接使用日志系统 (Log4j,Logback) 中的 API, 而应依赖使用日志框架 SLF4J 中的 API, 使用门面模式的日志框架, 有利于维护和各个类的日志处理方式统一.
三, 单元测试
[强制] 好的单元测试必须遵守 AIR 原则. 说明: 单元测试在线上运行时, 感觉像空气 (AIR) 一样并不存在, 但在测试质量的保障上, 却是非常关键的. 好的单元测试宏观上来说, 具有自动化, 独立性, 可重复执行的特点.
- A:Automatic(自动化)
- I:Independent(独立性)
- R:Repeatable(可重复)
四, 安全规约
[强制] 隶属于用户个人的页面或者功能必须进行权限控制校验.
[强制] 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定, 防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库.
[强制] 用户请求传入的任何参数必须做有效性验证.
五, MySQL 数据库
(一) 建表规约
[强制] 如果存储的字符串长度几乎相等, 使用 char 定长字符串类型.
[强制] 表达是与否概念的字段, 必须使用 is _ xxx 的方式命名, 数据类型是 unsigned tinyint( 1 表示是, 0 表示否 )
说明: 任何字段如果为非负数, 必须是 unsigned .
正例: 表达逻辑删除的字段名 is_deleted ,1 表示删除, 0 表示未删除.
[强制] varchar 是可变长字符串, 不预先分配存储空间, 长度不要超过 5000, 如果存储长 度大于此值, 定义字段类型为 text , 独立出来一张表, 用主键来对应, 避免影响其它字段索 引效率.
[强制] 表必备三字段: id , gmt _ create , gmt _ modified .
说明: 其中 id 必为主键, 类型为 unsigned bigint , 单表时自增, 步长为 1. gmt_create, gmt_modified 的类型均为 date_time 类型, 前者现在时表示主动创建, 后者过去分词表示被 动更新.
[推荐] 字段允许适当冗余, 以提高查询性能, 但必须考虑数据一致. 冗余字段应遵循:
1 ) 不是频繁修改的字段.
2 ) 不是 varchar 超长字段, 更不能是 text 字段.
正例: 商品类目名称使用频率高, 字段长度短, 名称基本一成不变, 可在相关联的表中冗余存 储类目名称, 避免关联查询.
(二) 索引规约
[强制] 业务上具有唯一特性的字段, 即使是多个字段的组合, 也必须建成唯一索引.
[强制] 超过三个表禁止 join. 需要 join 的字段, 数据类型必须绝对一致; 多表关联查询时, 保证被关联的字段需要有索引.
[强制] 在 varchar 字段上建立索引时, 必须指定索引长度, 没必要对全字段建立索引, 根据 实际文本区分度决定索引长度即可.
[强制] 页面搜索严禁左模糊或者全模糊, 如果需要请走搜索引擎来解决.
[推荐] 利用覆盖索引来进行查询操作, 避免回表. 说明: 如果一本书需要知道第 11 章是什么标题, 会翻开第 11 章对应的那一页吗? 目录浏览 一下就好, 这个目录就是起到覆盖索引的作用.
正例: 能够建立索引的种类: 主键索引, 唯一索引, 普通索引, 而覆盖索引是一种查询的一种 效果, 用 explain 的结果, extra 列会出现: using index.
[推荐] 建组合索引的时候, 区分度最高的在最左边.
正例: 如果 where a=? and b=? ,a 列的几乎接近于唯一值, 那么只需要单建 idx_a 索引即 可.
(三) SQL 语句
[强制] 不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的 标准统计行数的语法, 跟数据库无关, 跟 NULL 和非 NULL 无关.
[强制] 不得使用外键与级联, 一切外键概念必须在应用层解决.
说明: 以学生和成绩的关系为例, 学生表中的 student _ id 是主键, 那么成绩表中的 student _ id 则为外键. 如果更新学生表中的 student _ id , 同时触发成绩表中的 student _ id 更新, 即为级联更新. 外键与级联更新适用于单机低并发, 不适合分布式, 高并发集群 ; 级联更新是强阻塞, 存在数据库更新风暴的风险 ; 外键影响数据库的插入速度.
[强制] 禁止使用存储过程, 存储过程难以调试和扩展, 更没有移植性.
(四)ORM 映射
[强制] 在表查询中, 一律不要使用 * 作为查询的字段列表, 需要哪些字段必须明确写明.
六, 工程结构
(一)应用分层
(二)二方库依赖
(三)服务器
[推荐] 高并发服务器建议调小 TCP 协议的 time_ wait 超时时间.
说明: 操作系统默认 240 秒后, 才会关闭处于 time_ wait 状态的连接, 在高并发访问下, 服务器端会因为处于 time _ wait 的连接数太多, 可能无法建立新的连接, 所以需要在服务器上调小此等待值.
正例: 在 Linux 服务器上请通过变更 / etc / sysctl . conf 文件去修改该缺省值 ( 秒 ) : net . ipv 4. tcp _ fin _ timeout = 30
[推荐] 调大服务器所支持的最大文件句柄数 (File Descriptor , 简写为 fd) .
说明: 主流操作系统的设计是将 TCP / UDP 连接采用与文件一样的方式去管理, 即一个连接对 应于一个 fd . 主流的 Linux 服务器默认所支持最大 fd 数量为 1024, 当并发连接数很大时很 容易因为 fd 不足而出现 "open too many files" 错误, 导致新的连接无法建立. 建议将 Linux 服务器所支持的最大句柄数调高数倍 ( 与服务器的内存数量相关 ) .
[推荐] 给 JVM 设置 - XX:+HeapDumpOnOutOfMemoryError 参数, 让 JVM 碰到 OOM 场景时输出 dump 信息.
[推荐] 在线上生产环境, JVM 的 Xms 和 Xmx 设置一样大小的内存容量, 避免在 GC 后调整堆 大小带来的压力.
[参考] 服务器内部重定向使用 forward; 外部重定向地址使用 URL 拼装工具类来生成, 否则 会带来 URL 维护不一致的问题和潜在的安全风险.
关注我
我是蛮三刀把刀, 目前为后台开发工程师. 主要关注后台开发, 网络安全, Python 爬虫等技术.
来微信和我聊聊: yangzd1102
GitHub: https://github.com/qqxx6661
原创博客主要内容
笔试面试复习知识点手册
Leetcode 算法题解析(前 150 题)
剑指 offer 算法题解析
Python 爬虫相关技术分析和实战
后台开发相关技术分析和实战
个人公众号: Rude3Knife
如果文章对你有帮助, 不妨收藏起来并转发给您的朋友们~
来源: https://juejin.im/post/5c5c0396e51d457ffc1bd38a