IntelliJ 디버거와 Hot Reload
IntelliJ IDEA의 Debug 모드로 runClient를 실행하여 breakpoint를 활용한 디버깅 방법과 Hot Reload(코드 변경 즉시 반영)의 사용법을 익힙니다.
IntelliJ 디버거와 Hot Reload
모드 개발에서 가장 많은 시간을 잡아먹는 건 "왜 이 코드가 실행되지 않지?"라는 의문입니다. LOGGER.info()를 잔뜩 심어두고 게임을 재시작하는 방식은 금방 한계에 부딪힙니다. IntelliJ의 디버거를 쓰면 코드 실행 흐름을 한 줄씩 따라가며 변수 값을 실시간으로 확인할 수 있습니다. 여기에 Hot Reload까지 더하면 간단한 수정은 게임 재시작 없이 바로 반영됩니다.
1단계: Debug 모드로 runClient 실행
일반 실행(runClient 더블클릭)과 Debug 실행의 차이는 JVM이 디버거 에이전트를 붙이고 시작하느냐입니다. Debug 모드에서는 breakpoint에서 실행이 멈추고, 변수 값을 검사하거나 코드를 한 줄씩 실행할 수 있습니다.
IntelliJ 우측 Gradle 패널을 엽니다.
examplemod → Tasks → neoforge → runClient
runClient를 우클릭하면 컨텍스트 메뉴가 나타납니다. 여기서 Debug 'examplemod [runClient]' 를 선택합니다.
또는, 상단의 재생 버튼 오른쪽의 벌래 모양 아이콘을 선택하여 실행 할 수 있습니다.
하단에 Debug 탭이 열리고 Gradle이 클라이언트를 빌드합니다. 일반 실행보다 약간 느리게 시작되는 건 정상입니다. JVM이 디버거 소켓을 열고 IntelliJ와 연결을 맺기 때문입니다.
2단계: Breakpoint 추가
Breakpoint는 "이 줄에서 실행을 멈춰라"는 표시입니다. 코드 에디터 좌측 거터(gutter) 영역을 클릭하면 빨간 원이 생깁니다.
단축키 Ctrl+F8을 눌러서 Breakpoint를 지정/해제 가능 합니다.
ExampleMod.java를 열고 생성자 첫 줄에 breakpoint를 추가합니다.
// ExampleMod.java — 68번째 줄 근처
public ExampleMod(IEventBus modEventBus, ModContainer modContainer) {
// ← 이 줄 거터를 클릭
modEventBus.addListener(this::commonSetup);거터에 빨간 원이 보이면 breakpoint가 활성화된 것입니다. 이미 Debug 모드로 게임이 실행 중이라면 다음 번 해당 코드가 실행될 때 멈춥니다. 아직 실행 전이라면 1단계로 돌아가 Debug 모드로 시작하세요.
3단계: Breakpoint Hit — Variables 패널 확인
게임이 시작되면서 ExampleMod 생성자가 호출되는 순간, IntelliJ가 실행을 멈추고 포커스를 가져옵니다. 에디터에서 현재 실행 중인 줄이 파란색으로 강조됩니다.
하단 Debug 탭에서 Variables 패널을 확인합니다.
modEventBus와 modContainer 파라미터가 보입니다. 각 변수 옆 ▶ 를 클릭하면 내부 필드까지 펼쳐볼 수 있습니다. 에디터에서 변수 위에 마우스를 올려도 팝업으로 값이 표시됩니다.
4단계: Stepping — 코드 한 줄씩 실행
실행이 멈춘 상태에서 다음 단축키로 코드를 탐색합니다.
| 단축키 | 동작 | 설명 |
|---|---|---|
F8 | Step Over | 현재 줄 실행 후 다음 줄로 이동. 메소드 호출은 내부로 들어가지 않음 |
F7 | Step Into | 메소드 호출 내부로 진입 |
Shift+F8 | Step Out | 현재 메소드를 끝까지 실행하고 호출한 곳으로 복귀 |
F9 | Resume | 다음 breakpoint까지 실행 재개 |
Ctrl+F2 | Stop | 디버그 세션 종료 |
F8을 몇 번 눌러 BLOCKS.register(modEventBus) 줄까지 이동해 보세요. Variables 패널에서 각 단계마다 상태가 바뀌는 걸 확인할 수 있습니다.
단축키를 바꾸고 싶다면 File > Settings > Keymap에서 변경 할 수 있습니다.
5단계: Resume — 게임 계속 실행
디버깅이 끝났으면 F9 또는 Debug 탭 상단의 Resume 버튼(초록 삼각형)을 눌러 게임을 계속 실행합니다. 다음 breakpoint가 없으면 게임이 정상적으로 시작됩니다.
Breakpoint를 일시적으로 비활성화하려면 빨간 원을 우클릭 후 Disable Breakpoint를 선택하거나, 거터를 다시 클릭해 제거합니다.
6단계: Hot Reload — 코드 변경 즉시 반영
게임이 실행 중인 상태에서 코드를 수정하고 Ctrl+F9(Build Project)를 누릅니다. IntelliJ가 변경된 클래스를 컴파일하고 "Reload Changed Classes" 다이얼로그를 표시합니다.
Reload Classes를 클릭하면 JVM이 변경된 클래스를 교체합니다. 게임을 재시작하지 않아도 수정 사항이 반영됩니다.
Hot Reload가 가장 유용한 상황은 메소드 본문 안의 로직을 수정할 때입니다. 예를 들어 commonSetup에서 출력하는 로그 메시지를 바꾸거나, 조건문을 수정하는 경우가 여기에 해당합니다.
⚠️ Hot Reload 한계
JVM HotSwap은 메소드 본문 변경만 지원합니다. 다음은 runClient 재시작이 필요합니다.
- 메소드 시그니처 변경 (인자 타입, 반환 타입, 메소드 이름)
- 새 메소드 또는 필드 추가
- 클래스 계층 구조 변경 (
extends,implements)- 어노테이션 변경 (
@Mod,@SubscribeEvent등)"현재 클래스는 HotSwap이 지원되지 않습니다" 메시지가 나타나면 Stop Debug (
Ctrl+F2) 후 Debug 모드로 재시작하세요.
자주 쓰는 디버깅 패턴
조건부 Breakpoint
특정 조건일 때만 멈추고 싶다면 breakpoint를 우클릭 후 Edit Breakpoint를 선택합니다. Condition 필드에 Java 표현식을 입력합니다.
// 예: modContainer의 modId가 "examplemod"일 때만 멈춤
modContainer.getModId().equals("examplemod")루프 안에서 특정 반복 횟수에만 멈추거나, 특정 값일 때만 검사하고 싶을 때 유용합니다.
Evaluate Expression
실행이 멈춘 상태에서 Alt+F8을 누르면 Evaluate Expression 창이 열립니다. 현재 스코프의 변수를 사용해 임의의 Java 표현식을 실행할 수 있습니다.
// 예: 현재 modEventBus의 클래스 이름 확인
modEventBus.getClass().getName()코드를 수정하지 않고도 "이 값이 뭔지" 바로 확인할 수 있어 매우 편리합니다.
Watch 표현식
Variables 패널 상단 + 버튼으로 Watch 표현식을 추가하면, 실행이 멈출 때마다 해당 표현식의 값을 자동으로 계산해 보여줍니다. 여러 단계를 거치며 특정 값의 변화를 추적할 때 씁니다.
정리
이 챕터에서 다룬 내용입니다.
- Debug 모드 진입:
runClient우클릭 → Debug - Breakpoint: 거터 클릭으로 추가, 조건부 breakpoint로 정밀 제어
- Stepping: F8/F7/Shift+F8으로 코드 흐름 추적
- Variables 패널: 실행 중 변수 값 실시간 확인
- Hot Reload:
Ctrl+F9→ Reload Classes (메소드 본문 변경만 지원)
다음 챕터에서는 NeoForge 이벤트 시스템의 기초를 다룹니다. 디버거를 켜두고 이벤트가 언제 발생하는지 직접 확인해 보세요.