Java 开发手册笔记

Java 开发手册笔记

基于嵩山版

一、OOP 规约

1.1【强制】如上所示 BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法。说明:equals() 方法会比较值和精度 (1.0 与 1.00 返回结果为 false) ,而 compareTo() 则会忽略精度

二、并发处理

2.1【强制】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

2.2【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收

正例:

objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}

2.3【强制】在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁

正例:

Lock lock = new XxxLock();
// ...
lock.lock();
try {
    doSomething();
    doOthers();
} finally {
    lock.unlock();
}

2.4【推荐】避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降

说明:Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。、

正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保证每个线程持有一个单独的 Random 实例

2.5【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题

说明:如果是 count++操作,使用如下类实现:

AtomicInteger count = new AtomicInteger(); 
count.addAndGet(1); 

如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)

2.6【参考】ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题

说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量

三、前后端规约

3.1【强制】前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合 {}

3.2【强制】对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用 Long 类型

3.3【强制】HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错

说明:nginx 默认限制是 1MB,tomcat 默认限制为 2MB,当确实有业务需要传较大内容时,可以通过调大服务器端的限制

3.4【强制】HTTP 请求通过 URL 传递参数时,不能超过 2048 字节

说明:不同浏览器对于 URL 的最大长度限制略有不同,并且对超出最大长度的处理逻辑也有差异,2048 字节是取所有浏览器的最小值

四、日期时间

【强制】日期格式化时,传入 pattern 中表示年份统一使用小写的 y。说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY 就是下一年
正例:表示日期和时间的格式如下所示:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

【强制】在日期格式中分清楚大写的 M 和小写的 m,大写的 H 和小写的 h 分别指代的意义。说明:日期格式中的这两对字母表意如下:

  • 表示月份是大写的 M;
  • 表示分钟则是小写的 m;
  • 24 小时制的是大写的 H;
  • 12 小时制的则是小写的 h

【强制】不要在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误

正例:

// 获取今年的天数
int daysOfThisYear = LocalDate.now().lengthOfYear();
// 获取指定某年的天数
LocalDate.of(2011, 1, 1).lengthOfYear();

反例:

// 第一种情况:在闰年 366 天时,出现数组越界异常
int[] dayArray = new int[365];
// 第二种情况:一年有效期的会员制,今年 1 月 26 日注册,硬编码 365 返回的却是 1 月 25 日
Calendar calendar = Calendar.getInstance();
calendar.set(2020, 1, 26);
calendar.add(Calendar.DATE, 365);

【强制】判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size()==0 的方式

五、异常日志

5.1【强制】 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理

5.2【强制】不要在 finally 块中使用 return

说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点

5.3【强制】在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable 类来进行拦截

5.4【推荐】防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景

正例:使用 JDK8 的 Optional 类来防止 NPE 问题

5.5【强制】在日志输出时,字符串变量之间的拼接使用占位符的方式

说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅是替换动作,可以有效提升性能

正例: logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

六、单元测试

6.1【强制】好的单元测试必须遵守 AIR 原则

说明:单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点

  • A:Automatic(自动化)
  • I:Independent(独立性)
  • R:Repeatable(可重复)

七、MySQL 数据库

7.1【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint(1 表示是,0 表示否)

说明:任何字段如果为非负数,必须是 unsigned

注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在 设置从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围

正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除

7.2【强制】表名、字段名必须使用小写字母或数字 , 禁止出现数字开头,禁止两个下划线中间只出现数字

7.3【强制】主键索引名为 pk_ 字段名;唯一索引名为 uk _ 字段名 ; 普通索引名则为 idx_ 字段名

说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称

7.4【强制】小数类型为 decimal,禁止使用 float 和 double

说明:在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储

7.5【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型

7.6【强制】 varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text ,独立出来一张表,用主键来对应,避免影响其它字段索引效率

7.7【强制】表必备三字段:id, create_time, update_time

说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time 的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新

7.8【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left (列名, 索引长度)) / count(*) 的区分度来确定

7.9【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关

说明:count(*) 会统计值为 NULL 的行,而 count(列名) 不会统计此列为 NULL 值的行

7.10【强制】count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1,col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0

7.11【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题

正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column), 0) FROM table;

7.12【强制】使用 ISNULL() 来判断是否为 NULL 值

反例:在 SQL 语句中,如果在 null 前换行,影响可读性。select * from table where column1 is null and column3 is not null; 而ISNULL(column)是一个整体,简洁易懂。从性能数据上分析,ISNULL(column) 执行效率更快一些

7.13【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射

7.14【强制】 iBATIS 自带的 queryForList(String statementName , int start , int size) 不推荐使用

说明:其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取 start,size 的子集合

正例:

Map<String, Object> map = new HashMap<>(16);
map.put("start", start);
map.put("size", size);

7.15【强制】不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出

反例:某同学为避免写一个 xxx,直接使用 HashTable 来接收数据库返回结果,结果出现日常是把 bigint 转成 Long 值,而线上由于数据库版本不一样,解析成 BigInteger,导致线上问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值