
秋招手撕代码自测清单边界值、空指针、大数越界的五项防御性编程检查很多同学在秋招技术面中经常遇到这样的场景面对一道中等难度的算法题思路非常清晰用了不到十分钟行云流水写完三十多行核心代码随后信心满满地告诉面试官“我写完了”。面试官扫了一眼屏幕微微一笑“如果入参传入null呢如果数组里全部是重复元素呢如果数值累加触发了溢出怎么处理”这时候才手忙脚乱地回过头去补if分支不仅打乱了原有的代码结构还容易引入新的逻辑漏洞。在面试官眼里手撕代码考察的从来不单单是“能否把题解背出来”更是候选人在高压环境下编写生产级代码的工程素养与防御性编程Defensive Programming习惯。一段合格的代码在核心业务逻辑运转之前应当能够抵御各类恶意、边界以及异常输入。结合真实面试与日常高频踩坑我梳理出这份手撕代码交付前的“五项防御性自测清单”。写完代码后不要急着交卷花两分钟按顺序走完这五项检查能直接规避 90% 的低级失分。检查一入口级空值与空结构拦截Null Empty Check绝大多数线上 NullPointerExceptionNPE或 Panic本质上都是因为函数入口没有严格校验契约。在白板或 IDE 面试中入口检查是展现编码成熟度的第一道门面。1. 指针与容器的双重校验对于数组、字符串、链表、树等复合数据结构必须区分“指针未初始化null”与“结构体为空empty”。数组与字符串务必使用短路逻辑联合校验。// 错误写法如果 nums 为 nullnums.length 会直接抛出 NullPointerException if (nums.length 0) return 0; // 正确写法利用逻辑与/或的短路特性优先拦截 null if (nums null || nums.length 0) { return 0; }链表与树节点单链表翻转、树的层序遍历等题目中头节点为null属于完全合法的输入。代码第一行必须明确处理根节点为空时的返回值避免后续解引用崩溃。2. 规模不足以支撑算法启动的极端场景除了null和size 0还要警惕容器长度小于算法窗口下限的场景。例如滑动窗口求长度为k的最大子数组和public int maxSubArrayLen(int[] nums, int k) { if (nums null || nums.length k || k 0) { // 当数组元素少于 k 个或者窗口大小 k 本身非法时必须阻断 return 0; } // 核心滑窗逻辑... }如果没有nums.length k的入口防御后续滑动窗口在初始化前k个元素时就会直接触发ArrayIndexOutOfBoundsException。检查二大数越界与隐式类型溢出Overflow Underflow计算机中 32 位有符号整数int的表示范围是 $[-2^{31}, 2^{31} - 1]$即 $[-2147483648, 2147483647]$。在涉及累加、乘法、反转、哈希计算的题目中溢出往往以极隐蔽的方式破坏计算结果。1. 二分查找中的中点计算这是最经典也最容易被面试官拿来当面设套的细节// 隐患写法当 left 和 right 都很大接近 2^31-1时相加直接上溢变为负数导致 mid 变为负索引 int mid (left right) / 2; // 防御写法 1减法防溢出 int mid left (right - left) / 2; // 防御写法 2无符号右移位运算优先级低于加法务必加括号 int mid (left right) 1;2. 累加/累乘过程中的隐式类型转换时机在大数取模类题目中如模 $10^9 7$很多同学会写出如下代码int MOD 1_000_000_007; int a 1_000_000_000; int b 1_000_000_000; // 致命错误a * b 在赋值给 long 之前就已经在 int 寄存器中溢出截断了 long wrongAns (a * b) % MOD; // 正确做法运算操作数必须先提升至 64 位宽 long long correctAns ((long) a * b) % MOD;3. 负数取模陷阱在哈希表映射、环形循环数组移动时负数取模是逻辑重灾区。例如在 Java 中-1 % 5的运算结果是-1如果直接拿着这个结果去访问数组arr[-1]会立即越界崩掉。防御性数学处理应当保证模数落在 $[0, m-1]$ 内// 通用防负取模公式 int index (val % k k) % k;4. 逆向反转整数的提前截断以经典的整数反转LeetCode 7或字符串转整数LeetCode 8为例必须在将新数位推入结果之前进行边界探测而不是等推入溢出后再来修补。// 每次推入 digit 前探测溢出边界 if (rev Integer.MAX_VALUE / 10 || (rev Integer.MAX_VALUE / 10 digit 7)) { return 0; // 上溢 } if (rev Integer.MIN_VALUE / 10 || (rev Integer.MIN_VALUE / 10 digit -8)) { return 0; // 下溢 } rev rev * 10 digit;检查三指针越界与短路求值顺序Index Out of Bounds写双指针、滑动窗口、单调栈以及二分搜索时指针越界是占比最高的逻辑 Bug。1. 逻辑与表达式的依赖顺序短路求值Short-circuit Evaluation是保障指针安全的核心武器。永远把边界合法性判断写在取值操作的最左侧。// 致命错误当 r nums.length 时先计算 nums[r] 会直接抛出数组越界异常 while (nums[r] target r nums.length) { r; } // 防御写法必须先卡控索引范围利用 的短路特性阻断越界寻址 while (r nums.length nums[r] target) { r; }2. 链表步长跨跳的级联防御在快慢指针寻找链表中点或判定环时快指针每次走两步fast fast.next.next。// 必须同时保证当前节点与其后继节点都不为空 while (fast ! null fast.next ! null) { slow slow.next; fast fast.next.next; }如果只校验fast ! null那么当链表长度为奇数、快指针跳到尾节点时fast.next为 null下一次循环执行fast.next.next就会对 null 解引用导致空指针异常。3. 虚拟头节点Dummy Head / Sentinel消除边界歧义在链表插入、删除、合并操作中头节点可能被变更或删除。为防止在头节点处单独写冗长且容易漏判的if (head null) / if (cur head)分支引入哨兵节点是极其优雅的工程解法ListNode dummy new ListNode(0); dummy.next head; ListNode cur dummy; // 所有的头节点增删操作统一退化为中间节点操作彻底杜绝针对 head 的越界与空指针检查四递归栈溢出与循环收敛性判定Loop Termination死循环与无限递归StackOverflowError会直接耗尽线程执行资源。手撕代码时必须能清晰证明自己的代码会在有限步内收敛。1. 递归基Base Case的封闭性写树或图的 DFS/递归时务必检查两点是否存在没有覆盖到的叶子状态。是否存在有向图/无向图成环导致递归深陷。对于包含环路的图遍历必须有状态记录表如boolean[] visited或着色标记法。如果缺少访问标记递归深度直接击穿调用栈。2. 循环内部指针步进的漏网之鱼在双指针去重时如三数之和while (left right) { int sum nums[i] nums[left] nums[right]; if (sum target) { res.add(Arrays.asList(nums[i], nums[left], nums[right])); // 跳过重复元素 while (left right nums[left] nums[left 1]) left; while (left right nums[right] nums[right - 1]) right--; // 关键点跳过重复之后必须推进指针跳出当前值 // 如果这里漏写了 left 和 right--循环将无限卡死在最后一个重复值上 left; right--; } else if (sum target) { left; } else { right--; } }3. 二分搜索区间的收敛模型二分搜索死循环的核心在于区间没有严格缩小导致mid重复计算。牢记两种典型闭环心智模型left mid 1,right mid - 1匹配while (left right)每次都排除mid绝对收敛。left mid,right mid - 1必须配合向上取整mid left (right - left 1) / 2。若采用默认向下取整在right - left 1时mid会永远等于left导致left mid陷入无限循环。检查五状态污染与回溯深浅拷贝State Mutation很多同学算法写得很溜但在处理组合、全排列、路径输出等题目时最终输出的全是空列表或者所有解都一模一样。根源在于对对象引用传递和状态回溯的理解偏差。1. 结果收集时的浅拷贝幽灵在 Java/Python 等语言中对象传参均为引用语义。// 错误写法把 path 的引用直接加到结果集里 // 后续回溯清理 path 时res 内部已收集的对象内容也会被同步清空 res.add(path); // 正确做法必须构建当前状态的快照深拷贝/创建新实例 res.add(new ArrayList(path));2. 回溯状态还原的对称性回溯的核心逻辑在于“做出选择”与“撤销选择”的严格对称。graph TD A[做出选择: path.add(val)] -- B[深入下一层递归: dfs(...)] B -- C[撤销选择: path.remove(path.size() - 1)]如果在深入递归之前修改了外部状态如visited[i] true必须确保在递归返回后、退出当前逻辑块前将visited[i] false还原。如果中间存在提前return语句必须检查是否在return之前丢失了状态回滚。3. 入参的原地修改约束In-place Trap除非面试官明确要求“O(1) 额外空间复杂度下原地修改输入数组”否则尽量不要在核心解题过程中直接污染调用方传入的入参。在工程体系中上游传进来的对象可能同时被其他线程读取随意修改入参内部结构容易诱发并发脏读与诡异的副作用。如果必须修改原数组可以在动手前主动与面试官沟通确认。面试现场自测推演心法DRY RUN写完代码后如何向面试官展示你的严谨度不要立刻说“我写完了”而是主动提出“代码框架已经完成我现在用几组典型测试用例做一下推演和防御性验证”测试用例类型输入特征重点检查的目标机制空值与极端小规模nums null或nums []入口条件拦截与异常防御单一元素与边界规模nums [1]单节点链表指针跨跳与循环退出条件全重复元素nums [2, 2, 2, 2]避免单调栈死循环、双指针卡死数学极值nums [Integer.MAX_VALUE, -1]累加与乘法是否溢出、中点计算防溢出逆序/无解场景目标值不存在或者严格降序输入递归与循环退出的基准分支覆盖率在白板上用手指或笔尖带着面试官过一遍变量指针的位移变化顺便指明你的空值保护逻辑与防溢出设计。当你能够主动将这些防御措施讲出来时面试官看到的就不仅是一个做对算法题的学生而是一个在生产线上值得信赖的准后端工程师。