디버깅 (Debugging)

Author

Mike K Smith

Published

March 9, 2023

코드의 에러를 찾고 수정하는 다양한 디버깅 전략을 배웁니다.

디버깅 전략

  1. 껐다가 다시 켜기: 환경(Environment)을 깨끗이 비우고 다시 시작하기
  2. 환경 이해하기: 전역(Global) 환경과 지역(Local) 환경 구분하기
  3. 도구 활용: debugonce(), browser(), RStudio 중단점(Breakpoint) 사용하기
  4. 추적: traceback(), options(error = recover) 활용하기

참고 비디오: R 디버깅 기초

1. 깨끗한 환경 유지하기

R 세션을 오래 사용하다 보면 수많은 객체가 쌓이게 됩니다. 이전에 만든 객체가 현재 작업에 의도치 않은 영향을 줄 수 있으므로, 주기적으로 환경을 정리하고 R을 재시작하는 것이 좋습니다.

  • 빗자루 아이콘: Environment 탭의 빗자루 아이콘을 클릭하면 현재 세션의 모든 객체가 삭제됩니다.
  • 재현성 확보: 스크립트를 처음부터 끝까지 실행했을 때 오류가 없어야 합니다. RStudio 설정에서 세션 종료 시 작업 공간을 저장하지 않도록 설정(Save workspace to .RData on exit: Never)하는 것을 권장합니다.

2. R 다시 시작하기 (Restart R)

환경을 비우는 것만으로 해결되지 않는 설정(로딩된 패키지, 옵션 등)이 있습니다. 이럴 때는 R 자체를 다시 시작해야 합니다.

  • 방법: RStudio 메뉴의 Session -> Restart R 또는 단축키 Ctrl+Shift+F10.
  • 특히 새로운 패키지를 설치하기 전이나 후에 세션을 초기화하는 습관을 들이는 것이 좋습니다.

3. 실행 환경의 이해

함수를 실행하면 R은 실행 환경 (Execution Environment)이라는 별도의 공간을 만듭니다. 함수 내부에서 생성된 객체는 함수 종료와 함께 사라지며, 전역 환경(Global Environment)에 영향을 주지 않습니다.

myFunction <- function(x) {
  a <- 3
  x + a
}

myFunction(2) # 결과: 5
[1] 5
# 함수 밖에서 'a'를 출력하면 에러가 발생하거나 전역 변수 'a'가 출력됩니다.
  • 함수 내부에서 전역 변수를 직접 참조하는 것은 좋지 않은 습관입니다. 필요한 값은 인수를 통해 전달하세요.

4. debugonce() 활용하기

함수 내부에서 어떤 일이 일어나는지 한 줄씩 확인하고 싶을 때 사용합니다.

debugonce(myFunction)
myFunction(2)

이 코드를 실행하면 RStudio는 디버그 모드로 진입합니다. - Next (n): 다음 줄 실행 - Continue (c): 남은 코드 모두 실행 - Stop (Q): 디버그 모드 종료

Environment 탭이 현재 함수의 실행 환경으로 바뀌어 내부 변수값들을 실시간으로 확인할 수 있습니다.

5. browser()와 중단점(Breakpoint)

  • browser(): 코드 중간에 browser() 함수를 삽입하면 실행 중에 해당 지점에서 멈추고 디버그 모드로 들어갑니다.
  • 중단점: 스크립트 에디터의 행 번호 왼쪽을 클릭하여 빨간 점(중단점)을 찍을 수 있습니다. 코드를 Source 실행할 때 해당 지점에서 멈춥니다.

6. 에러 추적: traceback()recover()

에러가 발생했을 때 어디서부터 잘못되었는지 역추적합니다.

  • traceback(): 에러 직후 호출하면 어떤 함수 호출 과정을 거쳐 에러가 났는지 보여줍니다.
  • options(error = recover): 에러 발생 시 즉시 멈추고, 어느 단계의 환경을 조사할지 선택할 수 있게 해줍니다. (조사 후에는 options(error = NULL)로 되돌리세요.)

챌린지

아래 adsl_counts 함수에는 에러가 포함되어 있습니다. 위에서 배운 디버깅 도구들을 사용하여 어디서 에러가 발생하는지 찾아보세요.

adsl_counts <- function(dataFile) {
  adsl_saf <- dataFile %>% filter(SAFFL == "Y")
  
  Big_N_cnt <- adsl_saf %>%
    group_by(TRT01AN, TRT01A) %>%
    summarise(N = n(), .groups = 'drop')

  small_n_cnt <- adsl_saf %>%
    group_by(TRT01AN, TRT01A, SEX) %>%
    summarise(name = "n", .groups = 'drop') # 여기에 문제가 있을까요?

  adsl_mrg_cnt <- small_n_cnt %>%
    left_join(Big_N_cnt, by = c("TRT01AN", "TRT01A")) %>%
    mutate(perc = round((n/N)*100, 1)) # 'n' 변수가 존재하나요?
  
  return(adsl_mrg_cnt)
}

# 힌트: debugonce(adsl_counts)를 실행한 뒤 함수를 호출해 보세요.