iBetter Books
수정

반응형 디버깅

Shiny 앱을 만들다 보면 "왜 이게 업데이트가 안 되지?", "왜 이게 너무 자주 실행되지?" 같은 상황을 마주칩니다. 반응형 시스템은 눈에 보이지 않기 때문에 일반 R 코드보다 디버깅이 까다롭습니다. 이번 챕터에서는 Shiny 앱을 효과적으로 디버깅하는 방법을 알아봅니다.

print()와 cat() — 가장 빠른 방법

가장 간단한 디버깅은 콘솔에 값을 출력하는 것입니다.

server <- function(input, output, session) {

  filtered <- reactive({
    cat("[filtered] 실행됨. dept:", input$dept, "\n")  # 언제 실행되는지 확인
    result <- students |> filter(dept == input$dept)
    cat("[filtered] 결과 행 수:", nrow(result), "\n")
    result
  })

  output$table <- renderTable({
    cat("[renderTable] 실행됨.\n")
    filtered()
  })

}

앱을 실행하고 위젯을 조작하면 RStudio 콘솔에서 각 반응식이 언제 실행되는지 확인할 수 있습니다.

print()는 콘솔에 출력하지만 반환값에 영향을 줄 수 있으므로 중간 확인에는 cat()을 주로 사용합니다.

message() 활용

cat() 대신 message()를 사용하면 표준 에러(stderr)로 출력되어 일반 출력과 구분됩니다.

filtered <- reactive({
  message("filtered 실행 — input$dept: ", input$dept)
  students |> filter(dept == input$dept)
})

browser() — 중단점으로 상태 검사

browser()는 코드 실행을 해당 지점에서 멈추고 R 콘솔에서 직접 변수를 확인할 수 있게 해줍니다.

server <- function(input, output, session) {

  filtered <- reactive({
    browser()  # 이 지점에서 실행이 멈춥니다
    students |> filter(dept == input$dept)
  })

}

앱을 실행하고 filtered()가 처음 호출될 때 RStudio 디버거가 활성화됩니다. 이 상태에서 input$dept를 입력하면 현재 값을 확인할 수 있고, n을 입력하면 한 줄씩 실행할 수 있습니다.

디버깅이 끝나면 browser() 줄을 지웁니다.

reactlog — 의존성 그래프 시각화

reactlog 패키지는 Shiny의 반응형 의존성 그래프를 시각적으로 보여줍니다. 어떤 반응식이 어떤 것에 의존하는지, 언제 실행됐는지 한눈에 파악할 수 있습니다.

먼저 패키지를 설치합니다.

install.packages("reactlog")

사용 방법은 두 단계입니다.

library(shiny)
library(reactlog)

# 1단계: 로그 기록을 활성화합니다
reactlog_enable()

# 2단계: 앱을 실행합니다
shinyApp(ui, server)

앱을 실행하고 위젯을 조작한 후 앱을 종료합니다. 그 다음 아래 명령어로 그래프를 엽니다.

shiny::reactlogShow()

브라우저에서 인터랙티브 의존성 그래프가 열립니다.

실행 중인 앱에서 바로 확인하고 싶다면 키보드 단축키를 사용합니다.

  • macOS: Cmd + F3
  • Windows: Ctrl + F3

reactlog 그래프 읽는 법

reactlog 화면에서 볼 수 있는 주요 노드 유형입니다.

색상/모양 의미
주황색 타원 반응형 소스 (input$*)
연두색 사각형 반응형 표현식 (reactive())
파란색 사각형 반응형 출력 (render*(), observe())
화살표 의존성 방향

그래프에서 각 노드를 클릭하면 그 반응식이 실행된 횟수, 최근 실행 시각, 의존하는 노드 목록을 확인할 수 있습니다.

흔한 실수와 해결법

실수 1. 반응형 값을 반응형 컨텍스트 밖에서 읽는다

# 잘못된 코드 — 오류 발생
server <- function(input, output, session) {
  current_dept <- input$dept  # 오류: 반응형 컨텍스트 밖

  output$text <- renderText({
    paste("학과:", current_dept)
  })
}

# 올바른 코드
server <- function(input, output, session) {
  output$text <- renderText({
    paste("학과:", input$dept)  # render*() 안에서 읽습니다
  })
}

input$*는 반드시 reactive(), render*(), observe() 같은 반응형 컨텍스트 안에서 읽어야 합니다.

실수 2. reactiveVal()을 괄호 없이 읽는다

my_val <- reactiveVal(0)

# 잘못된 코드
output$text <- renderText({
  my_val  # 반응식 객체를 반환합니다. 값이 아닙니다.
})

# 올바른 코드
output$text <- renderText({
  my_val()  # 괄호를 붙여야 값을 읽습니다.
})

실수 3. reactive() 결과를 괄호 없이 사용한다

filtered <- reactive({ students |> filter(dept == input$dept) })

# 잘못된 코드
output$table <- renderTable({
  filtered  # 반응식 객체를 반환합니다.
})

# 올바른 코드
output$table <- renderTable({
  filtered()  # 괄호를 붙여야 계산 결과를 반환합니다.
})

실수 4. observe()에서 반환값을 기대한다

# 잘못된 코드 — observe()는 값을 반환하지 않습니다
result <- observe({
  students |> filter(dept == input$dept)
})

# 올바른 코드 — 값이 필요하면 reactive()를 사용합니다
result <- reactive({
  students |> filter(dept == input$dept)
})

실수 5. 순환 의존성

# A가 B에 의존하고, B가 A에 의존하면 무한 루프가 발생합니다
a <- reactive({ b() + 1 })
b <- reactive({ a() + 1 })

오류 메시지를 보면 "maximum reactive dependency depth reached" 같은 내용이 나옵니다. reactlog로 의존성 그래프를 확인하면 순환 관계를 시각적으로 찾을 수 있습니다.

req() — 조건이 충족될 때만 실행하기

req()는 주어진 조건이 충족되지 않으면 반응식 실행을 조용히 멈춥니다. 오류를 일으키지 않고 "아직 준비가 안 됐다"는 것을 Shiny에 알립니다.

server <- function(input, output, session) {

  output$table <- renderTable({
    req(input$dept)          # dept가 NULL이 아닐 때만 실행합니다
    req(nchar(input$dept) > 0)  # 빈 문자열이 아닐 때만 실행합니다

    students |> filter(dept == input$dept)
  })

}

앱이 처음 로드될 때 아직 사용자가 아무것도 선택하지 않은 상태에서 출력을 안전하게 처리할 때 특히 유용합니다.

validate()와 need() — 사용자에게 오류 메시지 표시

req()가 조용히 멈추는 것과 달리, validate()는 사용자에게 친절한 오류 메시지를 표시합니다.

server <- function(input, output, session) {

  output$table <- renderTable({
    validate(
      need(input$dept != "", "학과를 선택해주세요."),
      need(input$min_score >= 0, "점수는 0 이상이어야 합니다.")
    )

    students |>
      filter(dept == input$dept, score >= input$min_score)
  })

}

조건이 충족되지 않으면 출력 영역에 지정한 메시지가 표시됩니다. 데이터가 없거나 조건이 잘못됐을 때 사용자에게 안내하는 데 적합합니다.

이 챕터에서 배운 디버깅 도구들을 활용하면 반응형 시스템의 동작을 투명하게 파악할 수 있습니다. 특히 reactlog는 Shiny 앱이 복잡해질수록 진가를 발휘합니다. 다음 파트에서는 Shiny의 다양한 입력과 출력 위젯을 본격적으로 다룹니다.