元数据卡
- 前置知识:第2章终端基础,第5章包管理器
- 预计时间:10 分钟
- 完成标志:能从栈调用中找出出错位置
场景:程序崩溃了
你的程序刚才还在好好跑着。你加了一行代码,编译通过,信心满满地运行了。
然后屏幕上炸出了一片红色——几十行英文字母,夹杂着数字和奇怪的符号,从屏幕底部一路滚到顶部,像一个滚下悬崖的石头上刻满了咒语。
你完全懵了。"Exception"、"Thread"、"NullPointer"、"at"——这些字你都认识,但它们组合在一起却毫无意义。
你转头看了看窗外——一切显得那么陌生。
"这堆红色的是什么?"你问工坊主人。他走过来,看了一眼屏幕,说:"这是程序给你留的遗言。学会读它,你就知道它死在哪里、怎么死的。"
第一招:读懂栈调用(Stack Trace)
"先别慌。"工坊主人手指点在屏幕最上方。"从上面开始读——不是从下面。"
"从上面?"你困惑地看着那坨红色。
他指着第一行:Exception in thread "main" java.lang.NullPointerException。"这行告诉你两件事:出错的线程叫 main,错误的类型是 NullPointerException。NullPointer 的意思是你用了一个不存在的东西。"
错误信息里的每一行都是一个脚印:
Exception in thread "main" ← 什么线程出错
java.lang.NullPointerException ← 什么类型的错误
at com.example.Main.processData(Main.java:12) ← 哪行出的错(文件:行号)
at com.example.Main.main(Main.java:6) ← 谁调了它(调用链)读法:从下往上读调用链,从上往下找错误类型。
- 先看异常类型:
NullPointerException→ 某个对象是 null 但你调了它的方法 - 看第一行具体位置:
Main.java:12→ 打开文件看第 12 行 - 看调用链回溯:是谁在第 6 行调了
processData
假设你的代码是:
// 运行方式: javac Main.java && java Main
public class Main {
public static void main(String[] args) {
String input = null;
processData(input); // 第6行:调了 processData
}
public static void processData(String data) {
int length = data.length(); // 第12行:data 是 null!
System.out.println(length);
}
}第 12 行 data.length()——data 是 null,调用 .length() 就崩了。解决方式:确保 data 不为 null,或者调用前检查。
给不同语言的读者
Python 的栈调用长这样:
# 运行方式: python3 main.py
def process_data(data):
length = len(data) # 出错位置
process_data(None)
# Traceback (most recent call last):
# File "main.py" line 6 in <module>
# process_data(None)
# File "main.py" line 3 in process_data
# length = len(data)
# TypeError: object of type 'NoneType' has no len()JavaScript(Node.js)的栈调用长这样:
// 运行方式: node main.js
function processData(data) {
console.log(data.length); // 出错位置
}
processData(null);
// TypeError: Cannot read properties of null (reading 'length')
// at processData (/app/main.js:7:18)
// at Object.<anonymous> (/app/main.js:3:1)格式不同,但结构一样:错误类型 → 位置 → 调用链。不管你在什么语言里,读栈调用的方法是一样的。
避坑:别跟第三方库的栈调用死磕
栈调用里通常有一半以上的行是第三方库的代码(比如 javax.servlet、org.springframework)。你的线索只在你自己的代码里——找到 at com.yourpackage.xxx 那一行。把库的栈帧当路径背景,别进去找 bug。
本讲小结
- 栈调用是程序死前的"遗言",记录它从哪里来、死在哪
- 从上往下看异常类型,从下往上看调用链
- 找到自己代码(
at com.yourpackage.xxx)的那一行开始排查
→ 下一步:日志基础
你学会了读遗言,但程序不只在终端里崩——它在没人盯着的时候崩了怎么办?你需要一个"黑匣子":日志。
→ 第6章:日志基础