跳到内容

元数据卡

  • 前置知识:第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) ← 谁调了它(调用链)

读法:从下往上读调用链,从上往下找错误类型。

  1. 先看异常类型:NullPointerException → 某个对象是 null 但你调了它的方法
  2. 看第一行具体位置:Main.java:12 → 打开文件看第 12 行
  3. 看调用链回溯:是谁在第 6 行调了 processData

假设你的代码是:

java
// 运行方式: 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 的栈调用长这样:

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)的栈调用长这样:

javascript
// 运行方式: 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.servletorg.springframework)。你的线索只在你自己的代码里——找到 at com.yourpackage.xxx 那一行。把库的栈帧当路径背景,别进去找 bug。

本讲小结

  • 栈调用是程序死前的"遗言",记录它从哪里来、死在哪
  • 从上往下看异常类型,从下往上看调用链
  • 找到自己代码(at com.yourpackage.xxx)的那一行开始排查

下一步:日志基础

你学会了读遗言,但程序不只在终端里崩——它在没人盯着的时候崩了怎么办?你需要一个"黑匣子":日志。

第6章:日志基础

Built with VitePress | Software Systems Atlas