루비(Ruby)의 클래스 변수는 악명이 높습니다. 숙련된 루비 개발자조차 그 동작을 직관적으로 예측하기 어려워합니다. 가장 대표적인 예는 상속과 관련이 있습니다. class Fruit @@kind = nil def self.kind @@kind end end class Apple < Fruit @@kind = apple end Apple.kind # => apple Fruit.kind # => apple 자식 클래스에서 kind 변수를 변경했을 뿐인데 부모 클래스의 값까지 함께 바뀌어
실행 중인 프로세스에서 발생하는 모든 메서드 호출을 실시간으로 기록할 수 있다는 사실을 알고 계셨나요? 심지어 실행 중인 프로세스 내부에 코드를 주입해 실행하는 것도 가능합니다. 이 모든 것이 바로 rbtrace 젬의 마법 덕분입니다. rbtrace의 구조 rbtrace 젬은 크게 두 부분으로 구성됩니다. 추적 라이브러리: 추적하고자 하는 코드에 포함시켜 사용하는 라이브러리 명령줄 유틸리티: 수집된 추적 데이터를 조회하는 CLI 도구 간단한 예제로 살펴보기 먼저 아주 간단한 예제를 통해 동작 방식을 확인해 보겠습니다. 추적
실용적인 내용에서 잠시 벗어나, Ruby의 흥미로운 트릭 하나를 소개해 보려고 합니다. Ruby는 프로그램을 실행하기 전에 반드시 코드를 파싱(parse)해야 합니다. 이 파서는 일종의 상태 머신(state machine)으로 동작하는데, 잘 알려지지 않은 명령줄 옵션 하나를 사용하면 상태 머신이 수행하는 모든 작업을 로그로 확인할 수 있습니다. -y 플래그로 파서 동작 들여다보기 다음과 같은 간단한 예제 코드가 있다고 가정해 보겠습니다: a = 1 + 2 이 코드를 -y 플래그와 함께 실행하면 다음과 같은 출력 결과를
커맨드 라인을 제대로 마스터하려면 수십 개, 어쩌면 수백 개에 달하는 작은 유틸리티 프로그램들을 익혀야 합니다. 각 도구는 조금씩 다르게 동작하기 때문에 상당히 부담스러울 수 있습니다. 다행히도 Ruby 같은 범용 프로그래밍 언어를 사용하면 이런 단일 목적의 도구들을 상당수 대체할 수 있습니다. 이미 알고 있는 Ruby 지식을 활용해 커맨드 라인 실력을 한 단계 끌어올릴 수 있는 것이죠. 이 글에서는 Ruby를 커맨드 라인 만능 도구로 활용하는 기본기를 살펴봅니다. 현란한 원라이너(한 줄 코드)를 나열하기보다는 실제 작동 방식을
솔직히 고백하자면, 저는 부동소수점 숫자가 좀 마음에 들지 않습니다. 물론 컴퓨터 입장에서는 유용하겠지만, 인간 입장에서는 다음과 같은 상황을 마주하면 어리둥절해질 수밖에 없죠. 129.95 * 100 # => 12994.999999999998 이는 수학적 조화에 어긋날 뿐만 아니라, UX 측면에서도 좋지 않습니다. 만약 레시피에서 밀가루를 0.37211927843컵 재라고 한다면, 아마 저자를 이상한 사람이라고 웃어넘기면서 1/3컵을 계량하겠죠. 대부분의 사람들은 임의의 소수보다 분수를 훨씬 쉽게 이해합니다. 따라서 앱이
예외(exception)가 어떻게 동작하는지 이해하는 일은 생각보다 훨씬 까다롭습니다. 특히 규모가 큰 애플리케이션에서는 더욱 그렇죠. 기존 프로젝트에서 코드를 작성하다가 예외를 발생시켰는데 이상한 일이 벌어진 경험이 있을 겁니다. 예외가 조용히 사라져 버리거나, 환경 변수가 변경되거나, 아니면 다른 예외로 감싸져 버리는 경우가 있습니다. 이 글에서는 TracePoint를 활용해 애플리케이션 내 예외에 대한 정보를 더 자세히 들여다보는 간단한 방법을 소개합니다. 심지어 예외가 어딘가에서 사라져 버린 경우에도 말이죠. 편리한 예제
모든 오류가 치명적인 것은 아닙니다. 어떤 오류는 단순히 다시 시도하라는 신호일 뿐입니다. 다행히 Ruby는 이러한 재시도를 손쉽게 구현할 수 있는 몇 가지 흥미로운 메커니즘을 제공합니다. 다만 그 방법들이 모두 직관적이거나 널리 알려진 것은 아닙니다. 이 글에서는 이러한 메커니즘들이 실제 환경에서 어떻게 동작하는지 살펴보겠습니다. retry 키워드 소개 이 방법은 존재 자체만 안다면 꽤 자명합니다. 필자 역시 Ruby 개발 경력이 상당히 쌓인 후에야 retry 키워드의 존재를 알게 되었습니다. retry는 Ruby의 예외 처
Ruby에서는 예외를 구조(rescue)한 뒤 다른 종류의 예외를 다시 발생(re-raise)시키는 패턴이 매우 흔하게 사용됩니다. ActionView가 가장 대표적인 예입니다. 이전에 TracePoint에 대해 작성한 글에서도 언급했듯이, ActionView는 템플릿 내부에서 발생하는 모든 예외를 삼켜버리고 이를 ActionView::TemplateError 형태로 다시 던집니다. 그런데 때로는 이것만으로는 부족할 때가 있습니다. 원본 예외 객체 안에 문제 해결에 꼭 필요한 데이터가 담겨 있어서 반드시 확인해야 하는 경우죠. 다
Ruby에서 rescue 구문을 사용하면 어떤 종류의 예외를 처리할지 지정할 수 있습니다. 다음과 같이 예외 클래스 목록을 넘겨주기만 하면 됩니다. begin raise RuntimeError rescue RuntimeError, NoMethodError puts rescued! end 하지만 코드를 작성하는 시점에 어떤 예외 클래스가 발생할지 알 수 없다면 어떻게 해야 할까요? 가장 단순한 방법은 일단 모든 예외를 받은 뒤 조건을 검사하고, 조건에 맞지 않으면 다시 raise하는 것입니다. 대략 이런 식이죠. begin
Ruby 애플리케이션 서버는 보통 nginx 같은 웹 서버와 함께 사용됩니다. 사용자가 Rails 앱의 페이지를 요청하면 nginx가 그 요청을 애플리케이션 서버에 전달하죠. 그런데 이 과정은 정확히 어떻게 동작하는 걸까요? nginx는 unicorn과 어떤 방식으로 대화하는 걸까요? 가장 효율적인 방법 중 하나는 바로 유닉스 소켓(unix socket)입니다. 이 글에서는 소켓의 기본 개념부터 시작해, 마지막에는 nginx가 프록시 역할을 맡아주는 나만의 간단한 애플리케이션 서버까지 직접 만들어 보겠습니다. 소켓을 활용하면
아직 Byebug를 사용해 보지 않으셨다면 지금 바로 확인해 보시길 권합니다. Ruby 2.x 환경에서 사용할 수 있는 훌륭한 디버거입니다. 개발자들의 설명을 인용하면 다음과 같습니다. Byebug는 Ruby 2를 위한 사용하기 쉽고 기능이 풍부한 디버거입니다. 실행 제어에는 새로운 TracePoint API를, 호출 스택 탐색에는 Debug Inspector API를 사용하기 때문에 내부 코어 소스에 의존하지 않습니다. C 확장으로 개발되어 속도가 빠르고, 전체 테스트 스위트를 갖추고 있어 신뢰할 수 있습니다. 기본 설정은 매
case 문만큼 단순하고 지루한 문법도 없을 것입니다. C 언어에서 물려받은 유산이고, 길게 늘어진 if 문들을 대체하는 용도로 쓰이니까요. 이제 더 이상 설명할 게 없어 보입니다. 하지만 정말 그럴까요? 사실 루비의 case 문은 여러분이 상상하는 것보다 훨씬 풍부하고 강력합니다. 간단한 예제 하나부터 살펴보겠습니다. case Hi there when String puts case statements match class end # 출력 결과: case statements match class 이 예제는 case 문이 값(
쉽게 재현할 수 없는 버그를 경험해 본 적이 있으신가요? 앱을 한동안 사용한 사용자에게만 발생하는 것처럼 보이는데, 에러 메시지와 백트레이스는 놀랍도록 도움이 되지 않죠. 바로 이런 순간에 예외가 발생하기 직전의 앱 상태를 스냅샷으로 찍을 수 있다면 정말 유용할 것입니다. 예를 들어 모든 지역 변수와 그 값들의 목록을 확보할 수 있다면 말이죠. 사실 가능합니다. 그것도 생각보다 어렵지 않습니다! 이 글에서는 예외가 발생한 시점에 지역 변수를 캡처하는 방법을 소개합니다. 하지만 그 전에 주의사항부터 말씀드려야겠습니다. 이 기법들은
유닉스 데몬(daemon)은 백그라운드에서 실행되는 프로그램입니다. Nginx, Postgres, OpenSSH가 대표적인 예입니다. 데몬은 몇 가지 특별한 기법을 사용해 프로세스를 분리(detach)하여, 어떤 터미널에도 종속되지 않고 독립적으로 실행됩니다. 저는 늘 데몬이라는 존재에 묘한 매력을 느껴왔습니다. 아마 이름 때문이기도 할 겁니다. 그래서 데몬이 어떻게 동작하는지, 그리고 Ruby에서 어떻게 만들 수 있는지 소개하는 글을 써보면 재미있겠다고 생각했습니다. ...하지만 그 전에 먼저 짚고 넘어갈 것이 있습니다. 집에서
루비의 예외 계층 구조를 한 번이라도 살펴본 적이 있다면, 뭔가 낯선 부분을 발견했을 것입니다. RuntimeError나 NoMethodError 같은 익숙한 예외들 사이에 Errno::*라는 이상한 항목이 끼어 있습니다. Exception StandardError ... SystemCallError Errno::* ... 디스크가 가득 찬 상태에서 파일에 쓰기를 시도하거나, 불안정한 네트워크 환경에서 API 호출을 해본 경험이 있다면 이런 오류를 직접 마주해봤을 겁니다. 실제로 지금 당장, 존
때로는 표준 백트레이스와 에러 메시지의 조합만으로는 오류의 원인을 파악하기에 부족할 때가 있습니다. 문제의 근본 원인을 찾으려면 추가적인 데이터가 필요하죠. 다행히 Ruby에서는 이를 아주 간단하게 구현할 수 있습니다. 1. 에러 메시지 커스터마이징 예외에 컨텍스트 정보를 추가하는 가장 간단한 방법은 예외 메시지 자체에 정보를 담는 것입니다. 아래 예제에서는 발생한 예외를 잡아 새로운 메시지와 함께 다시 던지고(re-raise) 있습니다. begin raise foo rescue => e raise e.class, b
Fiddle은 Ruby 1.9.x 버전에 표준 라이브러리로 추가된, 아직 많이 알려지지 않은 모듈입니다. 이 모듈을 사용하면 Ruby 코드에서 C 라이브러리와 직접 상호작용할 수 있으며, 심지어 실행 중인 Ruby 인터프리터 자체를 들여다보고 수정하는 것도 가능합니다. Fiddle은 널리 쓰이는 C 라이브러리인 libffi를 감싸는 방식으로 동작합니다. libffi는 한 언어로 작성된 코드가 다른 언어로 작성된 함수를 호출할 수 있게 해주는 라이브러리로, ffi는 foreign function interface(외부 함수 인터페이
Honeybadger에서는 Sidekiq를 아주 많이 사용하는데, 정말 훌륭한 도구입니다. 그런데 개발하면서 자주 마주치는 골칫거리가 하나 있습니다. 바로 저는 평소 개발 환경에서는 Sidekiq를 실행하지 않는다는 점입니다. 그런데 수동 테스트 과정에서 수많은 잡(job)이 큐에 쌓이게 되죠. 그 결과, 다음에 Sidekiq를 실행하면 밀려 있던 모든 잡을 한꺼번에 처리하려고 들어 문제가 됩니다. 특정 Sidekiq 큐에서 잡을 삭제하는 코드 조각은 인터넷에서 쉽게 찾을 수 있습니다. 하지만 저희 서비스에는 수많은 큐가 존재하고,
Rails로 개발할 때의 큰 장점 중 하나는 개발 중 문제가 발생했을 때 정말 훌륭한 에러 상세 페이지를 볼 수 있다는 점입니다. 애플리케이션과 관련된 부분이 강조된 깔끔한 백트레이스가 제공되고, POST로 전송된 파라미터를 확인할 수 있으며, 환경 변수와 세션 변수까지 자세히 들여다볼 수 있습니다. 오늘은 이렇게 화려한 에러 페이지가 어떻게 동작하는지 그 내부 원리를 살펴보겠습니다. ActionPack 열어보기 오늘 집중적으로 살펴볼 파일은 actionpack/lib/action_dispatch/middleware/deb
Ruby의 raise 구문은 발생시키고 싶은 오류의 종류를 지정하는 몇 가지 방법을 제공합니다. 아래 코드에서는 RuntimeError를 발생시키는 세 가지 방식을 확인할 수 있습니다. raise hello raise RuntimeError, hello raise RuntimeError.new(hello) # ...위의 세 가지 코드 모두 RuntimeError: hello를 결과로 냅니다. 그런데 예외가 아닌 다른 것을 발생시키고 싶다면 어떻게 될까요? 만약 숫자를 발생시키고 싶다면요? 안타깝게도 Ruby는 이를 허용하지 않으