【空指针异常,也不全是。】

Hello,你们好呀,我是老丑。

今天的主题是它——空指针异常。Null Pointer Exception。

说明:空指针是 Null Pointer。空指针异常是 Null Pointer Exception。对于空指针的引用,去触发其行为亦或者获取其属性会产生空指针异常。

各位在代码届纵横数年,风光无限.不知道是否还会遇见这臭名昭著的空指针问题呢?想必不会了吧.

注:这篇文章讲的是空指针异常,也不全是。

本篇文章以Java语言输出,但是不局限。

空指针异常,你来了吗?

Null Pointer Exception : 你若'盛开',蝴蝶自来!
各位大佬:那我还是不'盛开'吧!退!退!退!

我们看一个空指针异常的demo

public class NullPointer {
​
    public static void main(String[] args) {
        NullPointer nullPointer = new NullPointer();
        System.out.println(nullPointer.getLength(null));
    }
​
    /**
     * 获取字符串 s 的长度
     * @param s 字符串
     * @author laochou
     */
    public int getLength(String s) {
        return s.length();
    }
}

如上述代码中 getLength() 是在开发过程中十分带有其面向对象四大特性中的封装浓厚色彩的常规操作。

运行上述代码,恭喜你就可以得到一个Null Pointer Exception啦。就像很多人没有boy friend 或者 girl friend,动不动就喊着要New一个出来,是啊,这件事是美好的呀,但是Null Pointer也是生活呀。不要只抬头看天上的月亮,也要偶尔看看地上的六便士一样。

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "s" is null
  at cn.laochou.bug.NullPointer.getLength(NullPointer.java:15)
  at cn.laochou.bug.NullPointer.main(NullPointer.java:7)

Null Pointer Exception 也是生活

空指针异常,一个我们既"爱"又恨的它。

我相信,各位大佬的代码不会出现空指针异常。但是线上环境中,错误日志中时隐时现的Null Pointer Exception,从哪里来的呢?

空指针异常并不可怕,可怕的是你,发现了空指针异常,但你却不去解决它。就如失败并不可怕,可怕的是你不敢面对它

我要面对它

它的进攻虽然猛烈——一大堆的堆栈。但那又何妨。只要我防御做的好,它就攻不进来——防御式编程。

想想也是。历代排除法都是好使的,就比如你考试的时候,题目的答案死活算不出来,但是你知道其他是几个是不可能的,或者是错误的。但这就已经够了——结果是好的,过程我们可以偷偷摸摸的搞吧搞吧。

public static void main(String[] args) {
        NullPointer nullPointer = new NullPointer();
        System.out.println(nullPointer.getLength(null));
    }
​
    /**
     * 获取字符串 s 的长度
     * @param s 字符串
     * @author laochou
     */
    public int getLength(String s) {
        if(s == null) return 0;
        return s.length();
    }
​
    public int getLength(String s) {
        return s == null ? 0 : s.length();
    }
}

以上两种写法都可以避免产生Null Pointer Exception,毕竟空指针的业务场景被我们给Pass掉了。

上述对于getLength()方法,其本质就是对外界完全不信任了。

getLength(): 我能不能完全信任外界呢?
Null Pointer Exception: 当然可以呀,你让外界方法调用你的时候,让它们传递安全可靠的字符串对象就好了呀。
getLength(): 是吗?那还挺好的,那我的底层实现就可以不校验了。
getLength(): 但是我真的可以相信外界吗?它们真的值得去相信吗
internal monologue: 我相信看到这里的各位大佬们,可能会有一些感触哦。我们也想相信这浮躁的社会,但谁也不敢相信,毕竟谁都不想被伤的遍体鳞伤。
Null Pointer Exception: 你还是靠自己吧,外界的哪些大猪蹄子!!!

对于工具方法,是需要防御式编程的。

其实从另外一个角度来看,我们做了防御式编程,底层实现更加内聚了。我们想想,如果由外界来做防御,保证其传递过来的对象是可靠的,那么是否是一种逻辑冗余。调用处全是 if,那是不是可以了解为实现内聚了呢?

代码本身就是追求高内聚,低耦合的。

防御式编程 到 以点带面?怎么回事

我们可以以点带面的思考下,下面一个业务场景。

场景如下:

我们是一个业务系统,业务系统中存在业务项配置,其中业务项配置存在 当前的业务配置状态和上一次的业务配置状态。其中业务配置通过OOA,OOD,OOP最后成为了我们下面的 BusinessConfig。我们提供了一个服务来修改我们的业务配置的状态。BusinessConfigService主要是提供服务。

// 业务配置对象
class BusinessConfig {
  
    // 业务编码
    String businessCode;
    
    // 当前的业务配置状态
    String currentStatus;
    
    // 上一次的业务配置状态
    String lastStatus;
​
    public String getCurrentStatus() {
        return currentStatus;
    }
​
    public void setCurrentStatus(String currentStatus) {
        this.currentStatus = currentStatus;
    }
​
    public String getLastStatus() {
        return lastStatus;
    }
​
    public void setLastStatus(String lastStatus) {
        this.lastStatus = lastStatus;
    }
​
}
// 领域服务
class BusinessConfigService{
    
    // 修改业务配置的状态
    void updateBusinessConfigStatus(BusinessConfig businessConfig, String status) {
        businessConfig.setLastStatus(businessConfig.getCurrentStatus());
        businessConfig.setCurrentStatus(status);
        businessConfigRepository.updateBusinessConfig(businessConfig);
    }
    
}
​
​
// repo
class BusinessConfigRepository {
    void updateBusinessConfig(BusinessConfig businessConfig) {
        mapper.update(businessConfig);
    }
}

各位大佬,请看我们BusinessConfigService.updateBusinessConfigStatus(businessConfig,status)该方法的实现

  1. 我们先修改 businessConfig的上一次状态,设置为 businessConfig的当前的状态
  2. 修改businessConfig的当前状态,设置为该方法入参中的状态。
  3. 调用 businessConfigRepository 来更新 businessConfig

以上貌似看上去很完美,很OK。

思考

// 领域服务
class BusinessConfigService{
    
    // 修改业务配置的状态
    void updateBusinessConfigStatus(BusinessConfig businessConfig, String status) {
        // 这两个状态的切换,真的应该在这里写吗?
        businessConfig.setLastStatus(businessConfig.getCurrentStatus());
        businessConfig.setCurrentStatus(status);
        businessConfigRepository.updateBusinessConfig(businessConfig);
    }
    
}

对于updateBusinessConfigStatus(businessConfig, status) 方法中的两个状态切换,写在这里真的合适吗

Service 层方法,真的需要关注领域对象中的状态变更的逻辑吗?虽然从业务角度上来看,确实可以满足业务诉求

我们看下,下面的实现是不是会更好一些呢?

// 业务配置对象
class BusinessConfig {
  // 代表上面代码块中的 BusinessConfig 都存在
  ...
    
  // 提供一个充血方法,刷新业务配置的状态
  // 由该方法讲上述 服务层的 逻辑内聚在 业务配置对象中
  void refreshBusinessConfigStatus(String status) {
        this.setLastStatus(this.getCurrentStatus());
        this.setCurrentStatus(status);
  }
}

服务层演化:

// 领域服务
class BusinessConfigService{
    
    // 修改业务配置的状态
    void updateBusinessConfigStatus(BusinessConfig businessConfig, String status) {
        businessConfig.refreshBusinessConfigStatus(status);
        businessConfigRepository.updateBusinessConfig(businessConfig);
    }
    
}

从演化后的代码看,在服务层的代码中,其BusinessConfig的状态更新逻辑更加内聚了,该refreshBusinessConfigStatus(status)方法封装了其状态发生变更的所有逻辑,如果有一天,更新BusinessConfig状态的逻辑中还需要更新其他的一些属性,就比如状态更新时间,那么我们的改动点是不是很小了,外面都是使用你的现有资产

当然这其实只是一个demo,大家可能觉得有点多此一举了。但是从项目长期迭代更新过程中,你累积的领域资产也就越来越多。迭代起来会越来越快。

跑题了,得拉回来。

OK,我们讲的还是空指针异常。在Java语言中,存在一项黑科技——Optional

// 这样写 是不是更优美了 可读性也增强了。
public int getLength(String s) {
    // 优雅从不过时
    return Optional.ofNullable(s).map(String::length).orElse(0);
}

在日常开发过程中,其实 Optional 用的非常多,判空利器

Optional + Stream YYDS

对于集合的过滤,筛选

// 去除集合中的Null和空元素
public List<String> removeEmpty(List<String> codes) {
    // orElseGet 就是当 ofNull 成立的话,就直接使用 orElseGet所得到的对象进行后续的处理。
    return Optional.ofNullable(codes).orElseGet(() -> new ArrayList<>(0))
        .stream()
        .filter(Objects::nonNull)
        .filter(StringUtils::isNotEmpty)
        .collect(Collectors.toList());
}

传入的集合空的,直接抛出业务异常

public List<String> getCodes(List<String> codes) {
    return Optional.ofNullable(codes).orElseThrow(() -> new IllegalArgumentException("codes can't be null"))
      .stream()
      .filter(Objects::nonNull)
      .filter(StringUtils::isNotEmpty)
      .collect(Collectors.toList());
}

这样的代码不香吗?各位大佬,please 秀出你们的Optional使用手法。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值