码出高效码出质量

栏目: 数据库 · 发布时间: 5年前

内容简介:本文是在作者阅读阿里巴巴 Java 开发手册后做的一些整理。在阅读过程中,作者发现有些错误是自己明确知道要去规避的,有些错误是自己已经犯过的,而有些错误是自己还不知道并且可能会在未来犯的。在此记录,做个警戒。在开发过程中,每一行代码都需要存在的理由,多反思,多总结,多怀疑自己。

本文是在作者阅读阿里巴巴 Java 开发手册后做的一些整理。在阅读过程中,作者发现有些错误是自己明确知道要去规避的,有些错误是自己已经犯过的,而有些错误是自己还不知道并且可能会在未来犯的。在此记录,做个警戒。在开发过程中,每一行代码都需要存在的理由,多反思,多总结,多怀疑自己。

编程规约

命名风格

  • POJO 类中布尔类型变量都不要加 is 前缀,否则部分框架解析会引起序列化错误。注意,在 MySQL 规约中,表达是与否的值采用 is_xxx 的命名方式,所以,需要在 resultMap 设置从 is_xxx 到 xxx 的映射关系。
  • 对于 Service 和 DAO 类,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。如果是形容能力的接口名称,取对于的形容词为接口名(通常是 able 的形容词)。
  • 各层命名规约
    • Service/DAO 层方法命名规约
      • 获取单个对象的方法用 get 做前缀
      • 获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects
      • 获取统计值的方法用 count 做前缀
      • 插入的方法用 save/insert 做前缀
      • 删除的方法用 remove/delete 做前缀
      • 修改的方法用 update 做前缀
    • 领域模型命名规约
      • 数据对象:xxxDO
      • 数据传输对象:xxxDTO
      • 展示对象:xxxVO

OOP 规约

  • 浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断。

    public class FloatEqualsDemo {
        public static void main(String []args) {
            /****************反例******************/
            float a = 1.0f - 0.9f;
            float b = 0.9f - 0.8f;
      
            if (a == b) {
                // 不会进入此代码块
                System.out.println("float a == float b");
            }
      
            Float x = Float.valueOf(a);
            Float y = Float.valueOf(b);
            if (x.equals(y)) {
                // 不会进入此代码块
                System.out.println("Float x == Float y");
            }
      
            /****************正例******************/
            float diff = 1e-6f;
            if (Math.abs(a - b) < diff) {
                System.out.println("float a == float b");
            }
      
            BigDecimal p = new BigDecimal("1.0");
            BigDecimal q = new BigDecimal("0.9");
            BigDecimal r = new BigDecimal("0.8");
      
            BigDecimal m = p.subtract(q);
            BigDecimal n = q.subtract(r);
            if (m.equals(n)) {
                System.out.println("BigDecimal m == BigDecimal n");
            }
        }
    }
  • 为了防止精度损失,禁止使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象。优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法。

  • 序列化类新增属性时,不要修复 serialVersionUID 字段,避免反序列化失败。

集合处理

  • ArrayList 的 subList 结果不可强制转换成 ArrayList,否则会抛出 ClassCastException 异常。subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList 的一个视图。对于 SubList 子列表的操作最终会反映到原列表上。
  • 使用 Map 的方法 keySet、values、entrySet 返回集合对象时,不可以对去进行添加元素的操作,否则会抛出 UnsupportedOperationException 异常。
  • Collections 类返回的对象,如 emptyList、singletonList 等都是 immutable list,不可对其进行添加或者删除元素的操作。
  • 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的参数是类型一致,长度为 0 的空数组。
  • 在使用 Collection 接口任何实现类的 addAll() 方法时,都要对输入的集合参数进行 NPE 判断。

并发处理

  • SImpleDataFormat 是线程不安全的类,一般不要定义 static 变量,如果定义为 static 变量,必须加锁,或者使用 DateUtils 工具类。如果是 JDK8 应用,建议使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
  • 必须回收自定义的 ThreadLocal 变量,尤其是在线程池场景下。
objectThreadLocal.set(userInfo);
try {
    // doSomething();
} finally {
    objectThreadLocal.remove();
}
  • 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
  • 在使用阻塞等待获取锁的方式中,必须在 try 代码块之外获取锁,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
    • 如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其他线程无法成功获取锁。
    • 如果 lock 方法在 try 代码块之间,可能由于其他方法抛出异常,导致在 finally 代码块中,unlock 对未加锁的对象进行解锁,抛出 IllegalMonitorStateException 异常。
/****************正例******************/
Lock lock = new XxxLock();
doSomething();
lock.lock();
// lock() 与 try 之间不能抛出异常
try {
    doOthers();
} finally {
    lock.unlock();
}

/****************反例******************/
Lock lock = new XxxLock();
try {
    // 如果 doSomething 方法抛出异常,则会直接执行 finally 代码块
    doSomething();
    // 无论加锁是否成功,finally 代码块都会执行
    lock.lock();
    doOthers();
} finally {
    lock.unlock();
}
  • 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行,如果在处理定时任务时使用 ScheduledExecutorService 则没有这个问题。

控制语句

  • 当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断。
  • 在高并发场景中,避免使用“等于”作为中断或者退出的条件。如果并发没有控制好,容易产生等值判断被“击穿”的情况,使用大于或者小于的区间判断条件来代替。

MySQL 数据库

建表规约

  • 表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint。
  • 小数类型为 decimal,禁止使用 float 和 double。float 和 double 在存储时存在精度损失的问题,可能导致在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。
  • 如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
  • varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其他字段索引效率。

索引规约

  • 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。不要以为 insert 会影响 insert 的速度,这个速度损耗可以忽略不计,但是对提高查询速度是明显的。另外,即使在应用层做了非常完善的校验机制,只要灭有唯一索引,根据墨菲定律,必然会有脏数据产生。
  • 在 varchar 字段上建立索引时,必须要指定索引长度,没有必要对全字段建立索引,根据实际文本区分度决定索引长度即可。可以使用 count(distinct left(列名, 索引长度))/count(*) 的来确定区分度。

SQL 语句

  • 不要使用 count(列名) 或 count(常亮) 来替代 count(*)。
  • 当某一列的值全为 NULL 时,count(col) 的返回结果为 0,但是 sum(col) 的返回结果为 NULL,因此使用 sum 时需要注意 NPE 问题,建议使用 SELECT IFNULL(SUM(column), 0) FROM table。
  • 代码中写分页查询逻辑是,若 count 为 0 应直接返回,避免执行后面的分页语句。
  • IN 操作能避免则避免,若实在避免不了,需要仔细评估 IN 后边的集合元素数量,控制在 1000 个之内。

ORM 映射

  • 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update tabel set c1=value1,c2=value2;这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
  • @Transactional 事务不要滥用,事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Go Web 编程

Go Web 编程

[新加坡]Sau Sheong Chang(郑兆雄) / 黄健宏 / 人民邮电出版社 / 2017-11-22 / 79

《Go Web 编程》原名《Go Web Programming》,原书由新加坡开发者郑兆雄(Sau Sheong Chang)创作、 Manning 出版社出版,人名邮电出版社引进了该书的中文版权,并将其交由黄健宏进行翻译。 《Go Web 编程》一书围绕一个网络论坛 作为例子,教授读者如何使用请求处理器、多路复用器、模板引擎、存储系统等核心组件去构建一个 Go Web 应用,然后在该应用......一起来看看 《Go Web 编程》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具