본문 바로가기
나의 서재

[책]Programming Ruby(프로그래밍 루비)

by esstory 2008. 2. 3.


Programming Ruby 프로그래밍 루비 - 전2권 - 10점
데이브 토머스 외 지음, 강문식 외 옮김/인사이트

 

 

1990년에 전산학과에 입학은 했지만, 그다지 PC와는 친하지 않아서 하릴없이 겨울 방학을 보내던 참이었는데, 서울에서 역시 전산학과에 재학중인 고등학교 동기 놈이 디스켓 5장인가를 가지고 시골에 내려 왔습니다.

친구 덕분에 제 평생 처음으로 프로그램 언어라는 걸 배웠는데, 5.25인치 Diskette 2장만 있으면 편집에서 컴파일, 실행파일까지 뚝딱 만들어내는 Turbo Pascal 의 매력에 푹 빠지지 않을 수 없었습니다.

파스칼은 나중에 배운 C 와는 달리 입문자가 처음 배우기에 정말 적합한 언어였습니다. 복잡한 포인터 같은 개념이 없어서 배우기 쉬웠고, Begin, End 와 같은 친숙한 영어단어들이 많이 사용된 언어여서 접근성이 좋았던 것 같습니다.  게다가 당시에 파스칼 관련 책들도 제법 시중에 시판 중이어서, 책 몇 권과 Turbo Pascal 만으로 6개월을 재미있게 보냈던 기억이 납니다.

파스칼로 이제 겨우 Top Down 메뉴도 만들고, 누군가에게 자랑할만한 프로그램을 만들 만큼 익숙해 져 가던 차에 선배 몇 명이 이번에는 Turbo C 디스켓을 들고 다니면서 대세는 C라고 말하고 다니더군요.

이제 겨우 파스칼이라는 언어에 익숙해 져서 뭔가 만들어 볼만 하다고 생각했는데 세상은 이미 C 가 점령한 상태였습니다.

C 언어를 처음 본 느낌은 파스칼의 beginend문을 {…… } 로 변경한 정도 같았기 때문에(뭐든 제대로 알기 전에 섣불리 판단해서는 안되었는데), 제가 아는 유일한 언어를 버리고 새로운 언어를 배운다는 것 자체가 쉽지가 않더군요.

하지만 시대가 그러하니 어쩔 수 없이 C 언어의 세계로 빠져들었고(대세에 동참하지 않으면 도태될 수 밖에 없는 게 현실이기 때문에), C 가 가진 무한한 능력, 포인터와 구조적 프로그램의 세계에 점점 빠져들었습니다. 

그 후 윈도우용 어플리케이션을 만들기 위해 자의 반 타의 반으로 C++ 을 사용하는 MFC 에 익숙해 져야 했고, 지금까지 C++로 밥벌이를 하고 있는 중입니다.

 

그 사이 비주얼 베이직이나, 자바와 같은 주류에 드는 언어에 아주 잠깐 눈길을 돌린 적은 있으나, 윈도우용 어플리케이션을 개발하기 위한 주력언어로서는 부족해 보였고, 특히 스크립트 언어의 어쩔 수 없는 성능 저하 때문에 나중에는 거들떠도 보질 않게 되었습니다. (다른 곳에 눈을 돌릴 만큼 적응력이 빠르지 않아서 새로운 언어가 쉽게 다가오지 않았던 것도 사실입니다)

 

이번에 본 책(사실은 다 읽은 지 벌써 한 달은 되는 것 같습니다), 스크립트 언어의 대명사로 떠오르고 있는 루비라는 언어입니다.

웹 하시는 분들에게는 워낙 유명한 언어겠지만, 저 같이 어플리케이션을 개발하는 사람에게는 그다지 친숙하지 않은 언어였는데, 제가 이 책을 보게 된 계기는 실용주의 프로그래머에서 저자가 프로그램 위에서 구동되는 프로그램을 위한 언어를 사용해야 한다, 텍스트를 다루는 프로그램을 사용해야 한다는 말을 누누이 강조했기 때문입니다. 물론 주력 언어인 C++로도 어느 정도 원하는 프로그램을 개발할 수 있겠지만, 적은 노력으로 동일한 결과를 얻을 수 있는 새로운 언어가 있다면 이를 익혀서 프로그램을 위한 자료를 수집하거나, 소스에서 특정 정보를 요약해 내거나 하는 작업을 할 수 있겠다 싶었습니다.

 

책의 저자는 제가 바이블로 삼고 있는 실용주의 프로그래머의 저자이기도 한 데이브 토머스, 앤디 헌트 입니다.

이 두 사람이 지은 책이니 만큼 책의 내용을 별다른 단점을 찾을 수 없이 실질적이고, 실용적인 루비의 장점과 루비의 기초 문법에 집중하고 있는 책입니다. (게다가 출판사가 인사이트입니다. 여기 출판사에서 나오는 책들은 대부분 이 분야에서 꽤 유명한 책들이고 제본 상태도 어느 출판사보다 좋습니다)

자바나 C# 이 그랬듯이 워낙 문법들이 C++ 과 유사한 게 많아서 처음 책을 읽어 나갈 때, 이건 뭐 또 다른 자바나 C# 의 아류 작이 아닌가 하고 의심도 들었습니다.

하지만, 페이지를 이어 가면서 점점 루비의 막강한 능력, Regular Expression 과 코드 블록과 같은 참신한 능력에 반하게 되더군요.

제가 무시하고 있는 스크립트 언어는, 비록 성능에는 포커싱을 맞추지 못했지만, 그 외 언어가 나아가야 하는 단순함, 코딩 그 자체에 집중할 수 있도록 해 주는 간결함과 기존에 틀에 박혀 있던 사고의 확장성이라는 목적에 몇 발짝 앞서 나가고 있었습니다.

C++ 로는 수십 줄이나 수백 줄이어도 구현하기 힘든 기능을 단 몇 줄로 구현하고, 이미 정의되어 있는 클래스에 기존 Method 를 새로 정의해 버리거나, 추가해 버리는 확장성에는 혀를 내둘러야 했습니다.


비록 책에서 본 수 많은 놀라운 언어적인 매력 덕분에 루비를 완전히 새롭게 바라보게 되었지만, 당분간은 바로 사용할 수 없는 언어이기에, (주력은 여전히 C++ 을 사용해야 하고, 기회가 있을 때마다 활용할 언어로 루비를 선택할 예정입니다) 책 서평과 함께 몇 가지 루비에 대한 주요한 특징들을 기록해 둡니다. (필요할 때 기억을 되살리기 위해)

 

1.     루비의 창시자 마츠모토 유키히로 라는 일본사람이 만든 언어. 동양인도 이런 멋진 언어를 만들 수 있구나 하는 생각을 해 봅니다. 펄과 파이썬을 대체하고자 개발했다고 합니다(펄보다 강력하고 파이썬보다 객체지향적인 언어를 만들고자 개발 개인적으로 펄이나 파이썬에 대해서는 전혀 모릅니다.)

 

2.     초 간단 루비 문법 정리 언뜻 보기에 C, C++ 의 아류 작이라는 느낌이 많이 들어 금방 익숙해 질 것 같지만, 생각 외로 복잡하게 외워야 하는 기본 지식들이 많았습니다. 사람이 말하고 듣는 언어처럼 프로그램 언어도 익숙해 지지 않으면 금방 잊어 버리기 때문에 간단한 루비 문법에서 특정되는 내용들을 정리했습니다.

1)     루비 기본 타입 숫자, 문자열, 배열, 해시, 범위, 심벌, 정규 표현식

 

2)     변수 선언

가)    인스턴스 변수 이름 - @으로 시작하는 변수. 클래스 본문 내의 인스턴스 메서드 안에서 사용할 수 있다. 일반적인 클래스의 멤버 변수라고 생각하면 됨.

나)    클래스 변수 이름 - @@으로 시작. 클래스나 모듈의 본문에서 사용할 수있다. 클래스 변수는 사용 전에 반드시 초기화 해야 하고, 클래스의 모든 인스턴스가 공유하며 클래스 자체 내에서 사용할 수 있다.

다)    상수 이름 대문자로 시작하고 그 뒤에 이름 문자들이 온다.

라)    전역변수 및 시스템 변수 - $로 시작. 프로그램 전체에서 사용할 수 있다.

 

3)     배열 선언 일반적인 언어들은 동일한 타입으로만 배열을 구성할 수 있지만, 루비는 타입에 상관없이 복수개의 타입들로 배열을 구성할 수 있습니다. 가변적인 배열을 위해서 별도의 배열 클래스(STL vector 와 같이)를 선언할 필요 없이 기본 타입인 Array 만으로 원하는 기능을 구현할 수 있습니다.

a = [‘개미’, ‘’, ‘고양이’]

b = %w{개미 벌 고양이 개 소}

a2 = [3.14159, “pie”, 99 ]

a2.class  -> array

a2[0] -> 3.14159

a2[2] -> 99

a2[3] -> nil

b2 = Array.new # 동적으로 배열 생성

b2[0] = “second”

b2[1] = “array”

# b2 -> [“second”, “array”]

 

4)     해시 테이블 만들기 – (Key => Value) 쌍으로 간단하게 STL::map 과 같은 기능을 구현할 수 있습니다.

h = { ‘dog’ => ‘canine’, ‘cat’ => ‘feline’, ‘donkey’ => ‘asinine’ }

h.length -> 3

h[‘dog’] -> “canine”

h[‘cow’] = “bovine”

 

5)     attr_reader, attr_writer: Class 멤버 변수에 대한 Get/Set 함수를 손쉽게 만들어 줍니다. C++ 에서는 이 단순 노가다 때문에 나름 매크로를 만들어 사용 중인데요. 루비에서는 이런 노가다 성 작업이 많이 줄어들도록 언어차원에서 배려해 주고 있습니다. C++ TR1 에 이런 기능들이 들어가면 좋겠네요 ^^.
attr_reader
다음에 변수 이름을 나열하면, 해당 변수에 대한 Get 함수들을 자동으로 생성해 줍니다.

attr_reader : name :artist : duration

 

6)     강력한 숫자 타입 지원 루비에서 정수 데이터 타입은 데이터의 크기에 따라 자동으로 Fixnum 에서 Bignum 클래스로 자료형이 변경됩니다. C 와 같은 언어에서는 표현할 수 있는 숫자보다 더 큰 값을 할당 받을 때 Overflow 문제를 겪게 되는데 적어도 루비에서는 그러한 걱정 없이 사용할 수 있는 장점이 있습니다.

num = 81

6.times do

    print “#{num.class} : #{num}”

    num *= num

end

 

# 실행결과는 아래와 같이 어느 순간에 같은 변수의 내용이 자동으로 타입 변환됩니다

Fixnum 81

Fixnum 6561

Fixnum 43046721

Bignum 1853020188851841

….

 

7)     모든 숫자는 객체입니다. 따라서 3.times 와 같이 숫자 다음에 ‘.’ 을 찍고 숫자 객체(Fixnum)의 클래스 Method 를 마음껏 호출 할 수 있게 됩니다.

 

8)     탁월한 문자열 처리 아래의 예는 ‘|’ 과 공백을 구분자로 문자열을 나눠 각각을 File, Length 등의 변수에 한번에 저장하는 역할을 합니다. C 에서는 strtok 함수를 수 차례 호출해야 가능한 일을 단 한 줄로 쉽게 처리할 수 있습니다.

Song_file.each do | line |

   File, length, name, title = line.chomp.split(/\s*\|\s*/)

End

또한 len 이라는 변수에 “2:58” 이라고 들어 있을 때
mins, secs = len.split(/:/)
“:” 를 기준으로 텍스트를 나눠어 mins secs 에 저장합니다.

9)     함수

가)    함수 선언 - def 함수명 ~ end

나)    함수명? – 어떤 값을 묻는 질문형 함수

다)    함수명= - 함수에 값을 대입시키는 것으로 주로 프로퍼티에 값을 설정할 때 쓰인다.

라)    함수명! – 함수는 어떤 값을 리턴하는데, 뒤에 ! 을 붙이면, 값을 리턴하는 대신 자기 자신의 값을 변경시킨다.

마)    가변 인자로 함수 호출 def function(arg1, *rest) -> rest 는 인자의 개수에 상관없이 가변 되는 인자들 리스트를 받을 수 있다. rest 는 하나이상의 인자이기 때문에 자동으로 Array 객체에 저장되어 배열과 같이 인자 내용에 접근할 수 있다.

바)    함수 인자 앞에 “&” 붙이기 코드 블록을 인자로 넘겨 변수로 저장한 후 다른 곳에서 코드 블록을 호출할 수 있음

class TaxCalculator

def initialize(name, &block)

           @name, @block = name, block

end

def get_text(amount)

           "#{amount}원에 대한 #@name = #{ @block.call(amount) }"

end

end

 

tc = TaxCalculator.new("부가가치세") {|amt| amt * 0.075 }

 

tc.get_text(100)  #결과: "100원에 대한 부가치세  = 7.5"

tc.get_text(250)  #결과: "250원에 대한 부가가치세 = 18.75"

 

사)    함수의 리턴값 모든 메서드는 값을 반환한다. return 문을 명백하게 사용하지 않을 경우 마지막으로 실행된 표현식의 결과값을 리턴 값으로 하고, 하나 이상의 값을 return 할 경우 자동으로 배열로 치환되어 배열을 리턴하게 된다.

 

10)   표현식

가)    a * b + c 표현식은 (a.*(b))+(c) 와 같이 * + 연산자에 각각 뒤에 있는 오퍼랜드를 인자로 넘겨주는 것과 동일하다

 

나)    루비의 기존 연산자가 마음에 들지 않을 경우 간단하게 재정의해서 사용할 수 있다.

class Fixnum

           alias old_plus +

          

           def +(other)

                     # 기존 + 연산자를 호출한 후, 그 다음 값을 리턴

                     old_plus(other).succ

           end

end

 

다)    일반적인 비교연산자

===

Case 구문의 when(C switch) 항목이 비교할 대상과 동일한지 비교하는데 쓰인다.

<=>

일반적인 비교 연산자. 수신자가 매개변수보다 작은 경우 -1, 같으면 0, 매개변수보다 클경우 1을 리턴한다.

=~

정규표현식 패턴이 매칭되는지 검사한다.

eql?

수신자와 매개변수가 서로 같은 타입이며 같은 값을 가지는 경우 참이 된다

Equal?

수신자와 매개변수가 같은 객체 ID 를 가질 경우 참이다.

 

라)    Case 단순한 if/else 문의 나열을 대체할 뿐만 아니라 범위지정, 복수조건 나열, 정규표현식 조건 지정 기능 등 한번에 다양한 기능들을 동시에 처리할 수 있도록 해 줍니다.

leap = case

           when year % 400 == 0: true

           when year % 100 == 0: flase

           when year % 4 == 0

           end

          

case input_line

when "debug"

           dump_debug_info

           dump_symbols

when /p\s+(\w+)/

           dump_variable($1)

when "quit", "ext"

           exit

else

           print "illegal command : #{input_line}end”

end

kind = case year

                     when 1850 .. 1889 then "Blues"    # then 대신에 콜론(:) 을 대신 쓸 수 있다.

                     when 1890 .. 1909 then "Ragtime"

                     when 1910 .. 1929 then "New Orleans Jazz"

                     when 1930 .. 1939 then "Swing"

                     else      "Jazz"

           end

 

case line

           when /title=(.*)/

                     puts "Title is #$1"

           when /track=(.*)/

                     puts Track is #$1

end

 

마)    반복문 – while until

while line = gets

           # do something

end

until play_list.duration > 80

           play_list.add(song_list.pop)

end

a *= 2 while a < 10

a -= 10 until a < 100

 

file = File.open("abc.txt")

while line = file.gets

           puts(line) if line =~ /third/ .. ~ /fifth/

end

바)    반복자

3.times do

           print "HO!"

end

0.upto(9) do |x|

           print x, " "

end

0.step(12, 3) { |x| print x, " " }

# grep 을 이용해서 특정 조건에 맞는 라인에 대해서만 작업

File.open("abc").grep /d$/ do |line|

           print line

end

사)    For…In

for song in songlist

           song.play

end

#위 내용은 다음과 같이 변환해서 쓸 수 있다.

songlist.each do | song |

           song.play

end

for i in ['fee', 'fi', 'fo', 'fum']

           print i, " "

end

for i in 1...3

           print i

end

# 파일에서 d로 끝나는 단어를 찾아 낸다.

for i in File.open("ab").find_all { | item | item =~ /d$/ }

           print i.chomp

end

11)   모듈 루비에서 모듈은 C++ namespace 와 같이 클래스의 이름 충돌을 막기 위해 사용되고, 믹스인이라는 클래스 확장 기능을 기능을 제공합니다..

가)    믹스인 클래스에서 모듈을 include 하게 되면 모듈에 있는 메서드가 마치 클래스에서 선언한 메서드처럼 섞여(mixed in) 사용할 수 있게 됩니다. 비록 루비에서 다중 상속을 지원하고 있지는 않지만 하나의 클래스가 서로 다른 성격의 여러 가지 모듈을 믹스인해서 씀에 따라 다중 상속과 동일한 효과를 낼 수 있게 됩니다.

module Debug

           def who_am_i?

                     "#{self.class.name} (##{self.jobject_id}): #{self.to_s}"

           end

end

 

class Phonograph

           include Debug

           #..

          

end

 

class EightTrack

           include Debug

           #..

          

end

 

ph = Phonograph.new ("West End Blues")

et = EightTrack.new ("Surrealistic Pillow")

 

ph.who_am_i?  # 결과 -> Phonograph (#945760): West End Blues

et.who_am_i?  # 결과 -> EightTrack (#945740): Surrealistic Pillow

 

나)    모듈 include – 해당 모듈에 대한 참조를 만들기만 합니다. 하나 이상의 클래스에서 동일 모듈을 include 할 수 있고 프로그램 실행 시 모듈의 메서드 정의를 수정하면 모듈을 include 하는 모든 클래스는 새로운 동작을 하게 됩니다.

          

3.     루비의 단순함 단순하다는 말은 언어 자체가 하고자 하는 일에만 집중할 수 있도록 프로그래머에게 입력을 최소화할 수 있는 여러 기능을 제공한다는 의미입니다.

1)     간단한 Swap 기능 일반적인 언어들에서 두 변수의 값을 서로 바꿀 때는 임시 변수를 하나 사용하지 않고는 바꿀 방법이 없습니다. 하지만 루비에서는 아래와 같은 단 한 줄로 두 변수의 내용을 바꿀 수 있습니다. 지금까지 언어에서는 볼 수 없었던 단순함입니다.
a , b = b, a
또한 아래와 같이도 사용할 수 있다
x = 0
a, b, c = x, (x += 1), (x += 1)  #
결과 [0, 1, 2]

 

2)     모든 것을 객체로 생각하는 루비
[3, 1, 7, 0].sort.reverse  #
결과 : [7, 3, 1, 0]

 

3)     낮은 수준이든 높은 수준이든 상관없이 대부분의 인턴넷 프로토콜을 유창하게 지원합니다.

# finger 프로토콜을 이용해서 로컬 머신의 사용자 ‘mysql’ 에 대한 정보를 얻는다.

require 'socket'

client = TCPSocket.open('127.0.0.1', 'finger')

client.send("mysql\n", 0) # 0 은 표준 패킷을 의미

puts client.readlines

client.close

# 홈페이지에 내용을 가져와 해당 페이지에 들어 있는 이미지 목록을 표시한다.

require 'net/http'

h = Net::HTTP.net('www.pragmaticprogrammer.com', 80)

response = h.get('/index.html', nil)

if response.message = "OK"

           puts response.body.scan (/<img src="(.*?)"/m).uniq

end

 

4.     강력한 정규 표현식 지원 언어 차원에서 정규 표현식을 지원함으로써 일반 텍스트나 웹 문서, XML 등을 Parsing 하는데 중요한 이점으로 보입니다.
실제로 요즘 보고 있는 Beautiful Code 에서는 루비의 정규표현식을 활용해서 웹 로그를 검색하는 예가 나옵니다.(해당 본문의 저자는 몇 줄의 코드 만으로 이러한 일을 해 낼 수 있다는 것만으로도 아름다운 코드라고 얘기하네요)  단 몇 줄의 코드로 수십 기가나 되는 데이터 파일에서 원하는 로그를 가져와 검색할 수 있다는 것만으로도 큰 의미가 있다고 하겠습니다.
새로 나올 Visual Studio 2008 에서도 C++ 의 확장인 TR1을 지원하는데 여기에 정규 표현식 지원 얘기가 나오더군요. 많이 기대되는 부분입니다.
루비를 읽기 전에는 정규표현식에 대해 제대로 감도 못 잡고 있었는데 이래 저래 배우기 잘 했다 싶습니다.
정규표현식을 처음 배우는 저 같은 사람에게는 정규표현식을 표현하기 위한 각종 예약어들 때문에 머리가 많이 복잡한데요. 이와 관련된 자주 쓰이는 예약어들을 정리 해 봤습니다.

1)     앵커
기본적으로 정규표현식은 문자열에서 패턴이 나타나는 첫 위치를 찾으려고 하는데 앵커는 패턴이 매치되는 위치를 지정하게 해 준다. ‘^’는 문자열 맨 앞에 있을 때에만 매치되고 \A 는 지정된 문자열의 첫 부분을 나타내고, \z \Z 는 문자열의 끝을 나타낸다. \b \B 는 각각 워드 경계와 워드가 아닌 것의 경계를 나타낸다.

설명

/^the/

첫 번째 매치되는 the 를 리턴

/is$/

마지막으로 매치되는 is 를 리턴

/AThis/

This 로 시작할 경우에 매치 성공

/bis/

is 가 단어의 시작인 곳에서 매치 성공

/Bis/

is 가 단어의 시작이지 않고 단어 속에 나타나면 매치 성공

 

2)     문자 클래스 대괄호로 둘러싸인 문자의 집합으로 대괄호 안의 문자 중 하나에 매치된다. [aeiou] 는 매치 대상 문자열에서 모음이 하나라도 나오면 매치된다. 대괄호 안에서 시퀀스를 사용할 경우 시퀀스 내 모든 문자를 의미한다.

시퀀스

범위[…]식 표현

의미

\d

[0-9]

숫자

\D

[^0-9]

숫자를 제외한 모든 문자

\s

[\s\t\r\n\f]

공백 문자

\S

[^\s\t\r\n\f]

공백 문자를 제외한 모든 문자

\w

[A-Za-z0-9]

워드

\W

[^A-Za-z0-9]

워드를 제외한 모든 문자

 

3)     마침표 (.) – 개행문자를 제외한 어떤 문자와도 매치된다.
/C.S/ -> C
S 사이에 어떤 글자가 들어와도 됨(COS, CAS )

 

4)     반복 매치할 단어가 한번 이상 나타나야 하는 경우 *, + 등을 사용해서 반복회수를 지정하여 매치할 수 있다.

r*

r 이 없거나 1개 이상 나타나는 문자열과 매치

r+

r 이 하나 이상 나타나는 문자열과 매치

r?

r 이 한번 나타나거나 나타나지 않는 문자열과 매치

r{m,n}

r 이 최소 m , 최대 n 번 나타나는 문자열과 매치

r{m,}

r 이 최소 m 번 나타나는 문자열과 매치

 

5)     선택(‘|’) – a|b a 또는 b 에 매치되는 결과를 원할 때 사용

 

6)     그룹 괄호() 를 사용해서 그룹으로 묶고 그룹을 반복하여 매치할 수 있음

"12:50am" =~/(d\d):(\d\d)(..)/ 

라고 할 경우 괄호로 그룹핑된 내용들은 괄호순서와 매칭된 값에 따라 전역변수인 $1, $2,,, 에 저장되게 되고 $1 -> 12 , $2 -> 50 가 들어 가게 됩니다.

 

7)     패턴을 활용한 치환
a = “the quick brown fox”
일 경우

가)    a.sub(/[aeiou]/, ‘*’)  -> th* quick brown fox”

나)    a.gsub(/[aeiou]/, ‘*’)  -> th* q**ck br*wn f*x”

다)    a.sub(/\s\S+/, ‘’) -> the brown fox

라)    a.gsub(/\s\S+/, ‘’) -> the

마)    코드 블록을 이용한 치환 코드블록을 활용해서 매친 된 부분에 대해 원하는 작업을 처리할 수 있다.

a = “the quick brown fox”

a.sub(/^./) { |match| match.upcase } -> The quick brown Fox

a.gsub(/[aeiou]/) { {vowel| vowel.upcase } ->thE qUIck brown fOx

 

5.     코드 블록(Code Block) – 루비의 가장 멋진 기능이 아닌가 싶습니다. 정말 이 기능만큼은 C++ 에 가져가 싶을 정도로 매력적입니다. 코드 블록이란, 함수 호출 시 코드 블록을 넘겨 함수에서 동적으로 원하는 코드와 연동시키는 루비의 가장 아름다운 기능입니다.  일반적으로는 함수를 호출할 때 함수의 인자만 전달하는데요, 루비에서는 인자와 함께 수행할 수 있는 코드를 전달할 수 있습니다. 이렇게 되면, 주어진 함수에서는 자신이 하는 일과, 전달된 코드(코드 블록)에서 구현되는 일까지 할 수 있게 되어 함수의 기능에 대한 무한한 확장이 가능하게 됩니다.

1)     코드 블록의 특징

가)    코드 블록은 함수 호출 뒤에만 나타난다

나)    코드 블록은 나타나자 마자 실행되는 것이 아니라, 블록을 기억하고 있다가 함수가 실행되는 시점에 함수에 있는 yield 호출에 의해서만 실행된다.

다)    중괄호({…… }) 또는 do ~ end 문으로 주어진다.

 

2)     배열 반복자

가)    주요 이점: 배열의 요소에 접근하는 작업은 배열 자신이 담당하고, 배열 항목에 대한 실행부분은 코드 블록을 이용한다.  즉 배열 전체에 대해 비교, 검색, 합계 등을 구하고자 할 때 배열 반복자에 원하는 코드 블록을 넘겨줄 수 있도록 해 준다.

나)    find 반복자 배열 내에 코드 블록과 동일한 항목을 찾는다

다)    each 반복자 배열 내의 element 를 하나씩 yield 문을 통해 코드 블록 실행한다

라)    collect 반복자 배열 항목별로 yield 를 호출하고 기존 배열 값을 리턴 값을 변경한다. (배열의 내용을 변경함)

마)    inject 반복자 코드 블록을 통해 배열의 값을 누적한다.

바)    생각할 거리 – C++ 에 있는 STL 의 경우 컨테이너와 이를 다루는 iterator, 연산을 수행할 알고리즘으로 분리되어 성능과 확장성에 중점을 두고 있는 반면에 루비는 컨테이너인 배열에 자신을 navigate 할 수 있는 iterator 함수들을 모두 제공하고 있습니다.  배열 바깥에서 배열 내부의 코드를 수정하지 않고 배열 내 어떤 작업을 원할 때에는 배열 반복자와 코드 블록을 통해 원하는 작업을 할 수 있도록 하는 방식입니다. 프로그래밍을 해야 하는 입장에서는 역시 루비 같은 방식이 사용하기에는 훨씬 편한 것 같습니다. (성능에 대한 관점을 접어 둔다면)

 

3)     배열, 배열 반복자, 코드 블록을 사용한 예

a = [‘개미’, ‘’, ‘고양이’]
a.each { |animal| puts animal } # a
배열의 내용을 하나씩 꺼내 puts 합니다.

 

#each 함수의 구현은 대략 다음과 같습니다.- 실제 코드는 아니고 이와 비슷하게 코드 블록을 호출(yield)하고 있음을 보여 줌

def each

   for each elmt

      yield(elmt)  # 코드 블록에 있는 내용 호출

   end

end

 

[‘고양이’, ‘’, ‘’].each { |name| print name, “ “}  # 호출결과 -> 고양이 개 말

5.times {print “*” }  #호출 결과 -> *****

3.upto(6) { |i| print i } # 호출 결과 -> 3456

 

4)     트랜잭션을 위한 블록 아래의 예는 파일을 열고 뭔가 처리해야 하는 함수에서 코드 블록을 사용한 경우임. 파일을 열고 나서, yield 문을 통해 코드 블록으로 파일을 전달하면 파일에서 한 라인씩 읽어와 프린트 하는 예제입니다. 코드 블록을 모두 수행 한 경우 f.close()를 호출하여 자동으로 파일을 닫도록 하고 있기 때문에 리소스의 생명주기를 클래스가 직접 관리할 수 있도록 해 주는 아주 유용한 코드 블록 사용 예이기도 합니다. 즉 파일을 열어 뭔가 처리해야 할 때 이를 클래스에서 함수로 제공하고, 파일을 열어 실제 구현해야 하는 내용은 코드 블록으로 만들어 사용하는 방식입니다. 파일 리소스에 대한 접근을 누구보다도 잘 아는 건 클래스 그 자체이기 때문에 클래스에서 파일을 열고, 모든 일이 끝나면 파일을 닫도록 해서 리소스를 잘 관리하도록 해 주고 있습니다.

class File

   def File.open_and_process(*args)

      f = File.open(*args)

      yield f   # 코드 블록 호출

      f.close()

   end

end

 

File.open_and_process(“testfile”, “r”) do | file |

    while line = file.gets

       puts line

    end

end

 

 

6.     루비 문서화 – Rdoc

문서화의 맹점은 프로그램 소스와 문서가 따로 놀기 때문이라고 생각합니다. 프로그램을 유지 보수 하면서 계속해서 달라지는 코드 내용을 기존의 문서에도 반영해야 하는데 그게 말처럼 쉬운 일이 아니어서 문서는 늘 폐기처분 되는 경우가 많습니다. 그래서 요즘에는 RDoc 처럼 소스코드의 주석을 자동으로 문서화시키는 방안들이 많이 나오네요. C++ 에도 있다고 들었는데, 처음에는 익숙해 지기 힘들겠지만, 숙달되다 보면 별도의 문서 작업 없이도 좋은 자료로 쓰일 수 있을 것 같습니다.

 

7.     오리 타입

루비에서는 변수와 함수의 리턴값에 타입이 없습니다. 비주얼베이직의 Variant 처럼 언제나 어떤 타입으로든 변할 수 있는데, C, C++, 자바 등의 언어에 익숙한 저 같은 사람에게는 이렇게 타입을 미리 정하지 않고 그때 그 때 상황에 따라 타입이 결정되는 언어가 받아 들이기 힘든 게 사실입니다. 책에서는 이런 논쟁에 대한 저자의 의견을 개진하는데 저자의 의견은 정적타입이 동적타입보다 안전하거나 더 신뢰할 수 있는 코드를 만들 수 있다는 어떠한 증거도 없으며, 동적타입이 오히려 생산성을 향상시킨다는 반론을 제시합니다. 오리 타입(Duck Typing) 이란 객체의 타입은 객체가 무엇을 할 수 있는 가에 의해 좌우될 뿐 클래스에 종속되지 않음을 의미합니다. 따라서 루비에서는 객체의 타입이 클래스에 의해서가 아니라, 객체가 실제 하는 일에 의해 결정된다고 주장합니다. 저자의 주장이 실제로 맞는지는 아직 루비에 익숙하질 않아서 뭐라 대응할 논리가 없습니다.  루비를 좀 더 사용해 본 다음에 다시 한번 이 주제에 대해 의견을 준비해 보겠습니다.

 

8.     조금 헷갈리는 루비

1)     아래와 같이 루비에서 변수는 값 자체를 가지고 있는 것이 아니라, 값을 가리키는 방식으로 사용됩니다(C 의 포인터처럼).
person2 = person1
이라고 했을 때 값이 복사되는 것이 아니라, 단지 같은 값을 가리키게 되는 식인데요. 만약 값을 새로운 변수에 복사하고자 한다면,
person2 = person1.dup
식으로 사용하면 됩니다. (ATL 에 있는 CComBSTR::Copy() 연산자와 비슷합니다.)

person1 = “Tim”

person2 = person1

person1[0] = ‘J’

person1 -> “Jim”

person2 -> “Jim”

2)     숫자 0 C/C++ 에서 처럼 false 로 해석되지 않는다. 길이가 0 인 문자열도 마찬가지여서 while 등과 같은 조건문 사용에 주의가 필요하다.

 

 

윈도우 개발자로서 이 책의 조금 아쉬운 점이 있다면, 윈도우용 루비 IDE 에 대한 소개나, 루비를 활용한 실용적인 예제가 좀 부족하지 않았나 하는 생각입니다. 문법책에서 이런걸 바라는게 좀 이상한 것 같기도 한데,  도스 command 창에서 한 줄 씩 입력해 가며 실행하는게 익숙치가 않아서 Visual Studio 같이 편리한 IDE 가 있으면 좋겠다는 생각이 드네요. 이클립스 등에서 된다고도 하던데 공부해 볼 예정입니다.

 

댓글