java开发规范
POJO 类中布尔类型的变量,都不要加 is ,否则部分框架解析会引起序列化错误。假设定义一个 boolean 的 isSuccess 属性,它的方法 Getter 被 IDE 生成为 isSuccess() , RPC 等三方框架在反向解析的时候, “以为” 对应的属性名称是 success ,导致属性获取不到,进而抛出异常。这点也是笔者之前遇到过的,查了很久哪里的错最后发现是这个问题,不过经历一次后基本后面就能避免。
接口类中的方法和属性不要加任何修饰符号。包括在一些开源的代码里,笔者也经常看见在接口方法上声明 public 关键字的,这是冗余的,在 Java 规范中提到过。关于代码的规范及简洁性诸位可以参考 《重构 改善既有代码的设计》 及 《代码整洁之道》。
方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。不过没有必要插入多行空格进行隔开。这样可读性会明显提高,笔者经常看到部分开发人员的代码在很长的代码块里完全没有一个空行,没有按逻辑进行换行,这种习惯是不太好的。
所有的覆写方法,必须加 @Override 注解。这样 IDE 会检查合法性,有错误的话会及时提示。
所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。比如 Integer 的、 -128 至 127 之间被缓存的对象可以直接使用 == 判断,因为被缓存了,是同一对象,地址相等,而这个区间外的却不能使用 == 判断,这也是面试时的一个常考点。
关于基本数据类型与包装数据类型:所有的 POJO 类属性必须使用包装数据类型,以便映射数据库中的 NULL ,局部变量推荐使用基本数据类型。
关于 hashCode 和 equals 的处理,遵循如下规则:只要重写 equals ,就必须重写 hashCode ,具体原因可参考《Effective java 中文版(第2版)》。
关于 ArrayList 里 subList 结果的注意事项,subList 只是 ArrayList 的一个视图,这部分大家可以参考 JDK 里的源码。
不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。
在 JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort, Collections.sort 会报 IllegalArgumentException 异常。这个在《Effective java 中文版(第2版)》中也有说明,虽然笔者之前看过,但在刚实习时的一个用于省份排序的代码里使用 Comparator 时还是忘了处理值相等的情况,所以,还是要实战后才能加深记忆。
集合初始化时,尽量指定集合初始值大小。这在笔者实习面试时也被问到,这块的话主要考察 ArrayList 的原理,内部机制,诸位看看JDK里 ArrayList 的原理就明白了。
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。概括为一句话就是:尽量降低锁的粒度。
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。关于并发这块可以参考 《Java并发编程实战》 ,个人认为这本在笔者看过Java并发的书籍里能算上乘之作,另外也可参考 《Java并发编程的艺术》 。
通过双重检查锁 (double-checked locking) (在并发场景) 实现延迟初始化的优化问题隐患 (可参考 The “Double-Checked Locking is Broken” Declaration) ,推荐问题解决方案中较为简单一种 (适用于 JDK5 及以上版本) ,将目标属性声明为 volatile 型。这部分涉及到两个重点,一是双重检查锁,二是 volatile 的原理及 Java 的主内存及每个线程的内存之间的关系。 volatile 只能解决多线程时的内存可见性问题,无法解决线程安全问题。可参考 Double checked locking 及 Initialization on demand holder idiom 。
注释掉的代码尽量要配合说明,而不是简单的注释掉。如果永久不用,建议直接删除,因为 Git 等版本控制系统保存了历史代码。
好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免无用的注释。
善用 TODO 及 FIXME , IDE 可以方便的进行扫描。
获取当前毫秒数使用 System.currentTimeMillis(), System.nanoTime() 产生的值仅用于比较,同一时刻不同虚拟机 System.nanoTime() 返回的值可能不一样并且相差很大,笔者的同事已经踩过一次坑,关于 nanoTime 诸位可以看一看 JavaDoc 。
异常处理
不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如: IndexOutOfBoundsException / NullPointerException ,这类异常由程序员预检查来规避,保证程序健壮性。说到这里,异常继承结构图也可以看下。
捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
避免出现重复的代码 (Don’t Repeat Yourself) ,即 DRY 原则。关于这部分可参考 《程序员修炼之道》 。
谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。关于日志把 server 磁盘撑爆的问题,我司也出现过,后面加了相关监控来避免。
MySQL 规约
表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint (1表示是,0表示否),此规则同样适用于 odps 建表。任何字段如果为非负数,必须是 unsigned 。因为这样的话可用容量提升了一倍。
表名不使用复数名词。表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于 DO 类名也是单数形式,符合表达习惯。
禁用保留字,如 desc、range、match、delayed 等,禁止在代码里对 SQL 关键字进行单独处理。
唯一索引名为 uk_ 字段名,普通索引名则为 idx_ 字段名。这样能让开发人员一眼就知道相关索引。
如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
表必备三字段: id, gmt_create, gmt_modified 。其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1 。gmt_create, gmt_modified 的类型均为 date_time 类型。创建时间与修改时间需要记录笔者理解,不理解的为什么要用 gmt 开头,北京时间应该是 GMT + 8:00 啊。
字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:不是频繁修改的字段;不是 varchar 超长字段,更不能是 text 字段。比如我司的很多表都冗余了 user_name 这个字段。
单表行数超过 500 万行或者单表容量超过 2 GB ,才推荐进行分库分表。
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。关于 MySQL 的知识,诸位可参考 《高性能MySQL》 。
利用延迟关联或者子查询优化超多分页场景。 MySQL 并不是跳过 offset 行,而是取 offset + N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
建组合索引的时候,区分度最高的在最左边。
不要使用 count(列名) 或 count(常量)来替代 count() ,count()就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和 非NULL 无关。
不得使用外键与级联,一切外键概念必须在应用层解决。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
数据订正时,删除和修改记录时,要先 select ,避免出现误删除,确认无误才能执行更新语句。
工程规约
高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
调大服务器所支持的最大文件句柄数 (File Descriptor,简写为fd) 。
给 JVM 设置 -XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump 信息。
安全规约
隶属于用户个人的页面或者功能必须进行权限控制校验。
用户敏感数据禁止直接展示,必须对展示数据脱敏。
用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库。
用户请求传入的任何参数必须做有效性验证。
表单、AJAX 提交必须执行 CSRF 安全过滤。
在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损。
关于安全这块可以阅读 《白帽子讲Web安全》 。