← 返回面试备战库
1 调试思维模型
什么时候用 Debug,什么时候用日志?
🧪 用 IDE Debug
- 能本地复现的问题
- 需要看变量实时变化
- 需要逐行跟踪执行路径
- 逻辑复杂的算法/业务代码
📝 用日志
- 生产环境无法 Debug
- 偶发问题,不方便打断点
- 需要长时间收集数据
- 多节点分布式问题
调试的核心思路:二分法定位
不要从头到尾逐行看。先在代码的"中间位置"打断点,判断问题在上半段还是下半段,然后继续二分。
🔴 发现 Bug
→
在中间打断点
→
上半段?下半段?
→
继续二分
→
🎯 精确定位
💡 类比:字典查单词。你不会从第一页翻到最后一页,而是翻到中间,判断目标在左边还是右边。调试也一样。
2 启动 Debug 的 3 种方式
方式一:点击右上角 Debug 按钮
OrderApplication — IntelliJ IDEA
OrderApplication
▼
▶ Run
🐛 Debug
main class: com.example.OrderApplication
↓ 点击绿色虫子按钮即可启动 Debug 模式
🐛 右上角的 Debug 按钮(虫子图标)是最常用的启动方式。确保先选择了正确的 Run Configuration。
方式二:右键 → Debug
OrderService.java — IntelliJ IDEA
1public class OrderApplication {
2 public static void main(String[] args) {
3 SpringApplication.run(OrderApplication.class, args);
4 }
5}
Copy Path
Copy Reference
🐛 Debug 'OrderApplication.main()'
▶ Run 'OrderApplication.main()'
Debug with Coverage
右键代码编辑区,选择 "Debug 'XxxApplication.main()'" 即可启动。
方式三:快捷键
| 系统 | 快捷键 | 说明 |
| Mac | ⌃ + D | Control + D 启动 Debug |
| Windows / Linux | Shift + F9 | 启动 Debug |
💡 建议:养成用快捷键的习惯。每次省下 2 秒,一天 Debug 20 次就省了 40 秒,一年下来... 你懂的。
3 六种断点 — 每种都带图
3.1 行断点(最常用)
点击行号旁的 gutter 区域,设置一个红色圆点。程序运行到这一行时会暂停。
OrderService.java — OrderService
OrderService.java
Order.java
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 total = total.add(order.getPrice());
5 }
6 return total;
7}
🔴 点击行号左边的灰色 gutter 区域,出现红色圆点 = 断点已设置。再次点击可取消。
💡 断点设置成功后,该行会显示红色圆点。再次点击可取消断点。也可通过 Ctrl+Shift+F8 打开断点管理窗口批量操作。
3.2 条件断点
右键点击已有的红色断点,在弹出对话框中输入条件表达式。只有满足条件时程序才会暂停。
OrderService.java — OrderService
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 total = total.add(order.getPrice());
5 }
6 return total;
7}
⚡ Conditional Breakpoint
Condition expression:
order.getPrice() > 100
☑ Suspend when true
🟠 橙色圆点 = 条件断点。只有当 order.getPrice() > 100 时才会暂停,其余循环正常执行。
💡 条件断点是调试循环的利器!5000 条数据中只关注特定的一条?设个条件就行,不用手动 F8 几千次。
3.3 日志断点(不暂停)
设置断点后取消 "Suspend",勾选 "Log evaluated expression",程序不会暂停,只在 Console 打印。
OrderService.java — OrderService
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 total = total.add(order.getPrice());
5 }
6 return total;
7}
Console
order 1 = 29.90
order 2 = 15.50
order 3 = 88.00
order 4 = 120.00
order 5 = 45.60
🟡 黄色圆点(带 = 号)= 日志断点。不暂停执行,只在 Console 打印。适合观察循环中的数据变化。
💡 日志断点的妙用:不想打断程序执行流程,但想观察某些值?用日志断点!比加 System.out.println 干净多了。
3.4 方法断点
在方法签名行设置断点。程序会在方法入口和出口都停下来。
OrderService.java
1// OrderService.java
2public class OrderService {
3 public BigDecimal calculateTotal(List<Order> orders) {
4 // ...
5 }
Debug panel → 停在方法入口
Method enter: calculateTotal(orders=ArrayList{5})
Method exit: calculateTotal → return BigDecimal("298.50")
🔷 在方法签名行打断点,会在方法入口和出口都停下来。适合看入参和返回值。
💡 方法断点特别适合调试接口/抽象类:你可以在接口方法上打断点,自动定位到实际实现类。
3.5 字段断点
在字段声明行设置断点,监视字段被谁修改。默认只在修改时暂停。
Order.java
1public class Order {
2 private String id;
3 private String name;
4 private BigDecimal price;
5 private BigDecimal total = BigDecimal.ZERO; 👁
6}
Field Watchpoint Settings
☑ Field access ☑ Field modification
👁 字段断点(Field Watchpoint)监视字段被谁修改了。右键断点可同时勾选 "access" 和 "modification"。
💡 发现某个值莫名其妙被改了?在字段上打个断点,立刻抓到是哪行代码改的!
3.6 异常断点(无需在代码里打断点!)
设置后,只要任何地方抛了指定异常,程序自动停在异常抛出位置。
Breakpoints — IntelliJ IDEA
Java Exception Breakpoints
java.lang.ArithmeticException
java.lang.NullPointerException
java.lang.ArrayIndexOutOfBoundsException
java.lang.ClassCastException
java.lang.IllegalArgumentException
Java Line Breakpoints
OrderService.calculateTotal : 4
NullPointerException Settings
☑ Caught exceptions
☑ Uncaught exceptions
☐ Class filters
当任何代码抛出 NullPointerException 时,调试器自动暂停在抛出位置。
🚨 不需要在代码里打断点!只要任何地方抛了 NullPointerException,程序自动停在那里。排查 NPE 的最强工具!
💡 设置路径:Run → View Breakpoints → 点击 + 号 → Java Exception Breakpoint → 选择异常类型。
4 Debug 面板 9 个按钮全解
这是调试的核心操作区。每个按钮都对应一个关键动作。先看全局图,再逐个详解。
Debug — OrderApplication
🐛 Debug
|
OrderApplication
[5005]
⏹ Stop
🔄 Rerun
⚙ Settings
ResumeStepOverStepInForceInStepOutDropFrRunToViewBPMute
📋 Variables
ordersArrayListsize = 5
totalBigDecimal"29.90"
orderOrder{网页设计}
📚 Frames (Call Stack)
calculateTotal OrderService.java:4
processOrders OrderController.java:23
handle DispatcherServlet.java:156
逐按钮详解
▶ 1. Resume Program — F9
继续运行程序,直到遇到下一个断点才暂停。如果后面没有断点了,程序会一直运行到结束。
🚦 类比:红灯变绿灯。程序在断点处停下来(红灯),按 F9 = 绿灯放行,程序继续跑到下一个红灯。
💡 配合条件断点使用效果最佳:设置条件后 Resume,调试器会自动跳过不满足条件的循环,只停在目标位置。
⏭ 2. Step Over — F8
执行当前行代码,然后移到下一行。如果当前行调用了方法,不会进入方法内部,而是直接执行完整个方法再停。
🐴 类比:走马观花。经过一个房间(方法)只看门口不进去,直接走到下一个房间。
💡 最常用的按钮!80% 的调试场景只需要 F8(Step Over)就够了。不确定要不要进方法?先用 F8。
⬇ 3. Step Into — F7
进入当前行调用的方法内部。可以进入自己写的代码方法,但不会进入 JDK/第三方库的方法。
🔬 类比:钻进去看。打开房间的门,进去仔细检查每一个角落。
💡 如果你想进入 JDK 源码(如 BigDecimal.add),需要用 Force Step Into (Alt+Shift+F7)。
⬇⬇ 4. Force Step Into — Alt+Shift+F7
强制进入任何方法,包括 JDK 源码、第三方库代码。用于深入分析底层行为。
🚪 类比:破门而入。连写着"禁止入内"的门也能进(JDK源码)。
⬆ 5. Step Out — Shift+F8
从当前方法中走出来。执行完当前方法的剩余代码,回到调用该方法的上层代码处。
🚶 类比:我撤了。检查完了,退出房间回到上一层。
💡 经常和 Step Into 配合使用:F7 进去看了几行发现不相关,Shift+F8 出来继续调试。
⏪ 6. Drop Frame
回到上一个方法调用帧。相当于"时光倒流",可以重新执行某个方法。
⏰ 类比:时光倒流!走到一半发现错过了什么,回到上一个路口重来。
⚠ 注意:Drop Frame 不是万能的 undo。已经发生的 I/O 操作(数据库写入、网络请求)不会回滚。
⬇🏃 7. Run to Cursor — Alt+F9
直接运行到光标所在位置。相当于在光标处临时设了个断点,到达后自动删除。
🏃 类比:跳着看。不想一步步走,直接跳到前方某个检查点。
💡 比 Resume 更灵活!不需要提前设置断点,直接把光标放到想停的位置即可。
🔴 8. View Breakpoints — Ctrl+Shift+F8
打开断点管理窗口,可以查看所有断点、批量启用/禁用、编辑条件。
💡 断点太多管理不过来?用这个功能一览无余。
🔇 9. Mute Breakpoints
临时禁用所有断点。程序会正常运行,不会在任何断点停下。再次点击恢复。
🔇 类比:静音模式。不想删断点,但暂时想让程序跑完不断。用完再取消静音。
5 Step 调试全流程 — 手把手 8 步
以下通过一个真实的 Bug 排查场景,展示完整的调试流程。代码如下:
// OrderService.java — 有 Bug 的代码
public BigDecimal calculateTotal(List<Order> orders) {
BigDecimal total = BigDecimal.ZERO;
for (Order order : orders) {
BigDecimal price = order.getPrice();
BigDecimal quantity = new BigDecimal(order.getQuantity());
BigDecimal subtotal = price.multiply(quantity);
total.add(subtotal); // ← Bug 在这里!
}
return total; // 返回 0.00 !
}
🐛 Bug:BigDecimal 是不可变对象,add() 返回新对象,不修改原对象。需要 total = total.add(subtotal)。
Step 1在入口打断点,启动 Debug
OrderService.java — Debugging
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 BigDecimal price = order.getPrice();
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal);
8 }
9 return total;
10}
程序停在第 1 行方法入口。Variables 面板可以看到入参 orders = ArrayList(size=5)。当前行还没执行。
Step 2按 F8(Step Over),执行到下一行
OrderService.java — Debugging
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 BigDecimal price = order.getPrice();
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal);
8 }
9 return total;
F8 执行了第 1 行(方法签名),现在停在第 2 行。⚠ 重点:高亮行(第 2 行)还没执行,所以变量面板里 还没有 total——这一行是"即将执行"。再按一次 F8 执行完第 2 行后,total 才会被创建并赋值为 "0.00"。
Step 3继续 F8,进入循环体
OrderService.java — Debugging
1public BigDecimal calculateTotal(List<Order> orders) {
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 BigDecimal price = order.getPrice();
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal);
8 }
F8 两次后进入循环。order 变量出现:第一笔订单「网页设计」price=29.90, quantity=2。当前停在第 4 行。
Step 4F8 执行 price 赋值
OrderService.java — Debugging
2 BigDecimal total = BigDecimal.ZERO;
3 for (Order order : orders) {
4 BigDecimal price = order.getPrice();
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal);
8 }
price = 29.90 出现了。注意 total 还是 "0.00"。继续往下看...
Step 5按 F7(Step Into),钻进 multiply 方法
BigDecimal.java (JDK Source) — Debugging
OrderService.java
BigDecimal.java
1454// java.math.BigDecimal
1455public BigDecimal multiply(BigDecimal val) {
1456 return new BigDecimal(inflate(), val.inflate(), ...);
1457}
F7 跳进了 BigDecimal.multiply() 的 JDK 源码!Call Stack 清楚显示调用链:multiply ← calculateTotal ← processOrders。
Step 6按 Shift+F8(Step Out),回到上层
OrderService.java — Debugging
OrderService.java
BigDecimal.java
4 BigDecimal price = order.getPrice();
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal);
8 }
9 return total;
Shift+F8 退出 multiply,回到 OrderService。subtotal = 59.80(29.90 × 2)。注意 total 还是 "0.00"!
Step 7右键变量 → Evaluate Expression,发现异常!
Evaluate Expression — OrderService
⚡ Evaluate Expression
Alt + F8
Expression:
total.add(subtotal)
Result:
BigDecimal → "59.80"
⚠ 但是!查看 Variables 面板:
total = "0.00" ← 还是 0!!
🔍 发现问题了!total.add(subtotal) 返回了 "59.80",但 total 本身还是 "0.00"——BigDecimal.add() 返回新对象,不修改原对象!
Step 8找到 Bug!修复代码
OrderService.java — Fixed
5 BigDecimal quantity = new BigDecimal(order.getQuantity());
6 BigDecimal subtotal = price.multiply(quantity);
7 total.add(subtotal); ← Bug
7 total = total.add(subtotal); ← 修复!
8 }
✅ 修复:total = total.add(subtotal)。BigDecimal 是不可变对象,必须用返回值接收!整个调试过程不到 3 分钟。
💡 总结:通过 F8 逐步执行 → 观察 Variables 变化 → F7 钻进可疑方法 → Evaluate 验证猜想 → 定位 Bug。这就是标准调试流程。
6 Evaluate Expression 详解
Evaluate Expression — IntelliJ IDEA
Expression
order.getPrice()
total.add(subtotal)
orders.stream().count()
Result
BigDecimal "29.90"
BigDecimal "59.80"
long 5
Evaluate
Code Fragment Mode ▾
什么时候用 Evaluate Expression?
| 场景 | 示例表达式 |
| 测试表达式结果 | total.add(subtotal) |
| 调用方法看返回值 | order.getQuantity() * order.getPrice() |
| 深入查看对象 | orders.get(0).toString() |
| 验证集合大小 | orders.stream().filter(o -> o.getPrice() > 100).count() |
| 执行工具方法 | JSON.toJSONString(order) |
两种模式
Expression 模式
输入单个表达式,立即看到结果。最常用。
Code Fragment 模式
可以写多行代码,包括局部变量、if/for 等。适合复杂逻辑验证。
💡 快捷键 Alt+F8 可以在不暂停程序的情况下打开 Evaluate 窗口(前提是已经停在某个断点)。
7 条件断点实战 — 从 5000 条数据中找一条
场景
列表有 5000 条订单数据,只有 id=3999 的那条计算结果异常。怎么快速定位?
Step 1在循环体打断点
OrderService.java
1for (Order order : orders) { // 5000 条数据
2 BigDecimal price = order.getPrice();
3 BigDecimal subtotal = price.multiply(quantity);
4 total.add(subtotal);
5}
Step 2右键断点 → 输入条件
Conditional Breakpoint
⚡ Conditional Breakpoint
Condition:
order.getId() == 3999
☑ Suspend when true
☐ Log message to console
☐ Log evaluated expression
输入条件 order.getId() == 3999。只有当循环到第 3999 条时才暂停。
Step 3按 F9(Resume)→ 自动跳过 3998 次
OrderService.java — Stopped at condition
1for (Order order : orders) {
2 BigDecimal price = order.getPrice();
3 BigDecimal subtotal = price.multiply(quantity);
4 total.add(subtotal);
5}
🎯 直接跳到了 id=3999 的数据!看 Variables 面板:quantity = "-1"!这就是 Bug 原因——数量为负数。
💡 条件断点让你在 5000 次循环中精准定位到目标,不用手动 F8 几千次。
日志断点示例
不想暂停,只想看每条数据的计算结果:
Console Output
id=1, price=29.90, qty=2, subtotal=59.80
id=2, price=15.50, qty=1, subtotal=15.50
id=3, price=88.00, qty=3, subtotal=264.00
...
id=3999, price=35.00, qty=-1, subtotal=-35.00 ← !!!
...
不暂停程序,在 Console 里看到所有数据。一目了然,第 3999 条的 quantity=-1 是问题所在。
8 五个真实场景
场景 1:NullPointerException 定位
最常见的异常。不需要猜,用异常断点一步到位。
UserService.java — NPE stopped here
12public UserVO toVO(User user) {
13 UserVO vo = new UserVO();
14 vo.setCity(user.getAddress().getCity()); ← NPE!
15 vo.setName(user.getName());
16 return vo;
17}
设置 NullPointerException 异常断点后,程序自动停在第 14 行。Variables 面板清楚地显示 user.address = null。是 getAddress() 返回了 null。
场景 2:接口 500 — Spring Boot 三层排查
从前端看到 500 错误,到后端 Controller → Service → Mapper 逐层排查。
OrderController → OrderService → OrderMapper
① Controller 层
23@PostMapping("/api/orders")
24public Result createOrder(@RequestBody OrderDTO dto) {
入参: dto = {userId=1001, items=[{skuId=5, qty=3}]}
② Service 层
45public Order processOrder(OrderDTO dto) {
46 BigDecimal amount = calculateAmount(dto.getItems());
dto.items = [{skuId=5, qty=3, price=29.90}]
③ Mapper 层
12@Insert("INSERT INTO orders...")
13int insert(Order order);
order.id = null ← 没有设置主键!INSERT 失败
在三层分别打断点,用 F9 逐层跳过,观察每一层的数据传递。最终在 Mapper 层发现 order.id 未设置。
场景 3:死循环排查
程序卡住了?很可能是死循环。用条件断点快速突破。
DataProcessor.java
5int i = 0;
6while (i < 100) {
7 process(data[i]); // 条件: i > 100
8 // 忘了 i++ !!!
9}
设条件断点 i > 100。如果循环正常应该在 i=100 时退出。但 i=101 了还在跑 → 说明 i 没有递增!看第 8 行:确实没有 i++。
场景 4:并发问题排查
多线程下数据不一致。使用 Debug 的 Threads 面板切换线程。
Debug — Threads
🧵 Threads
▶
Thread-1
RUNNING
at OrderService.processOrder(OrderService.java:23)
⏸
Thread-2
PAUSED at breakpoint
at OrderService.processOrder(OrderService.java:23)
⏸
main
WAITING
📋 Thread-1 Variables
counterint2
📋 Thread-2 Variables
counterint2
两个线程的 counter 都是 2!说明 Thread-2 读到了 Thread-1 还没写完的中间值。这是典型的 race condition。
💡 并发调试技巧:在 Threads 面板点击不同线程,查看各自的变量状态。可以手动控制线程执行顺序来复现竞态条件。
场景 5:远程调试
测试环境/预发环境出了问题,本地复现不了?用 Remote Debug 连上去!
Run/Debug Configurations — IntelliJ IDEA
+ → Remote JVM Debug
Name:
Remote-Test-Server
Host:
192.168.1.100
Port:
5005
JVM Arguments (copy to remote server):
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
1远程服务器启动命令加 JVM 参数:
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar
2本地 IntelliJ → Run → Edit Configurations → + → Remote JVM Debug
3填入远程 IP 和端口 → 点击 Debug 连接
⚠ 远程调试注意事项:只适用于测试/预发环境,严禁在生产环境使用!调试时该请求线程会暂停,影响其他用户。
9 VS Code 调试指南
9.1 launch.json 配置
launch.json — VS Code
1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "type": "node", // ← 运行类型
6 "request": "launch", // ← launch 或 attach
7 "name": "Debug Server", // ← 显示名称
8 "program": "${workspaceFolder}/src/index.js",
9 "args": ["--dev"], // ← 命令行参数
10 "env": { "NODE_ENV": "development" }
11 }
12 ]
13}
launch.json 是 VS Code 的调试配置文件。放在 .vscode/ 目录下。最关键的字段:type、program、args。
9.2 VS Code Debug Toolbar
VS Code 的调试工具栏是居中悬浮的,和 IntelliJ 位置不同但功能类似。
VS Code — Debug Mode
ContinueStepOverStepIntoStepOutRestartStop
| 按钮 | 图标 | 快捷键 | 功能 |
| Continue | ▶ | F5 | 继续运行到下一个断点 |
| Step Over | ⏭ | F10 | 执行当前行,不进方法 |
| Step Into | ⬇ | F11 | 进入方法内部 |
| Step Out | ⬆ | Shift+F11 | 从当前方法退出 |
| Restart | 🔄 | — | 重新启动调试 |
| Stop | ⏹ | Shift+F5 | 终止调试 |
9.3 Node.js 调试示例
index.js — VS Code Debugging
1const express = require('express');
2const app = express();
3
4app.get('/api/orders', (req, res) => {
5 const userId = req.query.userId;
6 const orders = getOrdersByUser(userId);
7 res.json({ data: orders });
8});
💡 VS Code 调试 Node.js 非常方便。直接在代码里点 gutter 设断点,按 F5 启动调试即可。
9.4 Chrome 前端调试
launch.json — Chrome Debug
1{
2 "type": "chrome",
3 "request": "launch",
4 "name": "Debug Frontend",
5 "url": "http://localhost:3000",
6 "webRoot": "${workspaceFolder}/src",
7 "sourceMaps": true
8}
VS Code 会自动打开 Chrome 浏览器并连接。在 VS Code 中打的断点会直接命中,前端和后端都能在同一 IDE 中调试。
10 快捷键速查表
IntelliJ IDEA Debug 快捷键
| 功能 | Mac | Windows / Linux |
| 启动 Debug | ⌃+D | Shift+F9 |
| Resume(继续) | ⌘+⌥+R / F9 | F9 |
| Step Over(步过) | F8 | F8 |
| Step Into(步入) | F7 | F7 |
| Force Step Into | ⌥+⇧+F7 | Alt+Shift+F7 |
| Step Out(步出) | ⇧+F8 | Shift+F8 |
| Run to Cursor | ⌥+F9 | Alt+F9 |
| Evaluate Expression | ⌥+F8 | Alt+F8 |
| View Breakpoints | ⌘+⇧+F8 | Ctrl+Shift+F8 |
| 条件断点(右键断点) | ⌘+⇧+F8 | 右键断点 → Condition |
| Mute Breakpoints | ⌥+⌘+M (click) | 点击工具栏🔇按钮 |
| Stop Debug | ⌘+F2 | Ctrl+F2 |
VS Code Debug 快捷键
| 功能 | Mac | Windows / Linux |
| 启动 Debug | F5 | F5 |
| Continue(继续) | F5 | F5 |
| Step Over | F10 | F10 |
| Step Into | F11 | F11 |
| Step Out | ⇧+F11 | Shift+F11 |
| Stop Debug | ⇧+F5 | Shift+F5 |
| Restart Debug | ⇧+⌘+F5 | Ctrl+Shift+F5 |
| 切换断点 | F9 | F9 |
| Evaluate | Debug Console 面板 | Debug Console 面板 |
高频使用 Top 5(必须记住)
| # | 操作 | IntelliJ | VS Code | 使用频率 |
| 1 | Step Over | F8 | F10 | ⭐⭐⭐⭐⭐ |
| 2 | Resume | F9 | F5 | ⭐⭐⭐⭐⭐ |
| 3 | Step Into | F7 | F11 | ⭐⭐⭐⭐ |
| 4 | Evaluate | Alt+F8 | Console | ⭐⭐⭐⭐ |
| 5 | Step Out | Shift+F8 | Shift+F11 | ⭐⭐⭐ |