(본 게시물은 저작권의 문제 발생시 출판사의 요청에 의해 삭제될 수 있습니다.)
루비 디버거
루비는 디버거와 함께 제공되고, 편리하게 기본 시스템에 내장되어 있다. 인터프리터를 실행할 때 스크립트의 이름, 다른 루비 옵션과 함께 -r debug 옵션을 주면 디버거를 실행할 수 있다.
ruby -r debug <디버그 옵션> <프로그램 파일> <프로그램 인자>
루비 디버거를 이용하면 보통의 디버거가 갖추고 있는 통상적인 범주의 기능을 대부분 제공한다. 중단점을 설정하거나 메서드 호출에 들어가거나(step into) 나오거나(step over), 스택 프레임과 변수를 출력할 수 있다. 또한 특정 객체 또는 클래스에 대해 정의된 인스턴스 메서드를 나열하거나 이를 제어할 수 있다.
루비 디버거 예제 세션을 살펴보자.
$ ruby -r debug t.rb
Debug.rb
Emacs support available.
t.rb:1:def fact(n)
(rdb:1) list 1-9
[1, 9] in t.rb
=>1 def fact(n)
2 if n <= 0
3 1
4 else
5 n * fact(n-1)
6 end
7 end
8
9 p fact(5)
대화형 루비 셀
대화형 루비 셀irb)는 운영체제 셀(작업 관리 기능을 가진)과 비슷한 루비 셸이다. irb는 실시간으로 루비를 가지고 놀 수 있는 환경을 제공한다. irb 실행 명령은 다음과 같다.
irb <irb 옵션> <루비 스크립트> <프로그램 매개 변수들>
irb는 표현식을 하나 입력할 때마다 평가 결과를 출력한다.
irb(main):001:0> a = 1 +
irb(main):002:0* 2 * 3 /
irb(main):003:0* 4 % 5
=> 2
irb(main):004:0> 2 + 2
=> 4
irb 는 각각 자신만의 문맥(context) 을 가지는 서브세션을 생성할 수 있다. 예를 들면, 초기 세션과 동일한 (최상위 수준의) 문맥을 갖는 서브세션을 생성하거나 특정 클래스 또는 인스턴스의 문맥 내에서 서브 세션을 생성할 수 있다.
편집기 지원
루비 인터프리터는 한 번에 프로그램을 읽어 들이도록 설계되어 있다. 이는 전체 프로그램을 파이프를 통해 인터프리터의 표준 입력으로 보낼 수 있다는 것을 의미하며, 그대로 잘 작동할 것이다.
이러한 기능을 활용해 편집기 내부에서 루비 코드를 실행할 수 있다. vi 편집기에서 :%!ruby 는 프로그램 텍스트를 그것의 출력으로 바꿔치기 한다. 또는 :w_!ruby 명령을 이용해 버퍼에 영향을 주지않고 출력 결과만 볼 수 있다.
동작하지 않을 때!
다음은 널리 알려진 실수와 팁들을 열거한 것이다.
1) 무엇보다 명심해야 하는 점은 먼저 경고가 출력되도록 하면서 스크립트를 실행하는 것이다( 명령행에서 -w 옵션으로 경과를 활성화한다)
2) 인자 목록(특히 출력용) 안에서 ','를 쓰는 것을 잊어버렸다몬 매우 이상한 에러 메시지가 나올 수 있다.
3) 속성 설정 메서드가 호출되지 않는다. 클래스 정의 내에서 루비는 setter=를 메서드 호출이 아닌 지역 변수에 대한 대입으로 해석한다. 메서드를 호출하려면 self.setter=의 형태를 사용해야 한다.
class Incorrect
attr_accessor :one, :two
def initialize
one = 1 # 의도대로 동작하지 않음, 이는 지역변수 one에 1을 대입한다.
self.two = 2
end
end
obj = Incorrect.new
obj.one #=> nil
obj.two # => 2
4) 적절히 초기화 되지 않은 것처럼 보이는 객체는 initialize 메서드의 이름을 틀리게 썻을 수 있다.
class Incorrect
attr_reader :answer
def initialise # 철자가 틀렸다.
@answer = 42
end
end
ultimate = Incorrect.new
ultimate.answer # => nil
인스턴스 변수 이름을 잘못 입력했을 때에도 비슷한 일이 일어날 수 있다.
class Incorrect
attr_reader :answer
def initialize
@anwser = 42 # 철자가 틀렸다.
end
end
ultimate = Incorrect.new
ultimate.answer # => nil
5) 소스코드 마지막줄에서 해석 에러(parse error)가 나타나는 것은 대부분 end 키워드를 깜빡햇거나 앞당겨 써버린 경우다.
6) syntax error, unexpected $end, expecting keyword_end 이러한 에러 메시지는 오류 코드 내에서 end가 빠져 있음을 의미한다.(메시지 내의 $end 는 end-of-file을 의미한다. 즉 필요한 end 키워드들을 모두 찾기도 전에 코드가 마지막에 도달했음을 의미한다.)
7) 루비 1.9부터 블록 매개 변수는 지역 변수와 같은 스코프를 가지지 않는다. 이는 과거의 코드와 호환되지 않을 수 있음을 의미한다. 이러한 문제를 찾기 위해 -w 옵션을 사용한다.
entry = "wibble"
[1, 2, 3].each do | entry |
# entry에 대한 처리.
end
puts "Last entry = #{entry}"
실행결과
prog.rb:2: warning: shadowing outer local variable - entry
Last entry = wibble
8) 연산자 우선순위를 신경 써야 한다. 특히 do...end 대신에 { ... } 를 사용할 때 주의하자.
def one(arg)
if block_given?
"block given to 'one' returns #{yield}"
else
arg
end
end
def two
if block_given?
"block given to 'two' returns #{yield}"
end
end
result1 = one two {
"three"
}
result2 = one two do
"three"
end
puts "With braces, result = #{result1}"
puts "With do/end, result = #{result2}"
실행결과
With braces, result = block given to 'two' returns three
Wtih do/end, result = block given to 'one' returns three
9) 터미널에 대한 출력을 버퍼링될 것이다. 이것은 어떤 메시지를 출력하자마자 볼 수 있는 것은 아니라는 의미다. 게다가 STDOUT 과 STDERR 양쪽에 모두 메시지를 출력한다면, 출력 결과는 기대했던 순서대로 나타나지 않을 수도 있다. 디버그 메시지를 보여주려면 항상 버퍼링하지 않은 I/O(sync=true 옵션을 사용)를 사용하라.
10) 숫자가 올바르게 나오지 않는다면 아마도 그것은 문자열 타입일 것이다. 파일에서 읽어 들인 텍스트는 String이며, 루비는 이 값을 자동으로 숫자로 변환하지 않는다. Integer 메서드 호출을 이용하면 잘 동작할 것이다.
while line = gets
num1, num2 = line.split(/,/)
# ...
end
이는 다음과 같이 고칠 수 있다.
while line = gets
num1, num2 = line.split(/,/)
num1 = Integer(num1)
num2 = Integer(num2)
# ...
end
또는 map을 사용해서 모든 문자열을 변환할 수 있다.
while line = gets
num1, num2 = line.split(/,/).map { | val | Integer(val) }
end
11) 의도하지 않은 별칭(aliasing) - 만일 어떤 객체를 해시에 대한 키로 사용한다면 그 객체게 자신의 해시 값을 변경하지 않도록 해야 한다. (또는 값이 변경되었을 때 Hash#rehash를 호출하도록 한다)
arr = [1, 2]
hash = { arr => "value" }
hash[arr] # = > "value"
arr[0] = 99
hash[arr] # => nil
hash.refresh # => { [99, 2]=>"value"}
hash[arr] # => "value"
12) 사용 중인 객체의 클래스는 개발자가 생각하는 것과 동일해야 한다. 의심스러운 부분이 있다면 puts my_obj.class를 사용하라.
13) 메서드 이름은 항상 소문자로 시작하다록 하고, 클래스와 상수 이름은 항상 대문자로 시작하라.
14) 메서드 호출이 기대했던 것을 수행하지 않는다면, 매개 변수를 괄호로 둘러 쌌는지 확인하라.
15) irb 와 디버거를 사용하라.
16) Object#freeze를 사용하라. 코드 중 알 수 없는 부분에서 변수를 엉뚱한 값으로 설정하고 있다면 해당 변수를 동결시켜 본다. 변수 값을 변경하려 시도할 때 용의자를 잡을 수 있을 것이다.
루비 코드를 더 쉽고 재미있게 작성하기 위한 기법중 하나는 애플리케이션을 점진적으로 개발하는 것이다. 즉 코드를 몇 줄 작성한 다음 실행하며 코드를 늘여간다. Test::Unit을 사용할것! 동적 타입 언어의 주요 장점 중 하나는 실행을 위해 꼭 완벽한 코드가 필요한 것은 아니라는 점이다.
너무 느릴 때
루비는 인터프리터 방식의 고수준 언어로 C 같은 저수준 언어만큼 빠르지 않다. 이어지는 내용에서는 실행 속도 향상을 위한 방법을 알아보자.
일반적으로 느리게 동작하는 프로그램들은 한두 개의 병목, 즉 실행 시간을 대부분 잡아먹는 부분이 있다. 이런 부분들을 찾아서 개선하면, 프로그램은 충분히 개선할 수 있다. 이를 위해 Benchmark 모듈과 루비 프로파일러(profiler)를 이용하면 된다.
Benchmark 모듈
코드 일부분을 실행하는데 걸리는 시간을 측정하기 위한 Benchmark 모듈을 사용하라. 단 벤치마크를 할 때 주의할 점은 루비 프로그램이 가비지 컬렉션 부하 때문에 느리게 동작하는 경우가 자주있다. 이러한 가비지 컬렉션을 프로그램 실행 경우에 언제든지 발생할 수 있기 때문에 벤치마크 결과가 잘못 나올 수 있다. 특정 코드가 느리게 나오더라도 코드가 실행될 때 가비지 컬렉션이 같이 실행된 것일 수 있다.
Benchmark 모듈은 테스트를 두 번 수행하는 bmbm 메서드를 가지고 있다. 한 번은 예행 연습이고 다음 한 번은 실제 성능을 측정한다. 이렇게 하는 이유는 가비지 커렉션으로 인한 데이터 왜곡을 최소화하기 위해서다.
프로파일러
루비는 코드 프로파일러를 포함하고 있다. 프로파일러는 프로그램 내의 각 메서드의 몇 번씩 호출되었는지, 그리고 루비가 해당 메서드에서 머물렀던 평균 및 누적 시간을 측정해 준다.
명령행 옵션 -r profile 또는 코드 안에 require 'profile'을 사용해서 코드를 프로파일 할 수 있다. 아래의 예를 살펴보자.
count = 0
words = File.open("/usr/share/dict/words")
while word = words.gets
word = word.chomp!
if word.length == 12
count += 1
end
end
puts "#{count} twelve-character words"
위의 코드는 프로파일러를 사용하여 동작하는 경우 많이 느리게 동작할 것이다. 루프를 개선하는 방법은 단어 리스트를 긴 문자열로 읽어 들인 다음 패턴 매치를 사용해 열두 문자의 단어를 찾어낸다.
words = File.open("/usr/share/dict/words")
count = words.scan(/^............\n/).size
puts "#{count} twelve-character words"
프로파일 결과가 훨씬 좋아졌다.(프로파일을 하지 않는다면 지금보다 다섯 배 정도 빠르게 작동한다)
루비는 매우 투명하고 표현력이 강한 언어다. 그렇다고 해서 프로그래머가 상식의 끈을 놓아버려도 된다는 의미는 아니다. 불필요한 객체를 생성하고 불필요한 작업을 수행하고, 불어터진 코드를 작성하면 어떤 언어를 사용하건 프로그램은 느려질 것이다.
끄읕.