9 디버깅

9 디버깅 (Debugging)

코드에 문제가 생겼을 때, R은 실행을 특정 지점에서 멈추고 그 시점의 계산 상태를 들여다볼 수 있는 도구들을 몇 가지 제공해요. 이 장에서는 그 도구들을 하나씩 살펴볼게요.

출처: R 공식 매뉴얼

본문

디버깅은 언제나 한편으로는 "예술"에 가까운 일이었어요. 그런데 R은 코드 속에서 문제를 찾는 데 도움을 주는 도구를 몇 가지 갖추고 있어요. 이 도구들은 코드의 특정 지점에서 실행을 멈추게 하고, 그 순간의 계산 상태를 살펴볼 수 있게 해주죠.

대부분의 디버깅은 browserdebug를 호출하는 방식으로 이뤄져요. 이 두 함수는 내부적으로 같은 메커니즘을 사용하고, 둘 다 사용자에게 특별한 프롬프트를 띄워줘요. 그 프롬프트에 어떤 명령이든 입력할 수 있는데, 명령이 평가되는 평가 환경(environment)은 바로 그 시점에 활성화된 환경입니다. 덕분에 지금 어떤 변수의 값이든 그대로 들여다볼 수 있어요.

다섯 가지 특별한 명령은 R이 다르게 해석하는데, 다음과 같아요.

  • RET (엔터) — 함수를 디버깅 중이라면 다음 문장으로 넘어가요. browser로 불린 상황이라면 실행을 계속해요.
  • c / cont — 실행을 계속해요.
  • n — 함수 안에서 다음 문장을 실행해요. browser 프롬프트에서도 동작해요.
  • where — 호출 스택(call stack)을 보여줘요.
  • Q — 실행을 멈추고 곧바로 최상위 수준(top-level)로 뛰어나와요.

만약 위 특별 명령과 같은 이름을 가진 지역 변수가 있다면, 그 값을 get으로 접근할 수 있어요. 이름을 따옴표로 감싸서 get을 호출하면 현재 환경에서 그 값을 가져오죠.

디버거는 인터프리터로 해석되는 표현식에만 접근할 수 있어요. 만약 함수가 C 같은 외부 언어를 호출한다면, 그 언어 안의 문장에는 접근할 수 없어요. 실행은 R로 평가되는 다음 문장에서 멈추게 되죠. 컴파일된 코드를 디버깅하려면 gdb 같은 심볼릭 디버거를 쓰면 돼요.

9.1 browser

browser 함수를 호출하면 그 지점에서 R의 실행이 멈추고, 사용자에게 특별한 프롬프트가 제공돼요. browser에 전달하는 인자(argument)는 무시됩니다.

> foo <- function(s) {
+ c <- 3
+ browser()
+ }
> foo(4)
Called from: foo(4)
Browse[1]> s
[1] 4
Browse[1]> get("c")
[1] 3
Browse[1]>

9.2 debug / undebug

debug(fun)이라고 하면 어떤 함수든 디버거로 실행할 수 있어요. 이후 그 함수가 평가될 때마다 디버거가 호출되는데, 함수 본문의 문장들을 하나씩 제어하며 볼 수 있게 되죠. 각 문장이 실행되기 전에 그 문장이 출력되고 특별한 프롬프트가 제공돼요. 어떤 명령이든 입력할 수 있고, 위 표에 있는 명령들은 특별한 의미를 갖습니다.

디버깅을 끄려면 함수를 인자로 해서 undebug를 호출하면 돼요.

> debug(mean.default)
> mean(1:10)
debugging in: mean.default(1:10)
debug: {
    if (na.rm)
        x <- x[!is.na(x)]
    trim <- trim[1]
    n <- length(c(x, recursive = TRUE))
    if (trim > 0) {
        if (trim >= 0.5)
            return(median(x, na.rm = FALSE))
        lo <- floor(n * trim) + 1
        hi <- n + 1 - lo
        x <- sort(x, partial = unique(c(lo, hi)))[lo:hi]
        n <- hi - lo + 1
    }
    sum(x)/n
}
Browse[1]>
debug: if (na.rm) x <- x[!is.na(x)]
Browse[1]>
debug: trim <- trim[1]
Browse[1]>
debug: n <- length(c(x, recursive = TRUE))
Browse[1]> c
exiting from: mean.default(1:10)
[1] 5.5

9.3 trace / untrace

R의 동작을 지켜보는 또 다른 방법은 trace 메커니즘이에요. trace에 추적하고 싶은 함수 이름 하나를 인자로 넣어서 호출하죠. 이름을 따옴표로 감싸지 않아도 되지만, 어떤 함수는 문법 오류를 피하려고 이름을 따옴표로 감싸야 할 때도 있어요.

함수에 trace를 걸어두면 그 함수가 평가될 때마다 해당 호출이 출력돼요. 이 메커니즘을 끄려면 함수를 인자로 해서 untrace를 호출하면 됩니다.

> trace("[<-")
> x <- 1:10
> x[3] <- 4
trace: "[<-"(*tmp*, 3, value = 4)

9.4 traceback

오류 때문에 최상위 수준으로 뛰어나오면, base 환경에 .Traceback이라는 특별한 변수가 놓여요. .Traceback은 오류가 발생한 시점에 활성 상태였던 각 함수 호출마다 하나의 항목을 갖는 문자 벡터(character vector)입니다. .Traceback의 내용은 traceback을 호출해서 살펴볼 수 있어요.

더 알아보기

  • 이 장에서 다룬 browser, debug/undebug, trace/untrace, traceback은 모두 R 실행 환경에서 제공되는 기본 디버깅 도구예요. 각 함수의 자세한 사용법은 R의 도움말(?browser, ?debug, ?trace, ?traceback)을 참고하면 돼요.
  • 외부 언어로 작성된 컴파일 코드를 디버깅할 때는 gdb 같은 심볼릭 디버거가 필요하다는 점도 기억해두세요.