01 清晨的Flag:今天绝不加班
“今晚一定要早睡!” 凌晨1点23分,李默第7次按下IDEA的运行键。屏幕右下角的微信图标疯狂跳动,产品经理发来第5条语音:“客户说这个订单状态查询接口必须今天上线......”
作为某电商公司的Java工程师,他正在改造一个祖传代码——十年前用JDK1.4写的订单系统。为了兼容新支付渠道,他给`OrderService`加了个新方法:
public BigDecimal calculateDiscount(User user) {
return user.getMemberLevel().getDiscount().multiply(new BigDecimal("0.9"));
}
“就三行代码能出什么问题?”李默抿了口冷掉的速溶咖啡,顺手点击了部署按钮。
02 午夜的惊雷:NullPointerException虽迟但到
凌晨3点17分,监控系统突然告警——订单服务崩溃了!日志里赫然躺着:
java.lang.NullPointerException:
at com.xxx.OrderService.calculateDiscount(OrderService.java:47)
李默盯着报错行,突然想起测试用例只覆盖了正常会员用户。而那些未登录的游客用户,他们的`user.getMemberLevel()`,可不就是`null`吗?
03 咖啡因与防御式编程
酒馆老板老K的深夜点评:
> “这就像调酒时忘记检查客人是否成年——直接递出烈酒,迟早要出事!”
李默颤抖着手补上防御代码:
public BigDecimal calculateDiscount(User user) {
if (user == null || user.getMemberLevel() == null) {
return BigDecimal.ZERO;
}
Discount discount = user.getMemberLevel().getDiscount();
return discount != null ? discount.getValue().multiply(new BigDecimal("0.9")) : BigDecimal.ZERO;
}
但这样够了吗? 当`discount.getValue()`返回`null`时,空指针的幽灵仍在黑暗中狞笑。
04 Optional:与null和解的艺术
**酒馆老板老K递上一杯“非空特调”**:
> “Java8的Optional不是银弹,但至少能让null穿上防弹衣”
重构后的代码:
public BigDecimal calculateDiscount(User user) {
return Optional.ofNullable(user)
.map(User::getMemberLevel)
.map(MemberLevel::getDiscount)
.map(d -> d.getValue().multiply(new BigDecimal("0.9")))
.orElse(BigDecimal.ZERO);
}
注意陷阱:
- 不要用Optional作为方法参数(会制造新的null问题)
- 避免`Optional.get()`直接取值(需配合isPresent判断)
05 清晨6点的顿悟
当第一缕阳光照进办公室时,李默终于明白:
1. **防御式编码不是万能**:过度判空会让代码变成`if地狱`
2. **契约式设计更优雅**:在方法注释中明确约定“user对象必须非空且具有完整会员信息”
3. **工具才是终极解药**:
- 启用IDEA的`@NotNull/@Nullable`注解提示
- 用SonarQube配置空指针检测规则
- 单元测试必须覆盖所有null分支