자바에서 디버깅하기
자바에서 디버깅하기
코드를 처음부터 완벽하게 한 번에 통과시키는 사람은 없어요. 우리는 대부분 코드에서 오류를 찾아 고치는 일을 반복하는데, 이 과정을 **디버깅(debugging)**이라고 불러요. 이 장에서는 디버거가 뭔지, 왜 println이나 단위 테스트만으로 부족한지, 그리고 디버거의 핵심 기능들을 이해하는 데 집중해요.
출처: dev.java
본문
디버깅이란?
우리 모두 첫 시도에 완벽하게 동작하는 코드를 작성하죠? 농담이에요. 우리는 종종 코드에서 오류를 찾아 고쳐야 해요. 이 과정을 디버깅이라고 불러요.
왜 "디버깅"이라고 부르는지 궁금할 수 있는데요. 이 용어는 1940년대에 그레이스 호퍼(Grace Hopper) 제독이 컴퓨터 안에서 나방(moth) 한 마리를 발견하면서 유명해졌어요. (당시 컴퓨터는 거대했기 때문에 실제 곤충이 들어갈 수 있었어요.) 여러분이 코드에서 동물을 다룰 일은 없겠지만, 우리는 **버그(bug)**라고 부르는 오류와 문제를 다루게 될 거예요. 결함(defect)이라고 부르기도 하는데, 보통 그건 코드를 커밋한 뒤에 불리는 이름이에요. 이름이 무엇이든, 문제를 찾아내는 것 자체가 바로 디버깅이에요!
**디버거(debugger)**는 IDE(통합 개발 환경) 안에서 프로그램의 서로 다른 지점에 있는 다양한 변수의 값을 볼 수 있게 해 주는 도구예요. 강력한 돋보기라고 생각하면 돼요. Eclipse, IntelliJ, NetBeans, VS Code 사이에 세부 사항은 조금씩 다르지만 개념은 동일해요.
코드를 실행할 때 일반/실행(run) 모드로 시작할지, 디버그(debug) 모드로 시작할지를 고를 수 있어요. 이렇게 해서 디버깅하고 싶을 때를 스스로 결정할 수 있어요.
왜 println이 아닐까?
코딩을 처음 배울 때 우리는 흔히 이런 코드를 써서 상황을 확인하곤 해요.
IO.println(numCats);
println을 쓰는 것 자체에는 문제가 없어요 (커밋만 하지 않는다면요). 하지만 더 복잡한 프로그램에서는, 특히 추적해야 할 것이 여러 개이거나 반복문이 많다면 금세 감당하기 어려워져요. 애플리케이션에 로깅이 많을 때도 찾기 어려워지죠. (저는 별표와 제 이니셜로 완화하려 했지만, 그래도 println이 유용한 지점을 넘어설 수 있어요.)
IO.println("*** JB i=%s numCats=%s numDogs=%s".formatted(i, numCats, numDogs));
지금은 println()이 필요를 충족시킬지 몰라도, 언제까지나 그러지는 않아요. 디버거 사용법을 익히면 "println을 아는 게 전부라서 그냥 쓴다"는 문제를 피할 수 있어요.
왜 단위 테스트가 아닐까?
**단위 테스트(unit testing)**는 버튼 한 번에 코드의 작은 조각들을 테스트하는 코드를 작성하는 것이에요. 단위 테스트 자체에는 문제가 없어요. 단위 테스트는 훌륭해요. 기대되는 동작을 문서화하고, 예상치 못한 값이 반환되면 알려 주며, 코드의 동작을 이해하는 데 도움을 줘요. 때로는 코드에 무엇이 잘못됐는지에 대한 큰 단서까지 주기도 해요. 하지만 단위 테스트는 깨진 코드가 올바른 값을 반환하지 않을 때 그 내부에서 무슨 일이 일어나는지는 알려 주지 않아요. 그걸 보려면 단위 테스트와 함께 디버거를 써서 메서드 안쪽을 살펴봐야 해요. 그리고 코드를 고친 뒤에는 단위 테스트가 새 버그가 유입되는 걸 잡아내는 데 도움이 돼요!
@Test
void magic() {
assertEquals(42, target.magic(), "magic number incorrect");
}
브레이크포인트란?
제가 magic 메서드를 (엉터리로) 구현했다고 해 볼게요. 이 메서드는 6 곱하기 7을 해서 42를 얻어야 해요. 하지만 그렇게 동작하지 않아요. (별로) 조사해 보지 않은 채, 저는 당황해서 라인 5에서 part1과 part2의 값이 무엇인지 알고 싶어졌어요.
그게 바로 **브레이크포인트(breakpoint)**의 용도예요. 브레이크포인트는 프로그램을 그 지점에서 일시 정지시켜서 주변을 살펴보게 해 줘요. 어떻게 설정하냐고요? 코드 옆 왼쪽 바에서 더블클릭(또는 우클릭해서 브레이크포인트를 켜기로 선택)하면 돼요. 설정한 브레이크포인트를 가리키는 작은 원이 나타나요.
public class Answer {
public int magic() {
int part1 = 3 + 2; // BUG!
int part2 = 7;
return part1 * part2;
}
}
브레이크포인트가 효과를 내려면 프로그램을 디버그 모드로 실행하는 것이 중요해요. (디버그 모드로 실행하지 않으면 모든 브레이크포인트는 무시된다는 점을 기억하세요.) 디버거가 라인 5에서 멈추자, part2는 예상대로 7이었어요. 그런데 part1은 6이 아니라 5예요. 버그를 찾았어요! 브레이크포인트를 설정할 수 있게 해 준 디버거 덕분이에요.
디버거는 어떻게 쓸까?
디버거를 쓰고 싶은 이유는 여러 가지가 있어요. 가장 흔한 세 가지는 다음과 같아요.
- 깨진 코드 고치기 — 디버거는 코드가 실행되는 동안 변수의 값을 보여 주게 해 줘요. 이를 통해 어디서부터 기대한 대로 동작하지 않게 되는지 볼 수 있어요.
- 낯선 코드 이해하기 — 코드가 실행되는 동안 각 변수의 값을 지켜보면 코드를 더 잘 이해하는 데 도움이 돼요.
- 코드의 경로 추적하기 — 브레이크포인트에서 멈추면, 디버거는 그 지점에 도달하기 위해 호출된 클래스/메서드를 보여 줘요. 클릭하면 그 지점들에서 유효한 변수가 무엇인지도 볼 수 있어요.
디버거 기초
디버거가 첫 브레이크포인트에서 멈춘 뒤 실행 흐름을 제어하는 데 쓰는 기본 명령은 네 가지예요. 각 명령을 Flow 클래스를 예로 들어 설명할게요.
public class Flow {
public static void main(String args[]) {
IO.println(debugging());
}
public static int debugging() {
int num = investigate();
num++;
return num;
}
public static int investigate() {
int found = 5;
return found;
}
}
- Step into (들어가기) — 프로그램을 실행하되 메서드 호출의 첫 줄까지만 진행하라고 지시해요. 라인 7에 브레이크포인트가 있다고 할게요. 디버거에 "step into"를 지시하면 라인 13으로 이동해요.
- Step over (건너뛰기) — 프로그램을 실행하되 어떤 메서드에서도 멈추지 않도록 지시해요. 라인 7에 브레이크포인트가 있고 "step over"를 지시하면 디버거는 라인 8에 있게 돼요. 다시 "step over"를 고르면 라인 9로 이동해요.
- Step out / return (나가기) — 메서드의 끝까지 실행하고 호출자로 돌아가라고 지시해요. 라인 13에 브레이크포인트가 있고 "step out"(또는 "step return")을 고르면, 디버거는 메서드 호출의 결과와 함께 라인 7에 있게 돼요. (Step out과 step return은 같은 것이에요. IDE마다 이름이 다를 뿐이에요.)
- Resume (재개) — 프로그램이 다른 브레이크포인트를 만나거나 완료될 때까지 계속 실행하라고 지시해요.
고급 기법
디버거에는 고급 기법이 많이 있어요. 흔한 세 가지는 다음과 같아요.
- 조건부 브레이크포인트(conditional breakpoint) — 평소엔 디버거가 브레이크포인트를 요청한 곳에서 멈춰요. 그런데 반복문 안에 있거나 어떤 값이 문제를 일으키는지 단서가 있다면, 그건 원하지 않을 거예요. 조건부 브레이크포인트는 브레이크포인트에 자바 코드를 조금 추가해서, 그 조건이 참일 때만 멈추게 해 줘요. 이 방식은 관심 있는 값에 도달할 때까지 resume을 수없이 누르는 일을 피하게 해 줘요.
- 평가(Evaluation) — 브레이크포인트에 도달하면 자바 코드를 작성해 상황을 파악할 수 있어요. 예를 들어 사용 가능한 변수에 메서드를 호출해 볼 수 있죠.
- 데이터 변경(Changing data) — 디버거에서 변수의 값을 직접 바꾸고 코드가 계속 실행되게 할 수 있어요. 원래 값 대신 새로 업데이트된 값을 사용해요. 이렇게 해서 잠재적 수정의 영향을 탐색할 수 있어요.
문서화
이제 디버거 사용 개념을 알았으니, 여러분의 IDE에 대한 문서를 살펴볼 차례예요! 키보드 단축키와 각 버튼의 위치에 주의를 기울이세요.
- Eclipse
- IntelliJ
- NetBeans
- VS Code
더 알아보기 (Learn more)
디버깅은 IDE마다 버튼 위치와 단축키가 조금씩 달라서, 해당 IDE의 공식 문서에서 각 기능의 위치를 확인하는 게 가장 정확해요. 개념 자체는 이 장에서 설명한 네 가지 기본 명령과 고급 기법이 어느 IDE에서나 공통으로 적용되니, 익숙해지면 어느 환경에서든 문제없이 쓸 수 있어요.