1.命名规范
1.类名 驼峰式命名规则 MarcoPolo
2.方法名 小写开头&单词首字母大写 testMethod
3.常量 大写单词,单词间使用_分割,含义明显 MAX_TICKET_COUNT
4.抽象类 以Abstract / Base 开始,异常类使用Exception 结束 , 测试类使用 Test结束
5.boolean 类型 , 变量不以 isXXX 命名
6.包名统一英文单词单数形式 , 不使用缩写
7.接口中不加修饰 ,public abstract 不写
8.形容能力的接口使用 -able结尾
2.代码格式
1.在贴近括号两边不出现空格,if / for / while / switch / do 在括号中表达逻辑时,必须加入空格。
2.二目、三目运算符的左右两边都需要加一个空格 a > b a > b? c : d
3.第二行相对第一行缩进4个空格,其他不缩进
4.传递参数的时候需要用空格隔开
5.不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
3.OOP规约
1.访问类中静态方法 、属性值,直接通过类名.方法名 、类名.属性名
2.过时的接口,使用@Deprecated 注解
3.不能使用过时的类和方法
4.常量或确定有值的对象调用equals方法 需要将常量写在前面,避免空指针 “test”.equals(str)
5.包装类对象之间值得比较,全部使用equals方法比较
6.POJO 类属性必须使用包装数据类型,RPC方法的返回值和参数必须使用包装数据类型
7.所有的局部变量使用基本数据类型
8.定义 DO / DTO / VO 等 POJO类 , 不要设定任何默认值
9.POJO 类必须写 toString 方法
10.字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展
补充 内容(8):
1、PO即persistant Object 持久对象: 在O/R 映射(即ORM-ObjectRelationMapping)中出现的概念,通常对应数据模型(数据库),是与数据库汇总的表想影射的java对象,最简单的PO就是对应数据库中某个表中的一条记录,多个记录则用PO的集合。PO中不应该包含任何对数据库的操作。
2、DO即Domain Object 领域对象: 是从现实世界中抽象出来的有形或无形的业务实体。
3、TO即Transfer Object数据传输对象: 不同应用程序之间传输的对象
4、DTO即Data Transfer Object:数据传输对象: 泛指用于展示层与服务层之间的数据传输对象
5、VO即value Object: 通常用于业务层之间的数据传递,和PO一样仅包含数据,但是抽象出的业务对象,可以和表对应,用new 关键字创建,GC回收
6、BO即Business Object 业务对象: 主要是将业务逻辑封装为一个对象,这个对象可以包含一个或多个其他对象,如一个简历中包含教育经历、工作经历、社会关系等,可以将一个教育经历对应一个PO、工作经历对应一个PO、设计关系对应一个PO,然后简历一个对应简历的BO兑现处理简历,每个BO包含这个PO这样处理业务逻辑是,可以针对BO去处理。封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作。
7、POJO即Plain Ordinary Java Object: 简单无规则的java对性,即在一些O/R 映射工具中,能做到维护数据库表记录的PO完全是一个符合Java Bean规范的纯java对象
4.集合处理
1.只要重写 equals ,就必须重写 hashCode. 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals 。
2.ArrayList 的 subList (List)结果不可强转成 ArrayList ,否则会抛出 ClassCastException 异常 。
3.Arrays asList() 把数组转换成集合时,不能使用其修改集合相关的方法,使用 add / remove / clear 方法会抛出 java.lang.UnsupportedOperationException异常,asList 的返回对象是一个 Arrays内部类,并没有实现集合的修改方法。
示例
int[] nums = new int[]{1,2,3,4,5};
List<int[]> ints = Arrays.asList(nums);
ints.add(new int[]{2});
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at fatcats.top.Main.main(Main.java:261)
4.泛型通配符<? extends T> 来接受返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T> 不能使用 get 方法,作为接口调用赋值时容易出错。频繁往外读取内容,使用<? extends T> ;经常往里面插入数据,使用<? super T>
5.不要在 foreach 循环中 进行增删改操作 (remove / add)操作. remove 元素使用 Iterator 方式,如果是并发操作,需要对 Iterator 对象加锁。
6.集合初始化时,指定集合初始值大小。说明:HashMap 使用 HashMap(int initialCapacity)初始化,正例:initialCapacity = (需要储存的元素个数 / 负载因子) + 1. 注意负载因子(即 loaderfactor)默认为0.75 ,如果暂时无法确定初始值大小,请设置为 16(默认值)。
7.使用 entrySet 遍历 Map 类集合KV,而不是 keySet 方式进行遍历。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。
集合类 | Key | Value | Super | 说明 |
HashTable | 不允许为NULL | 不允许为NULL | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许为NULL | 不允许为NULL | AbstractMap | 锁分段技术(JDK8 CAS) |
TreeMap | 不允许为NULL | 允许为NULL | AbstractMap | 线程不安全 |
HashMap | 允许为NULL | 允许为NULL | AbstractMap | 线程不安全 |
5.并发处理
1.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
2.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,更加明确线程池的运行规则,规避资源耗尽风险。
3.SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量, 强行使用的话,需要加锁。JDK8 以上可以使用 DateTimeFirmatter 代替 ,它的线程是安全的。
4. 多线程并行处理定时任务时,Timer 运行多个 TimerTask 时,只要其中之一没有捕获抛出的异常,其他的任务都会自动终止运行,可以使用 ScheduledExecutorService 解决。
5.避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一个 seed 导致的性能下降。JDK7 后 可使用 ThreadLocalRandom .
6.在并发场景下,通过双重检查锁,实现延迟初始化的优化问题隐患,推荐解决方案中较为简单的一种,将目标属性声明 volatile 型。
7.volatile 解决多线程内容不可见问题对于一写多读,是可以解决变量同步问题。对于多写,同意是无法解决线程安全问题。对于 count++ 操作,使用 AtomicInteger 类实现,count.addAndGet(1);对于JDK8,使用LongAdder 对象。
8.HashMap 在容量不够进行 resize 时,在高并发环境下可能出现死链 ,导致CPU 飙升,在开发过程中可以使用其他数据结构或加锁来规避此风险。
6.控制语句
1.表达异常分支时,少用 if-else 方法,可以改写成return 返回
if(condition){
...
return obj;
}
2.方法的返回值可以为 null, 不强制返回空集合、空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断,防止 NPE(NullPointerException 空指针) 问题。
3.定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException() ,更不允许抛出 Exception 或者 Throwable , 应使用有业务含义的自定义异常。
4.应用中不可直接使用日志系统(Log4j 、Logback)中的API ,应该使用日志框架SLF4J中的API,使用门面模式的日志框架,便于维护。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** logger */
private static Logger logger = LoggerFactory.getLogger(A.class);
5.避免重复打印日志,浪费磁盘控件,务必在log4j.xml中,设置 additivity = false。
<logger name="com.taobao.dubbo.config" additivity="false">
7.单元测试
1.单元测试应该是全自动执行的,并且非交互式的。
2.测试框架通常是定期执行的,执行过程必须全自动化。
3.单元测试中不允许使用 System.out 来进行验证,必须使用 assert 验证。(断言)
8.Mysql
1.表达是否概念的字段,必须使用 is_xxx 方式命名,数据类型 unsigned tinyint (1 表示是,0表示否)
2.主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
3.小数类型为 decimal ,禁止使用 float 和 double。
4.表必备三字段:id,gmt_create , gmt_modified
5.表的命名最好是“业务名称_表的作用”
6.超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致; 多表关联查询时,保证被关联的字段需要有索引。
7.页面搜索严禁左模糊或者全模糊,使用搜索引擎来解决此问题。索引文件具有 B-tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。
8.不要使用 count( 列名 ) 或 count( 常量 ) 来替代 count(*) , count() 是 SQL 92定义的标准统计行数的语法,跟数据库、NULL 、非NULL 无关。count( *) 会统计值为NULL的行,而 count(列名)不会统计此列为NULL值得行。
9.count(distinct col)计算该列除NULL之外的不重复的行数,注意 count(distinct col1 ,col2) 如果其中一列全为NULL , 那么即使另一列有不同的值,也返回为0.
10.当某一列的值全是NULL时,count(col) 的返回结果为0 ,但是 sum(col) 的返回结果为NULL ,因此使用 sum() 时需要注意 NPE 问题。可以使用 SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
11.使用ISNULL() 判断是否为 NULL 值。NULL 与任何值的比较都为NULL。
12.分页查询逻辑,若 count 为 0 应该直接返回,避免执行后面的分页语句。
13.不得使用外键与级联,一切外键概念必须在应用层解决。
9.服务器
1.高并发的服务器建议调小TCP协议的time_wait超时时间。操作系统默认是240秒才会关闭处于time_wait的链接,高并发下服务端会因为处于time_wait链接数太多,无法建立新连接,需要调小等待值。
在linux服务器上通过变更/etc/sysctl.conf 修改缺省值
net.ipv4.tcp_fin_timeout = 30
2.调大服务器所支持最大文件的句柄数。主流操作系统将TCP/UDP链接采用与文件一样的连接方式管理,一个连接对应一个fd.
linux 默认 fd数为1024.并发数过大会导致 “open too many files"错误
3.给JVM设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM输出Dump
4.线上JVM的Xms初始堆大小和Xmx最大堆大小一样储存容量,避免GC调整给堆带来压力。
5.服务器内重定向使用forward,外部重定向使用URL拼装工具生成,否则带来URL维护不一致问题。
10.二方库依赖
1.线上应用不要依赖snapsho版本,不依赖是保证发布的幂等性。
2.二方库的新增或者升级,保持除功能点之外的其他jar包仲裁结果不变。如果有改变,必须明确评估和验证,建议进行dependency:resolve前后信息对比,如果仲裁结果完全不一致,通过dependency:tree找出差异点,进行excludes排除jar包。
3.二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型,或包含枚举类型的pojo。
4.依赖于一个二方库时,必须定义一个统一的版本,避免版本号不一致。
11.应用分层
1.在Dao层,无法用细粒度异常进行catch,所以使用catch(Exception e)方式,兵throw new DAOException(e) 不进行打印。
在manager/service层进行捕获,并打印到日志中,service层讲日志输出到磁盘,web层跳转到友好界面。
12.ORM映射
1.查询表的时候,一律不使用 * 作为查询字段列表,必须写明需要查询的字段。
2.pojo属性不能加is ,数据库必须要加is_xxx,需要在mybatis生成器中将代码进行修改。
3.sql.xml配置参数使用 #{} , 不要使用 ${} ,会发生SQL注入问题。
4.不允许直接拿HashMap和HashTable作为查询结果集的输出。
5.事务不要滥用,事务影响数据库的QPS,使用事务的地方需要考虑各方面的回滚。
13.SQL语句
1.禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
2.数据订正时,删除和修改记录,要先使用select 查询,避免出现误删除,确认无误避免出现错误删除。
3.in操作能避免就避免,不能避免要控制in后边集合数量,控制在1000个之内。
4.如果有全球化的需求,所有字符使用utf-8进行储存。
select length("轻松工作") // 返回12
select character_length("轻松工作") // 返回4
存储表情用utfmb4进行储存,注意它和utf-8的区别。
5.不建议使用truncate。
14.索引规约
1.业务上具有唯一特性的字段,即使多个字段的组合,也必须构建唯一索引。
2.在varchar上创建索引,必须指明索引长度,没有必要对全字段建立索引,根据实际文本区分度决定索引长度即可。
count(distinct left(列名,索引长度)) /count(*)
3.如果有order by 场景,注意利用索引的有序性,order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。
索引 a_b_c where a = ? and b = ? order by c
4.利用覆盖索引来进行查询,避免回表,能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种效果,用explain的结果,extra列会出现。
5.利用延迟关联或者子查询优化差多分页场景。
6.SQL的性能目标,至少要达到range级别,要求是ref级别,如果可以是consts最好consts单表中最多只能有一个匹配行,在优化阶段即可读取到数据。ref指的是使用普通索引。
7.建立组合索引的时候,区分度最高的在最左面,如果 where a = ? and b = ? a列几乎接近于唯一值,那么只需要单建idx_a索引即可。
存在非等号和等号混合判断条件时,在创建索引时,把等号条件的列前置。where a > ? and b = ? 即使a的区分度很高也需要b放在索引的最前面。
8.防止字段类型不同锁造成的隐式转化,导致索引失效。
9.创建索引要避免宁滥勿缺,认为查询需要创建一个索引,宁缺毋滥也不要,认为索引会消耗控件,拖慢更新和新增速度。地址唯一索引,认为唯一索引需要在应用层通过先插后插方式解决。
10.varchar 是可变长字符串 ,不预先分配储存控件,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出一张表,用主键来对应,避免影响其他字段索引效率。
11.单行表数据超过500万或者单行表容量超过2GB,才推荐分库分表
12.合适的字符存储长度,不但节约数据库的表空间,节约索引的储存,更重要的是提升检索速度。
15.安全规则
1.用户个人的页面必须进行权限校验。
2.用户铭感数据禁止直接展示,必须脱敏,手机号隐藏中间4位。
3.用户输入的sql参数严格禁止使用参数绑定或者metadata字段值限定,防止SQL注入,禁止字符串拼接SQL访问数据库。
4.用户请求传入的参数必须进行有效的验证:否则导致
1).page size过大内存溢出
2).恶意order by 导致数据库查询慢
3).任意重定向
4).SQL注入
5).反序列化注入
6).正则输入源串拒绝服务ReDos
5.禁止向HTML页面输出未经安全过滤或者未正确转义的用户数据。
6.表单、AJAX提交必须执行CSRF安全过滤。
CSRF跨站请求伪造是一类常见的编程漏洞,对于存在CSRF漏洞的应用网站,攻击者可以事先构造好URL,只要受害用户已访问,后台便在用户不知情的情况下对数据库进行修改。
7.单元测试可以重复执行,不能受外面环境的影响,在设计时就要把SUT改为注入,在测试时使用spring这样的DI框架注入一个本地实现。
16.异常处理
1.java 类库中定义的一类RuntimeException可以通过预先检查进行规避,而不应该通过catch进行处理,比如IndexOutOfBoundsException,NullPointerException。
2.有try块放到事务代码中,catch后,需要事务回滚,一定注意手动回滚。
3.不能在finally中使用return,finally块中的return返回方法后结束执行,不会再执行try中return语句。
4.方法的返回值可以为null,不强制返回空集合和空对象,必须添加注释说明什么情况下返回为空。
17.其他
1.在使用正则表达式时,要利用预编译,加快正则匹配速度,定义正则的时候不要在方法体内进行。
2.volocity调用POJO类属性的时候,使用属性名取名即可,模板引擎会自动按照规约调用Pojo的getXxx(),如果是boolean基本数据类型 ,调用isXxx(),如果是Boolean包装对象,调用getXxx()方法。
3.后台输出给页面的变量必须加!var中间感叹号,如果var = null 不存在,则{var}不会显示到页面上
4.任何数据结构构造和初始化,都应该指定大小,避免数据结构无限增长,吃光内存。
5.对于暂时被注释掉,后续可能恢复使用的代码片段,统一使用 ///来说明注释掉代码的理由。