
大厂 Code Review 中的防御性编程检查单从空指针到集合并发修改异常在大厂参与多人协作的大型后端系统开发时代码的“健壮性Robustness”远比单纯实现业务功能重要得多。在学校写大作业时只要正常输入能跑通大家就觉得大功告成但在每天承载几千万请求的生产环境里各种畸形参数、上游依赖超时、网络并发重试、脏数据污染无时无刻不在发生。一个微小的防御疏漏比如未判空链式调用、多线程下直接使用普通 ArrayList就可能直接引发线上核心服务不可用被定级为 S2/S1 生产事故。今天我把我在团队 Code Review 和线上故障排查中总结的Java 后端防御性编程 7 大铁律检查单整理出来。检查单 1空指针防御与安全解构NPE 防御空指针异常NullPointerException是 Java 线上报错统计中常年霸榜第一的隐患。高危反例// 链式调用未防御任何一级为 null 都会直接触发 NPE String cityName userDTO.getAddress().getCity().getName();防御性范本// 方案 AJava 8 Optional 安全链式解构 String cityName Optional.ofNullable(userDTO) .map(UserDTO::getAddress) .map(AddressDTO::getCity) .map(CityDTO::getName) .orElse(未知城市); // 方案 B常量放在 equals 左侧基础常识 if (SUCCESS.equals(status)) { ... } // 绝对不要写 status.equals(SUCCESS)检查单 2集合并发修改与快速失败ConcurrentModificationException在遍历集合时动态增删元素是新手常犯的典型错误。高危反例// 在 foreach 增强 for 循环中直接 remove触发快速失败机制 for (Ticket ticket : ticketList) { if (ticket.isExpired()) { ticketList.remove(ticket); // 抛出 ConcurrentModificationException } }防御性范本// 方案 A使用 Iterator 显式迭代器安全删除 IteratorTicket iterator ticketList.iterator(); while (iterator.hasNext()) { Ticket ticket iterator.next(); if (ticket.isExpired()) { iterator.remove(); // 安全删除 } } // 方案 BJava 8 removeIf推荐语义清晰 ticketList.removeIf(Ticket::isExpired);检查单 3多线程共享集合的可见性与线程安全单例 ServiceSpring 默认中定义的成员变量集合必须强制保证线程安全。高危反例Service public class ConfigManager { // 致命缺陷单例 Bean 中的非线程安全 HashMap并发 put 会导致扩容死循环或数据丢失 private MapString, ConfigItem configCache new HashMap(); }防御性范本Service public class ConfigManager { // 强制使用 ConcurrentHashMap并配合原子方法 private final MapString, ConfigItem configCache new ConcurrentHashMap(); public ConfigItem getConfig(String key) { // 原子化计算与加载杜绝重复计算与并发竞态 return configCache.computeIfAbsent(key, this::loadFromDb); } }检查单 4包装类型自动拆箱与隐式 NPE在Integer、Long、Boolean参与三元表达式或条件判断时JVM 会隐式调用.intValue()/.booleanValue()。高危反例Long count getNullableCountFromRpc(); // 当 count 为 null 时三元表达式因类型提升触发隐式拆箱直接抛出 NPE long finalCount (condition) ? count : 0L;防御性范本Long count getNullableCountFromRpc(); long finalCount (condition count ! null) ? count : 0L;检查单 5浮点数精确计算与 BigDecimal 构造涉及金额、资产、费率等金钱相关计算严禁使用float/double。高危反例// 存在精度截断误差new BigDecimal(0.1) 实际值为 0.10000000000000000555111512312578... BigDecimal amount new BigDecimal(0.1);防御性范本// 必须使用 String 构造器或 BigDecimal.valueOf BigDecimal amount new BigDecimal(0.1); // 比较大小必须使用 compareTo绝对不能用 equalsequals 会严格比较 scale 标度 if (amount.compareTo(BigDecimal.ZERO) 0) { ... }检查单 6外部资源泄漏与 Try-With-Resources数据库连接、网络流、文件 I/O 必须确保在任何异常情况下 100% 释放。防御性范本// 统一采用 try-with-resources 语法糖自动触发 AutoCloseable try (InputStream is Files.newInputStream(path); BufferedReader reader new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { String line; while ((line reader.readLine()) ! null) { processLine(line); } }检查单 7防御性拷贝Defensive Copying对外暴露内部可变对象如Date、List、Map时防止外部调用方恶意篡改内部状态。防御性范本public ListString getPermissions() { // 返回不可变视图防止外部直接 permissions.add() 破坏内部不变性 return Collections.unmodifiableList(this.permissions); }实习生的 CR 自律每次在把代码提交给 Mentor 之前照着这份检查单从头到尾扫一遍。把防御性思维内化为写代码的肌肉记忆你的代码质量就会成为团队最放心的安全屏障。