Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby 프로그램 디버깅과 오류 수정 완벽 가이드

프로그램이 처음 실행부터 원하는 대로 정확히 작동한 적이 얼마나 자주 있었나요?

대부분의 경우 프로그램은 우리가 기대하는 대로 작동하지 않습니다. 그래서 문제의 원인을 찾기 위해 Ruby 디버깅이라는 기술이 필요합니다.

다음과 같은 에러 메시지를 본 적이 있으실 겁니다.

undefined method 'some_method' for nil:NilClass

이 메시지는 nil 값이 코드 어딘가에 스며들었다는 뜻입니다. 이 글에서 소개하는 기법들을 활용하면 이런 문제와 유사한 상황을 효과적으로 해결할 수 있습니다.

에러와 스택 트레이스(Stack Trace) 이해하기

Ruby 인터프리터에서 에러가 발생하거나 프로그램이 의도대로 동작하지 않는다면, 디버깅 모드로 전환할 시간입니다.

프로그램이 크래시되는 경우라면 에러 메시지를 주의 깊게 읽는 것이 무엇보다 중요합니다. 에러 메시지에는 보통 문제의 단서가 담겨 있습니다.

예제를 살펴보겠습니다:

def method1
  method2
end

def method2
  puts invalid_variable
end

method1

이 코드를 실행하면 다음과 같은 에러가 출력됩니다.

/tmp/stack.rb:6:in 'method2': undefined local variable or method 'invalid_variable' for main:Object (NameError)
    from /tmp/stack.rb:2:in 'method1'
    from /tmp/stack.rb:9:in '
'

이것이 바로 스택 트레이스(stack trace)입니다. 함께 분석해 보겠습니다.

스택 트레이스 읽는 법

분석은 항상 맨 윗줄부터 시작합니다. 이 줄은 실제로 에러가 발생한 지점을 가리키지만, 에러의 근본 원인이 여기서 시작되었다는 의미는 아닙니다. 그래도 조사를 시작하기에는 가장 좋은 출발점입니다.

각 부분의 의미는 다음과 같습니다:

텍스트 설명
/tmp/stack.rb:6 파일명과 줄 번호
in `method2` 메서드 이름
undefined local variable or method 'invalid_variable' 에러 메시지
main:Object 클래스 이름
(NameError) 예외(Exception) 이름

이렇게 하나씩 나눠서 보면 에러 메시지가 생각보다 무섭지 않다는 것을 알 수 있습니다. 참고로 Ruby 공식 문서에서 예외 클래스 목록을 확인할 수 있습니다.

중요한 점: 스택 트레이스의 첫 번째 줄 아래에 있는 각 줄은 코드가 어떤 경로로 여기까지 도달했는지를 알려줍니다. 즉, 메서드 호출 체인인 셈입니다. 아래로 계속 내려가다 보면 애플리케이션의 시작 지점(main)에 도달하게 됩니다.

스택 트레이스 처리 알고리즘

  1. 스택 트레이스의 맨 윗줄을 읽습니다.
  2. 해당 파일이 프로젝트에 속한다면, 표시된 줄 번호의 파일을 엽니다. 외부 파일이라면, 인식할 수 있는 파일이 나올 때까지 스택 트레이스를 아래로 계속 따라갑니다.
  3. 눈에 띄는 문제가 있는지 살펴보고 수정합니다(에러 메시지에 언급된 내용을 집중적으로 확인).
  4. 그래도 해결되지 않으면 영향을 받은 변수의 값 등 추가 정보를 확보해야 합니다.

Ruby 기본 디버깅: puts와 p 활용하기

가장 기본적인(기본적이라고 해서 나쁜 것은 아닙니다) 디버깅 기법은 의심되는 변수의 값을 직접 출력해 보는 것입니다.

Ruby에서는 puts 또는 p를 사용하면 됩니다. 특히 pputs 변수.inspect와 동일한 역할을 하므로 객체의 내부 구조를 확인할 때 매우 유용합니다.

예제:

Book = Struct.new(:title)

def find_book(title)
  books = []
  books << Book.new('Eloquent Ruby')

  books.find { |b| b.title == title }
end

book = find_book('Eloquent Ruby')
p book # 책 객체가 출력됩니다

book = find_book('POODR')
p book # nil이 출력됩니다

book.name # 다음에 어떤 일이 일어날까요?

Pry로 더 깊이 파고들기

확인해야 할 변수가 많아지면 곳곳에 puts를 삽입하는 방식은 비효율적입니다. 이럴 때 pry를 사용해 보세요.

pry를 사용하면 특정 코드 줄에서 실행을 멈추도록 설정할 수 있습니다(이를 브레이크포인트라고 합니다). 실행이 멈추면 irb와 유사한 환경으로 진입하여, 프로젝트의 컨텍스트 안에서 Ruby 코드를 자유롭게 평가하거나 pry의 다양한 유용한 명령어를 실행할 수 있습니다.

사용법은 정말 간단합니다. 브레이크포인트를 설치하고 싶은 위치에 binding.pry 한 줄만 추가하면 됩니다. 물론 프로젝트에 pry를 require해야 합니다(require 'pry').

임시로만 사용할 거라면 스크립트를 실행할 때 다음과 같이 호출할 수 있습니다.

ruby -rpry app.rb

하지만 Rails 앱에서는 이 방법이 적합하지 않으므로 Gemfile에 pry를 추가하는 것이 좋습니다. 저는 개인적으로 에디터에 require 구문과 브레이크포인트를 같은 줄에 포함하는 스니펫(매크로)을 만들어 두고, 삭제할 때 한 번에 함께 제거되도록 관리합니다.

pry 세션에 진입하면 다음과 같은 화면을 볼 수 있습니다.

Ruby 프로그램 디버깅과 오류 수정 완벽 가이드

pry 세션을 완전히 종료하려면 exit!를 입력하세요. 일반 exit를 입력하면 다음 브레이크포인트까지 프로그램이 계속 실행됩니다.

pry의 강력함은 여기서 끝나지 않습니다. 예를 들어 ls 명령어를 사용하면 객체가 접근할 수 있는 메서드와 인스턴스 변수 목록을 확인할 수 있습니다.

Ruby 프로그램 디버깅과 오류 수정 완벽 가이드

모든 명령어 목록이 궁금하다면 help 명령어를 실행해 보세요!

또 다른 디버거: Byebug

Byebug는 pry의 대체재로 사용할 수도 있고, gdb처럼 동작하는 Ruby 전용 디버거로도 활용할 수 있습니다.

pry 대체재로 사용하려면 코드 실행을 멈추고 싶은 위치에 binding.pry 대신 byebug를 넣으면 됩니다. 다만 Byebug는 pry와 달리 문법 강조(syntax highlighting)를 지원하지 않는다는 단점이 있습니다.

Byebug에서 브레이크포인트를 설정하고 코드를 디버깅하는 방법을 살펴보겠습니다.

보통은 help 명령어를 먼저 호출하지만, Byebug의 help는 정보가 다소 부족합니다.

Ruby 프로그램 디버깅과 오류 수정 완벽 가이드

따라서 공식 문서를 참고하는 것이 좋습니다. break 명령어와 줄 번호를 함께 사용하면 브레이크포인트를 설정할 수 있고, info breakpoint로 설정된 브레이크포인트 목록을 확인할 수 있습니다.

브레이크포인트가 설정되면 다음 명령어들로 프로그램 실행을 제어할 수 있습니다.

  • step — 한 단계씩 진행하며 메서드 내부로 진입합니다.
  • next — 한 단계씩 진행하되 메서드 내부로는 들어가지 않습니다.
  • continue — 프로그램이 끝나거나 다음 브레이크포인트에 도달할 때까지 실행합니다.

명령어 없이 엔터만 누르면 마지막 명령어가 반복 실행됩니다. 코드를 한 줄씩 따라가며 디버깅할 때 매우 편리한 기능입니다.

모든 방법이 실패했을 때

오랜 시간 고민했는데도 해결책이 보이지 않는다면 잠시 휴식을 취하세요. 새로운 마음으로 돌아오면 해결책이 이미 눈앞에 있었음을 깨닫게 되는 경우가 많습니다. 다른 사람에게 문제를 설명해 보는 것도 좋은 방법입니다(흔히 말하는 '러버 덕 디버깅'이죠).

문제의 위치조차 알 수 없을 때도 선택지는 충분합니다. 예를 들어 코드 블록을 주석 처리하면서 문제를 격리해 볼 수 있습니다. 문제가 사라졌다면, 주석 처리한 코드 중 일부를 다시 활성화하면서 범위를 좁혀 나가면 됩니다. 아주 로우테크한 방법이지만, 때로는 정확히 이것이 필요한 해결책입니다.

여기까지 시도했는데도 도움이 되지 않았다면, 이제 강력한 무기를 꺼낼 차례입니다. 다음 시스템 도구들이 큰 도움이 될 수 있습니다.

  • Wireshark — 네트워크 트래픽을 상세히 검사할 수 있는 도구입니다.
  • mitmproxy — SSL 암호화된 트래픽을 다룰 때 MITM(중간자) 프록시 방식으로 도움을 받을 수 있습니다.
  • curl — 터미널에서 직접 HTTP 요청을 보내 서버의 잘못된 응답을 디버깅하는 데 유용합니다.
  • strace(Linux 전용) — 애플리케이션이 수행하는 모든 시스템 콜을 보여줍니다. -e 옵션으로 특정 시스템 콜만 필터링할 수 있으며, 더 현대적인 대안으로는 sysdig가 있습니다.

경고! strace는 테스트 대상 시스템의 성능을 심각하게 저하시키므로 운영 환경(production)에서의 사용은 피하는 것이 좋습니다.

마지막으로, 문제가 외부 gem에서 발생한 것으로 보인다면 해당 gem의 소스 코드를 직접 확인하는 것이 좋습니다. gem open <gem-name> 명령어를 사용하면 설정된 에디터에서 소스 코드를 바로 열 수 있습니다.

마무리

디버깅이 세상에서 가장 재미있는 작업은 아니지만, 이를 더 쉽게 만들어 주는 도구와 기법은 무궁무진합니다. 오늘 소개한 방법들을 적극적으로 활용해 보세요.

이 글이 도움이 되었다면 주변에 공유해서 더 많은 개발자가 배울 수 있도록 해주세요! 🙂

감사합니다.