개발자노트2006/01/18 23:15
Things That Newcomers to Ruby Should Know에서 발췌한 내용들이다.


1. ruby를 실행할 때 -w 옵션을 주어 실행하면 실행시 발생하는 경고(warning)메시지를 볼 수 있다. 환경 변수(RUBYOPT)를 설정하여 ruby interpreter에 옵션을 전달할 수 있다.

2. irb라는 ruby interactive shell이 있으므로 디버깅이나 테스트 용으로 사용하면 편리하다.

3. ruby관련 문서는 ri를 이용하면 쉽게 볼 수 있다.
ri File # File object ri IO.open # IO.open method

4. 문서에서 "Klass#method"로 표기된 method는 instance method이고 "Klass.method"로 표기된 것은 class method이다. 이것은 단순히 표기법일 뿐이지 ruby syntax는 아니므로 혼동하면 안된다.

5. String#[Fixnum]은 문자열의 지정된 위치에 있는 ascii값을 반환하지 문자 그 자체를 반환하지 않는다. 예를 들어 "hello"의 두번째 문자인 "e"를 출력하기 위해 "hello"[1]과 같은 ruby expression은 "e"를 반환하지 않고 "e"의 ascii값인 101을 반환하게 된다. ascii값이 아닌 문자 자체를 반환 받고 싶다면 위치 인덱스 뿐 아니라 길이도 함께 주어야한다. 또는 ascii값을 문자로 바꾸는 Fixnum#chr 을 이용해도 된다.

  • "hello"[1] ==> 101

  • "hello"[1, 1] ==> "e"

  • "hello"[1].chr ==> "e"

  • ?e ==> 101 # 해당 문자의 ascii값을 구함.


6. Array.new(2, Hash.new)는 두개의 동일한 Hash object에 대한 reference를 두개 가지고 있는 배열을 만든다. 서로 다른 Hash object를 포함하고 있는 배열을 만들고 싶다면 map이나 collect method를 사용하면 된다.
arr = (1..2).map {Hash.new}

7. mutable(내용이 변할 수 있는) 객체를 해쉬의 키로 사용했다면 키가 변할 경우 해쉬 키의 인덱스를 재배치해야만 바뀐 키로 데이터를 찾을 수 있다.
s = "mutable"
arr = [s]
hsh = { arr => "object" }
s.upcase!
p hsh[arr] # nil
hsh.rehash
p hsh[arr] # "object"

8. file에서 데이터를 읽어서 숫자로 처리를 하려면 .to_i, .to_f method로 정수 또는 실수로 변환을 해야한다.

9. ruby에는 ++, --와 같은 increment, decrement operator가 없다. i++과 같은 것은 parse error가 발생하고 ++i와 같은 것은 무의미한 operation이다. ++ 두개는 양수에 양수라는 의미이기 때문이다. 마찬가지로 --i 역시 음수의 음수는 다시 양수 이므로 그냥 i가 된다. 증가, 감소를 하려면 i += 1, i -= 1을 이용하면 된다.

10. local변수와 블럭 내에서의 local변수가 같이 사용될 때 주의해야한다. 블럭 내의 파라메터로 전달되는 local변수와 같은 이름의 local변수가 블럭이 선언되기 전에 사용되었고 이름이 같다면 블럭 내에 전달된 local변수는 바깥 쪽에 선언된 local변수를 override하게 된다.
11. ruby에서는 logical operator가 동일한 두 개의 세트가 존재한다. [!, &&, ||]는 [not, and, or]와 같다. 주의할 점은 연산자 우선 순위인데 [!, &&, ||]는 [=, %=, ~=, /=,...)등보다 우선 순위가 높은 반면 [not, and, or]는 우선 순위가 낮다.
a = 'test'
b = nil
both = a && b # both == nil ----> both = (a && b)
both = a and b # both == 'test' ---> (both = a) and b
both = (a and b) # both == nil

12. 다음과 같은 문장이 있을 때
case obj
when obj_1
when obj_k

여기에서 비교를 위해서 === operator를 사용하게 된다. 이 경우 비교 순서는 obj_1 === obj 또는 obj_k === obj가 된다. obj === obj_1과 같은 순서로 비교 되지 않는다. 비교 연산자의 순서가 중요한 이유는 ===가 operator가 아니고 method이기 때문이고 객체에 따라서 다르게 작동할 수 있기 때문이다. obj_1 === obj 는 obj_1.===(obj) 와 같다. 즉 ===는 operator가 아니고 method이름이다. Module이나 Class에 정의된 ===는 비교 대상이 되는 객체가 같은 종류의 클래스 또는 상위 클래스 인지를 비교한다. Regexp에서 ===는 =~ 와 같다. Range에서의 ===는 범위 내에 포함이 되었는지를 알려준다.

13. method호출 시 ( 앞에 white space를 주지 않아야 한다. $VERBOSE == true인 경우에 경고 메시지가 나온다.

14. .(dot)은 가장 우선 순위가 높은 operator이다. 그래서 1.e6은 Fixnum타입인 1에 e6라는 method를 실행하는 것과 같다. 원하는 결과를 얻으려면 1.0e6와 같이 해야한다

15. "o..k"는 Range객체를 나타내지만 "[o..k]"은 Array객체를 나타낸다.

16. Ruby에서는 오직 nil과 false만이 false로 간주된다. 0 또는 "", '', [], {} 등은 모두 true이다.

17. Ruby에서 = operator는 객체에 대한 reference를 복사하는 것이다. a += b는 실제로 a = a + b로 간주되어 처리된다. (a += b는 a의 값에 b를 직접 더하는 것처럼 보이지만 실제로는 따로 계산을 한 뒤 a에 재할당을 한다는 것임) 이와 같이 ruby에서는 어떤 operation이 객체를 직접 변화시키는지 변화시키지 않는지 주의해야한다. (mutable인지 immutable인지 주의해야한다는 얘기임. ruby에서는 method이름 뒤에 !가 붙는 것들이 있는데 이 convention으로 mutable operation인지 immutable opeation인지 알 수 있다. !가 붙으면 mutable operation)
예를 들어 String객체에 다른 문자열을 더하는 operation으로 '< <' 와 '+='가 있다. 속도의 차이로 본다면 '<<'쪽이 '+='보다 빠르다. 왜냐하면 '<<'는 원본 문자열에 직접 다른 문자열을 더하는 것이고 '+='는 숫자 연산에서와 마찬가지로 '+'로 문자열을 더한 후 새로 생성된 문자열을 다시 할당하는 것이기 때문이다.

18. Ruby에서는 객체를 deep copy하는 방법이 없다. Deep copy를 하고 싶다면 marshaling/unmarshaling을 이용하면 된다.

19. Class변수는 클래스 계층을 따라 공유 된다. Child class에게도 공유가 되고 instance화된 객체들에게도 모두 공유가 된다. 하지만 하나의 예외가 있는데 class변수가 하위 class에 의해 초기화가 된 경우에는 공유되지 않고 별도의 변수처럼 작동하게 된다.
class Base
def initialize; @@var = 'base'; end
def base_set_var; @@var = 'base'; end
def base_print_var; puts @@var; end
endclass Derived < Base
def initialize; super; @@var = 'derived'; end # notice
def derived_set_var; @@var = 'derived'; end
def derived_print_var; puts @@var; end
end

d = Derived.new
d.base_set_var; d.derived_print_var # -> 'base'
d.base_print_var # -> 'base'
d.derived_set_var; d.derived_print_var # -> 'derived'
d.base_print_var # -> 'derived'

위의 소스를 보면 notice라고 된 부분에서 super를 호출하여 상위 클래스의 constructor가 먼저 실행되도록 하였다. 하지만 아래 소스를 보면 super를 호출하기 전에 @@var를 먼저 초기화를 했다. 출력되는 결과를 보면 알겠지만 아래와 같이 하위 클래스에서 먼저 클래스변수를 초기화한 경우에는 클래스 변수가 공유되지 않고 별도의 독립적인 영역을 가지게 된다.

class Base
def initialize;
@@var = 'base';
end
def base_set_var;
@@var = 'base';
end
def base_print_var;
puts @@var;
end
end

class Derived < Base
def initialize; @@var = 'derived'; super; end # changed
def derived_set_var; @@var = 'derived'; end
def derived_print_var; puts @@var; end
end

d = Derived.new
d.base_set_var; d.derived_print_var # -> 'derived'
d.base_print_var # -> 'base'
d.derived_set_var; d.derived_print_var # -> 'derived'
d.base_print_var # -> 'base'

20. \(backslash) 치환에 주의
str = 'a\b\c' # -> a\b\c
puts str.gsub(/\\/,'\\\\') # -> a\b\c
puts str.gsub(/\\/,'\\\\\\') # -> a\\b\\c
puts str.gsub(/\\/,'\\\\\\\\') # -> a\\b\\c
puts str.gsub(/\\/) { '\\\\' } # -> a\\b\\c
puts str.gsub(/\\/, '\&\&') # -> a\\b\\c

Posted by 앤디군
개발자노트2005/10/13 10:18
ruby가 깔끔하고 타 언어의 좋은 장점들만을 모아서 만들었다는 철학에 걸맞게 좋은 인상을 주고 있다. 하지만 다만 한가지 걸리는 것은 웹 개발시 production level에서도 사용할 수 있을 만한 성능을 내줄 수 있느냐 하는 것이다.
우선은 소프트웨어의 구조를 잘 정돈하고 profiling을 통해서 성능 개선을 하면 어느 정도는 만족하지 않을까....

ruby interpreter에는 profiler가 내장되어 있다. profiler library를 포함시키는 것 만으로 전체 프로그램 실행 결과에 대한 profiling 결과를 얻을 수 있는데 -r profiler 또는 source code에 require "profiler"를 추가하면 된다.

개인적으로는 소스를 고치지 않고 -r profiler 옵션을 붙여서 실행하는 것을 선호한다. 아무튼 profiling을 한 결과는 프로그램 실행 후 다음과 같이 출력이 된다.


% cumulative self self total
time seconds seconds calls ms/call ms/call name
38.18 1.26 1.26 1145 1.10 1.49 KeywordExtractor#goto_s
12.73 1.68 0.42 68 6.18 113.38 Array#each
12.42 2.09 0.41 2946 0.14 0.16 Hash#[]
4.24 2.23 0.14 1780 0.08 0.08 Fixnum#==
3.33 2.34 0.11 1641 0.07 0.07 NilClass#nil?


다른 profiler와 비슷한 출력 결과를 보여주는데 java의 profiler와 같이 thread stack도 같이 보여주면 좋을 것 같은데 아쉽게 그렇게 그 정도까지 보여주진 않는다. 하지만 이런 정보를 -r profiler 옵션 하나로 쉽게 볼 수 있다는게 어딘가..
Posted by 앤디군
TAG 루비, 성능
개발자노트2005/09/04 09:51
A morality tale of Perl versus Python - SouNerd.com

Perl과 Python을 스타워즈의 스토리를 이용하여 비교한 재밌는 글이다.

루크는 요다를 등에 묶어 업고 Dagobah통계연구소를 향해 늪지대의 수많은 두터운 덩굴들을 기어 올라간다. 숨이 차도록 그는 grepping(UNIX grep command를 이용해서 문자를 찾는 것을 말하는 것 같음), 새로운 패키지 설치하기, root로 로그인하기, 2년된 오래된 shell script를 python으로 만들기 등의 연습을 계속 하고 있다.

요다(Yoda): 코드(code)!!, 그래 프로그래머의 힘, 강력함은 코드를 얼마나 유지보수가 쉽도록 만드느냐에서 나오지. 하지만 Perl을 주의하게. 간결한 문법... 여러가지 문제 해결 방법.. 디폴트 변수들... 코드의 유지보수성에 있어서 어둠의 측면(The dark side of code maintainability)는 바로 그것들이네..코드를 작성할 때 그러한 것들에게 물를기 쉽다네. 어둠에 길로 한번 들어서면 너의 운명은 영원히 지배당할 것이야.
루크(Luke): Perl이 Python보다 좋습니까?
요다: 아니.. 하지만 빠르고 쉽고, 게다가 매력적이지..
루크: 그러면 어떻게 Python이 Perl보다 좋다는 것을 알 수 있습니까?
요다: 곧 알게 될꺼야. 네가 만든 코드를 읽는데 6개월이 걸린다면.....


Perl과 다른 스크립트 언어들을 비교할 때 거의 항상 나오는 얘기지만 그 때마다 Perl의 단점으로 지적되는 것이 readibility다. 읽기 어렵고 유지보수가 어렵다는 것이 그 이유이고 그것은 사실이지만 그 문제는 프로그래머가 만드는 것이지 Perl 자체가 만드는 것은 아니다. 다만 Perl은 그 문제가 발생될 여지를 주었다는 게 죄다. 비유하자면 무법지대라고나 할까..
무법지대가 꼭 나쁜 것인가? 그건 아닌 것 같다. 어렸을 때 도덕책에서 본 듯한 기억이 있는데, 공자가 그랬나?.. 암튼 "마음이 가는데로 행하여도 법도에 어긋남이 없는 경지"가 있다고 그랬는데 이것을 프로그래밍에 비유하자면 "하고 싶은데로 코딩을 하여도 코딩 컨벤션에 어긋나지 않는다." 인것 같다. 즉, 문제는 환경이 아니라 주어진 환경에서 타인들과의 약속(법, 코딩 컨벤션)을 얼마나 잘 지키냐의 문제인 것이다. Perl뿐만이 아니라 다른 언어에도 코딩 컨벤션이 있지만 얼마나 잘 지켜지고 있을까? SUN JDK 라이브러리 소스 조차 엉망인데...
언어의 표현의 자유도에서 발생된 문제를 탓할게 아니라 그 표현의 자유도를 이용하는 사람들의 잘못된 습관, 타인과의 약속(코딩 컨벤션)을 지키지 않는 습관을 탓해야한다. Perl의 잘못은 약속을 지키지 않았을 때 떨어질 수 있는 나락의 깊이를 너무 깊게 해줬다는 것...
하지만 Perl의 문제는 표현의 약속(코딩 컨벤션)으로만 발생되는 것인가? 그건 아니다. more than one way to do it 이 말해주고 있듯이 하나의 로직을 표현하는데에도 여러가지 방법을 제시해주고 있다. 더군다나 암묵적인 디폴트 변수 $_, @_ .. 이건 도대체 눈을 똑바로 뜨고 쳐다보고 있지 않으면 언제 어디서 값이 바뀔지 모르고 어디로 넘어갈지 모른다. 하지만 이것도 자유도의 문제이듯 디폴트 변수를 되도록 안 쓰면 된다. 하지만 디폴트 변수가 익숙해지면 편하기 때문에 그 어둠의 포스에 빠져들기 쉽긴 하지만...
뭐 암튼... Perl의 자유분방함이 낳은 무질서가 Perl을 역으로 압박하고 있는 것 같긴 하지만 무질서를 만들어 낸 것은 프로그래머들일 뿐.. 그것 자체로 프로그래밍 언어의 좋고 나쁨을 결정할 것은 아닌것 같다. 정말 좋은 언어란... 개인적인 기준이지 공통으로 적용될 기준도 아닌 것 같고..
Posted by 앤디군