1.常见问题避坑记载

1.1 为什么写这个常见问题避坑记载?

看到公司同事的代码经常写的不规范,呈现比较多低级过错,所以写这个文章阐明常见的各种问题,后续将继续完善该文章,不断积累记载的一起进步本身代码质量。

1.2 NPE问题

1.2.1 非正确运用目标以及特点

Person person = personMapper.selectById(123L);
if (!person.getUserName().isEmpty()) {
    // doSomething...
}

上述代码中想完成的作用是,查询id为123的person信息,然后判别假如userName不为空,就进行逻辑的处理,可是经过观察代码,能够直接发现2处过错

  1. 没有处理person为null的状况
  2. 没有处理person.getUserName()为null的状况

为什么会这么说呢,来依次验证一下,首要是数据库里是没有准备id为123的person信息,所以这儿必定是查不到信息的,开始测试:

  1. 验证状况1,不处理person为null的状况,直接进行运用
Person person = personMapper.selectById(123L);
String userName = person.getUserName();

运转成果

Java避坑记录

Java避坑记录

原因剖析

能够看到上述代码在查询不到person信息的时分,直接调用person.getUserName()确实呈现了NPE,由于person查不到就代表person == null,null.getUserName()必定就NPE了

  1. 验证状况2,查询到person信息之后,不正确的运用person上的特点,例如person.getUserName() 咱们把上面的代码略微改改,对person进行处理之后再用它的特点,这儿我提前准备好了一条id为1685291564680609793的数据,这条数据的userName为null,开始测试:
Person person = personMapper.selectById(1685291564680609793L);
if (Objects.nonNull(person) && !person.getUserName().isEmpty()) {
    // doSomething...
}

运转成果

Java避坑记录

Java避坑记录

原因剖析

上述代码的原意是在userName不为空的时分进行逻辑处理,可是判别的方式却呈现了过错。假如person.getUserName()为null,那么person.getUserName().isEmpty()就等价于null.isEmpty(),因而就会呈现NPE

Java避坑记录

Java避坑记录

那么咱们应该怎样正确判别字符串为空呢,咱们能够运用他人造好的轮子,直接引入这个依靠

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

然后咱们就能够运用StringUtils.isNotEmpty()办法来帮咱们正确的判别字符串的为空 略微修正一下上面的代码:

Person person = personMapper.selectById(1685291564680609793L);
if (Objects.nonNull(person) && StringUtils.isNotEmpty(person.getUserName())) {
    // doSomething...
}

这样咱们就能够正确的判别字符串为空了

弥补阐明

StringUtils.isEmpty()和StringUtils.isBlank()的作用不一样,前者不答应字符串中有空格,只判别null和length,后者答应存在空格

1.2.2 非正确运用调集进行查询

有的时分咱们需求经过服务调用或许办法传递过来的参数进行查询,假设下面的nameList的来源是外部,不能确认传进来的是什么,或许是null、或许是[]、也或许是正常的list,那么咱们直接调用下面的办法就有或许呈现问题,假如nameList为null的时分就会呈现NPE

List<String> nameList = null;
List<Person> people = personMapper.selectList(Wrappers.<Person>lambdaQuery().in(Person::getUserName, nameList));

运转成果

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression 'ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfNormal'. Cause: org.apache.ibatis.ognl.OgnlException: sqlSegment [java.lang.NullPointerException]

原因剖析

nameList为null,条件为in的时分传给mybatis一个null会导致NPE。那么应该怎样解决呢?需求结合详细的场景,能够在service层就处理nameList,确保调用到这儿的时分nameList不为null,或许假如一定要在这儿调用的话,能够在in前面加上条件,修正之后的代码如下,这样就不会NPE

List<String> nameList = null;
List<Person> people = personMapper.selectList(Wrappers.<Person>lambdaQuery()
        .in(!CollectionUtils.isEmpty(nameList), Person::getUserName, nameList));

1.2.3 equals的不正确运用

在办法中,直接用入参调用equals,这样假如key为null的时分就会呈现NPE

public void testMethod(Integer key) {
    if (key.equals(1)) {
        // doSomething...
    }
}

改善如下:

  1. 把已知常量作为equals的调用方
if (1.equals(key)) {
    // doSomething...
}
  1. 运用Objects.equals办法
if (Objects.equals(1, key)) {
    // doSomething...
}

1.3 “多此一举”问题

1.3.1 mybatis查询调集之后做剩余的判别

List<Person> list = personMapper.selectList(Wrappers.<Person>lambdaQuery().eq(Person::getId, 123L));
System.out.println("list = " + list);  
if (list != null && list.isEmpty()) {  
// doSomething ...  
}

检查控制台输出的list

JDBC Connection [HikariProxyConnection@1703458581 wrapping com.mysql.cj.jdbc.ConnectionImpl@15a3b42] will not be managed by Spring
==>  Preparing: SELECT id,user_name,sex,age,deleted,version,create_time,update_time FROM person WHERE deleted=0 AND (id = ?)
==> Parameters: 123(Long)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@688a2c09]
list = []

能够看到,就算查询不到数据,mybatis给咱们回来的也是个空数组,不会呈现成果为null的状况,因而能够删除list != null这个判别条件

弥补阐明

假如某些状况需求对查询到的list进行判空,然后安全的进行处理,大可不必直接写list != null || list.isEmpty(),spring给咱们供给了许多个工具类,咱们能够直接用,简化开发,不要重复造轮子

  1. CollectionUtils.isEmpty()
List<String> list;
if (!CollectionUtils.isEmpty(list)) {  
    // doSomething...  
}

详细完成:

public static boolean isEmpty(@Nullable Collection<?> collection) {
    return collection == null || collection.isEmpty();
}

2.ObjectUtils.isEmpty()

List<String> list = new ArrayList<>();
if (!ObjectUtils.isEmpty(list)) {
    // doSomething...
}

详细完成:

public static boolean isEmpty(@Nullable Object obj) {
    if (obj == null) {
        return true;
    } else if (obj instanceof Optional) {
        return !((Optional)obj).isPresent();
    } else if (obj instanceof CharSequence) {
        return ((CharSequence)obj).length() == 0;
    } else if (obj.getClass().isArray()) {
        return Array.getLength(obj) == 0;
    } else if (obj instanceof Collection) {
        return ((Collection)obj).isEmpty();
    } else {
        return obj instanceof Map ? ((Map)obj).isEmpty() : false;
    }
}

1.4 魔法值问题

Person person = new Person().setSex(0);
personMapper.insert(person);

如上述代码所示,新添加一个Person的信息,设置sex为0,或许自己开发的时分知道0代表的是男仍是女,可是其他同事来阅览代码会呈现不知道这个0是什么意思,代表的是男仍是女,得翻来覆去的找,浪费时间。因而咱们在写代码的时分应该注意,应该依据详细场景采用枚举或许常量来代表魔法值,假如是常量需求判别该常量需求作为当前类的常量仍是需求作为大局常量来运用,优化代码:

  1. 新建枚举
@Getter
@AllArgsConstructor
public enum PersonEnum {
    /**
     * 性别:男
     */
    SEX_MALE("男", 0),
    /**
     * 性别:女
     */
    SEX_FEMALE("女", 1);
    private final String key;
    private final Integer value;
}
  1. 修正原代码
Person person = new Person().setSex(PersonEnum.SEX_MALE.getValue());
personMapper.insert(person);

能够看到修正之后可读性变强了。

1.5 代码逻辑问题

List<Person> personList;
if (!personList.isEmpty() || peopleList != null) {
    // doSomething...
}

假设上面的personList为null,那这个if判别是不是又NPE了,怎样能够先把isEmpty()放到前面先判别呢,而且peopleList != null放到 || 的后面,就算能执行到这儿,必定永远是true,所以像这种过错是能够防止的

1.6 办法命名不明晰

现有个办法,想完成依据userName(用户名字)查询最近20条操作记载。

过错示例:public List<OperationRecord> selectTwenty(String userName)

为什么说上面的办法命名不明晰呢?

首要我第一眼看到这个办法,我不知道查询20什么,然后办法里又有条件,会让人产生疑惑,看不懂这个代码,还需求进行推理才能知道要表达的意思,因而才说这个办法命名不明晰。

怎样改善?

一个办法的命名无非就是为了见名知意,让其他人以最小的代价就能知道你这个办法做的事情。无非就是为了告诉他人,你这个办法是经过xxx查询xxx,假如改善成下面这个名字,是不是更加明晰呢? public List<OperationRecord> selectLatestTwentyOperationRecordListByUserName(String userName) 是不是相对上面来说就明晰得多了。 假如看到这儿觉得比较长,不要紧,还能够进一步优化,假如所处的上下文都是只查询操作记载表的信息,那么咱们能够优化为public List<OperationRecord> selectLatestTwentyListByUserName(String userName),进步代码可读性的一起减少了办法长度

1.7 办法回来值不一致

private List<Person> selectAllPersonList() {
    List<Person> list;
    if ("xxx".equals("xxx")) {
        return null;
    }
    return list;
}

上述代码的问题在于假如正常回来的时分是一个List<Person>,可是if代码块里又有回来null的,这个给到前端处理起来或许会比较费事,应该改成一致的格局,假如没查询到数据,应该也是回来空数组,所以应该修正一下,坚持回来数据格局的一致

if ("xxx".equals("xxx")) {
    return Collections.emptyList();
}

1.8 常量方位存放过错

这个主要是发现前端有一些常量提取到了api/xxx/xxx.ts下,在项目结构中,这儿是存专门用于恳求的api,不是存常量,把常量也放到这儿这样会弄得很紊乱,要依照分类把文件放到它应该在的地方……

1.9 办法修正之后没有同步注释

/**
 * 依据用户工号查询用户信息
 *
 * @param userName 用户名字
 * @return 用户信息调集
 */
public List<Person> selectPersonListByName(String userName) {
}

上述代码的问题在于,注释写的是依据用户工号查询用户信息,可是参数又是名字,这样会产生歧义,假如修正办法之后注释与办法的作用不一致,需求把办法的注释进行同步

2 总结

上述代码问题都是比较常见的,而且能够防止掉的,不然写出来的代码必定存在许多问题。当然了,上面只是很小的一部分,还有其他的常见的问题需求在实际开发过程中多思考,不断总结改善。