실무 개발자가 IDE를 선택하는 이유
텍스트 에디터와 터미널에서 javac와 java 명령어로 코드를 컴파일하고 실행하는 방식은 언어의 기초 동작 원리를 이해하는 데 큰 도움이 됩니다. 하지만 프로젝트 규모가 커져 수십 개 이상의 클래스가 서로 관계를 맺기 시작하면 파일 간 이동, 오탈자 즉시 감지, 의존성 라이브러리 연동, 복잡한 런타임 상태 확인을 수작업으로 감당하기 어렵습니다.
IntelliJ IDEA(인텔리제이 아이디어)는 현대 Java 생태계에서 가장 널리 쓰이는 통합 개발 환경(IDE)입니다. Java 25 LTS의 최신 언어 스펙을 온전히 인식하여 문법 오류를 입력 즉시 붉은 밑줄로 경고하고, 객체 타입에 맞는 메서드 목록을 문맥에 따라 지능적으로 추천합니다. 무엇보다 프로그램이 멈추지 않고 지나쳐버리는 런타임 메모리 상태를 한 줄씩 멈춰 서서 들여다볼 수 있는 디버거(Debugger)는 개발자의 버그 수정 시간을 획기적으로 줄여줍니다.
프로젝트 생성과 Java 25 SDK 연동
IntelliJ IDEA를 처음 실행한 뒤 New Project 창을 열면 프로젝트의 기본 속성을 설정하게 됩니다. 이때 가장 중요한 점은 컴파일과 실행을 담당할 JDK(Java Development Kit) 버전을 명확히 지정하는 것입니다.
- Name과 Location 설정: 프로젝트 이름(예:
VoxelAdventure)과 디렉터리 경로를 지정합니다. - Language 및 Build System 선택: Language는
Java, Build system은 초기 학습 단계에서는IntelliJ를, 이후 다룰 빌드 자동화 단계에서는Gradle을 선택합니다. - JDK 선택: 시스템에 이미 설치된
Java 25 LTS를 선택하거나, 드롭다운 메뉴의 Download JDK 항목을 눌러 Eclipse Temurin 또는 Oracle OpenJDK 25 버전을 직접 내려받습니다. - 프로젝트 구조 확인: 좌측 Project 도구 창(단축키
Alt + 1또는Cmd + 1)을 열면 소스 코드가 위치하는src디렉터리가 보입니다. 이 폴더 안에 패키지와 자바 소스 파일(.java)을 생성합니다.
새 프로젝트가 준비되면 src 우클릭 후 New → Java Class를 선택해 클래스를 생성할 수 있습니다. IntelliJ는 파일명과 public class 이름이 일치해야 하는 자바 규칙을 자동으로 반영해 줍니다.
생산성을 2배로 높이는 필수 단축키와 템플릿
숙련된 개발자는 마우스에 손을 올리는 횟수를 최소화하고 키보드 단축키로 코드를 읽고 편집합니다. IntelliJ에서 가장 자주 쓰이는 단축키 체계를 익혀두면 코드 작성 속도가 비약적으로 향상됩니다.
| 작업 | Windows / Linux | macOS | 실무 활용 상황 |
|---|---|---|---|
| 전체 파일·클래스 검색 | Shift + Shift |
Shift + Shift |
클래스나 설정 파일 이름을 즉시 찾아 열기 |
| 빠른 수정 및 제안 | Alt + Enter |
Option + Enter |
에러 수정, import 추가, 문법 개선 자동 반영 |
| 코드 자동 서식 정리 | Ctrl + Alt + L |
Cmd + Option + L |
들여쓰기, 줄바꿈, 연산자 공백 표준화 |
| 선언부 이동 및 사용처 추적 | Ctrl + B 또는 클릭 |
Cmd + B 또는 클릭 |
메서드 정의부나 호출 위치로 단번에 점프 |
| 이름 일괄 변경 (Refactor) | Shift + F6 |
Shift + F6 |
변수·클래스 이름을 참조처까지 안전하게 수정 |
| 실행 및 디버그 | Shift + F10 / Shift + F9 |
Ctrl + R / Ctrl + D |
현재 파일 즉시 실행 또는 디버그 모드 진입 |
또한 psvm 또는 main을 입력한 뒤 Tab 키를 누르면 public static void main(String[] args) 선언이 자동으로 완성되며, sout을 입력하면 System.out.println() 구문이 바로 채워지는 라이브 템플릿(Live Templates) 기능도 적극 활용할 수 있습니다.
버그를 추적하는 디버거의 기본 메커니즘
프로그램이 예기치 않게 종료되거나 계산 결과가 기댓값과 다를 때, 초보자는 종종 의심되는 위치마다 System.out.println()을 여러 줄 추가해 변수를 찍어보곤 합니다. 그러나 이 방식은 출력문을 추가하고 컴파일하는 반복 노동을 유발하며, 객체 내부 컬렉션이나 다단계 호출 흐름을 한눈에 파악하기 어렵습니다.
디버거를 사용하면 실행 중인 JVM(Java Virtual Machine) 프로세스를 특정 소스 코드 라인에서 일시 중지시키고, 그 순간의 스택 프레임(Stack Frame)과 힙(Heap) 메모리에 할당된 객체 인스턴스의 실제 필드 값을 실시간으로 검사할 수 있습니다.
실습을 위해 voxel 모험 게임에서 플레이어가 몬스터와 차례로 조우하며 장비 내구도와 체력을 소모하는 코드를 준비했습니다. 아래 코드는 겉보기에 정상 컴파일되지만, 루프 내부의 상태 변화를 면밀히 추적해 볼 필요가 있습니다.
import java.util.ArrayList;
import java.util.List;
public class Main {
record VoxelMonster(String name, int attackPower, int expReward) {}
static class PlayerState {
private final String name;
private int health;
private int armorDurability;
private int totalExp;
public PlayerState(String name, int health, int armorDurability) {
this.name = name;
this.health = health;
this.armorDurability = armorDurability;
this.totalExp = 0;
}
public void takeDamage(int rawDamage) {
int absorbed = Math.min(this.armorDurability, rawDamage / 2);
int finalDamage = Math.max(1, rawDamage - absorbed);
this.armorDurability -= absorbed;
this.health = Math.max(0, this.health - finalDamage);
}
public void gainExp(int exp) {
this.totalExp += exp;
}
public boolean isAlive() {
return this.health > 0;
}
@Override
public String toString() {
return String.format("Player[%s, HP=%d, Armor=%d, EXP=%d]",
this.name, this.health, this.armorDurability, this.totalExp);
}
}
public static void main(String[] args) {
PlayerState player = new PlayerState("Steve", 100, 30);
List<VoxelMonster> encounters = List.of(
new VoxelMonster("Zombie", 16, 25),
new VoxelMonster("Skeleton", 24, 40),
new VoxelMonster("Creeper", 50, 80),
new VoxelMonster("Enderman", 35, 60)
);
System.out.println("=== 모험 시작 ===");
System.out.println("초기 상태: " + player);
List<String> battleLogs = new ArrayList<>();
for (int i = 0; i < encounters.size(); i++) {
VoxelMonster monster = encounters.get(i);
player.takeDamage(monster.attackPower());
player.gainExp(monster.expReward());
String log = String.format("Round %d vs %s -> 남은 HP: %d, 방어구: %d",
i + 1, monster.name(), player.health, player.armorDurability);
battleLogs.add(log);
if (!player.isAlive()) {
System.out.println("전투 중 쓰러졌습니다: " + monster.name());
break;
}
}
System.out.println("=== 모험 종료 ===");
System.out.println("최종 상태: " + player);
System.out.println("전투 기록 수: " + battleLogs.size());
}
}
예상 출력
=== 모험 시작 ===
초기 상태: Player[Steve, HP=100, Armor=30, EXP=0]
=== 모험 종료 ===
최종 상태: Player[Steve, HP=25, Armor=6, EXP=205]
전투 기록 수: 4
중단점(Breakpoint) 설정과 단계별 실행 기법
디버거를 가동하려면 먼저 실행을 일시 정지할 위치를 지정해야 합니다. 코드 에디터 좌측의 줄 번호(Line Number) 옆 빈 영역(Gutter)을 클릭하면 빨간색 원형 아이콘이 나타나며, 이것이 바로 **중단점(Breakpoint)**입니다.
중단점을 설정한 후 상단 도구 모음의 벌레 모양 아이콘을 누르거나 Shift + F9(macOS Ctrl + D)를 누르면 프로그램이 일반 실행 대신 디버그 모드로 구동됩니다. 실행 흐름이 중단점 줄에 도달하는 즉시 JVM은 일시 중지되며, IntelliJ 하단에 Debug 도구 창이 자동으로 열립니다.
디버그 도구 창에서 가장 핵심적인 제어 버튼은 다음 네 가지입니다.
- Step Over (
F8): 현재 멈춰 선 소스 라인을 통째로 실행하고 바로 다음 줄로 이동합니다. 현재 줄에 다른 메서드 호출이 있어도 그 메서드 내부로 들어가지 않고 결과만 받아옵니다. - Step Into (
F7): 현재 줄에서 호출되는 사용자 정의 메서드 내부로 실행 포인터를 이동시켜 메서드 첫 줄부터 한 단계씩 검사합니다. - Step Out (
Shift + F8):Step Into로 들어간 메서드의 나머지 줄을 한 번에 실행하고, 자신을 호출했던 상위 메서드로 즉시 복귀합니다. - Resume Program (
F9): 다음 중단점을 만날 때까지 일시 정지를 풀고 프로그램을 계속 실행합니다. 더 이상 중단점이 없다면 정상 종료될 때까지 달립니다.
변수 검사와 조건부 중단점(Conditional Breakpoint)
디버그 창의 Variables 탭에는 현재 활성화된 스택 프레임의 모든 로컬 변수와 this 객체의 상태가 트리 구조로 표시됩니다. 컬렉션 객체를 펼치면 size와 각 인덱스 요소의 필드까지 실시간으로 확인할 수 있습니다.
변수 값 즉석 평가 (Evaluate Expression)
복잡한 계산식이나 메서드 반환값을 멈춰 있는 상태에서 미리 계산해 보고 싶다면 Alt + F8(macOS Option + F8)을 눌러 Evaluate Expression 창을 띄웁니다. 여기에 player.health > 50이나 encounters.get(2).attackPower() * 1.5 같은 자바 표현식을 입력하면 현재 런타임 메모리 상태를 토대로 즉각적인 평가 결과를 보여줍니다.
조건부 중단점(Conditional Breakpoint) 활용
만약 루프가 수천 번 도는 데이터 처리 중 특정 조건에서만 멈추고 싶다면 빨간색 중단점 아이콘을 우클릭합니다. 나타나는 팝업 메뉴의 Condition 입력란에 자바 불리언 식을 지정할 수 있습니다.
예를 들어 위 예제의 player.takeDamage(monster.attackPower()); 줄에 중단점을 걸고 조건식으로 다음을 입력할 수 있습니다.
monster.name().equals("Creeper")
이렇게 설정해 두면 Zombie나 Skeleton과 만날 때는 중단점이 멈추지 않고 지나가며, 오직 Creeper를 조우해 루프가 실행되는 순간에만 디버거가 자동으로 일시 중지됩니다. 무의미한 F8 연타를 피할 수 있어 대규모 배열이나 컬렉션을 다룰 때 필수적인 기법입니다.
리팩토링 기능으로 코드 품질 가꾸기
IntelliJ IDEA의 강력함은 단순한 오탈자 검출에 그치지 않고, 복잡하고 지저분해진 코드를 안전하게 구조화하는 리팩토링(Refactoring) 엔진에 있습니다.
- Extract Method (
Ctrl + Alt + M/Cmd + Option + M): 길어진 메서드에서 일부 로직 블록을 블록 지정한 뒤 단축키를 누르면, 매개변수와 반환 타입을 분석해 독립된 새 메서드로 자동 분리해 줍니다. - Introduce Variable (
Ctrl + Alt + V/Cmd + Option + V): 복잡하게 얽힌 계산식의 결과를 알기 쉬운 이름의 지역 변수로 뽑아내 가독성을 높입니다. - Rename (
Shift + F6): 변수나 클래스 이름을 바꿀 때 단순 문자열 치환이 아닌 구문 트리를 기반으로 해당 심볼을 참조하는 모든 파일의 위치를 한 번에 안전하게 갱신합니다.
직접 해보기
아래 실습 코드는 플레이어의 장비 강화와 몬스터 처치 로직을 담고 있습니다. 코드를 직접 입력하고 중단점과 디버거를 설정해 동작을 분석해 보세요.
public class Main {
record Equipment(String partName, int level, int defenseBonus) {}
public static int calculateTotalDefense(Equipment[] gear) {
int sum = 0;
for (Equipment eq : gear) {
int effectiveBonus = eq.defenseBonus() + (eq.level() * 2);
sum += effectiveBonus;
}
return sum;
}
public static void main(String[] args) {
Equipment[] inventory = new Equipment[] {
new Equipment("투구", 2, 5),
new Equipment("갑옷", 3, 12),
new Equipment("부츠", 1, 3)
};
int totalDefense = calculateTotalDefense(inventory);
System.out.println("장착 장비 총 방어력: " + totalDefense);
}
}
미션 과제
calculateTotalDefense메서드의sum += effectiveBonus;줄에 중단점을 배치하세요.- 디버그 모드(
Shift + F9)로 실행한 뒤 Variables 창에서 각 장비(eq)의partName,level,defenseBonus, 그리고 계산된effectiveBonus값이 회차마다 어떻게 변하는지F8(Step Over)을 누르며 확인하세요. - 중단점 조건(Condition)에
eq.partName().equals("갑옷")을 입력한 뒤 재실행하여 오직 갑옷을 계산할 때만 실행이 일시 정지되는지 검증하세요. Shift + F6을 사용해effectiveBonus변수명을calculatedDefense로 변경해 보고, 참조 위치가 모두 안전하게 자동 갱신되는지 확인하세요.