RubyOnRails를 실무에 적용한지 한 달 남짓이 되었다. 써본 감상이 그리 좋지만은 않다. 우선 Ruby라는 언어. 순수 객체지향이면서 깔끔한 문법으로 보기 좋은 코드를 만들어낸다... 이게 Rubyist들의 주장인데 이제 이 주장은 동의할 수 없다. 오히려 Ruby로 만든 코드들을 보면 정말 읽기 난해한 코드가 많다. Rails와 Rails 플러그인의 코드 퀄리티도 만족스럽지 않았다. 오히려 괜찮은 자바 프레임웍의 코드 퀄리티가 더 높다고 느껴질 정도였으니까. 물론 코드 가독성을 결정짓는 가장 큰 요소는 프로그래머의 능력이지 프로그래밍 언어가 아니기 때문에 이런 문제는 Ruby 언어의 책임이 아니라고 할 수도 있다. 하지만 내가 보기엔 루비 코드를 읽기 어렵게 만드는, 그리고 agile language라고 불리면서도 초보자에게 어려운 언어로 남아 있는 이유가 몇 가지 있는 것 같다.
가장 문제가 되는 것은 유의미하게 사용하는 기호가 너무 많다는 것이다. 다른 언어에서는 키워드를 사용하는 것을 기호로 표현하기도 하고 단일 개념으로 쓰는 것을 분리해서 여러 개의 기호로 사용하기도 한다. 이를테면 this.을 @로 쓰고 static 멤버는 @@로, extends 대신 <를, isXXX 대신 XXX?를 사용한다. static 선언은 ClassName::methodName과 같은 형식으로 선언한한다. String이지만 key 역할을 할 때 사용하는 심볼은 :string과 같이 사용한다. 이런 기호들이 하나하나 뜯어보면 같은 내용을 함축적으로 간결하게 표현할 수 있기 때문에 좋은 점이 있고 개념적으로도 좋은 것들이 많지만 이런 것들이 하나 하나 늘어날수록 언어의 복잡성이 증가하고 코드의 많은 부분이 기호로 뒤덮이게 된다. 앞서 말한 것처럼 기호는 간결한 표현과 함축성이 장점이기도 하지만 반대로 기호를 모르는 사람과의 의사소통을 저해하고 학습 장벽으로 높이는 단점이기도 하다. 학습 장벽이 높다는 것은 그것만 넘어서면 편해지는 그런 게 아니다. 문법을 늘 기억하고 있어야만 쓸 수 있다는 것이다. vi 에디터와 같은 단점을 갖고 있는 것이다.
비슷한 역할에 여러 개의 개념을 사용하는 것이 많다는 것도 단점이다. 그 단적인 예가 클로저 역할을 하는 루비 블럭이다. 루비의 가장 큰 장점이라고들 이야기하지만 요즘 클로저 없는 언어도 별로 없는데 클로저 있는 언어 중에서는 제일 좋지 않은 방식으로 구현된 것 같다. 다른 언어들은 메소드나 함수의 문법과 거의 유사하게 구현이 되어 있고 클로저 자체도 객체로 메소드의 인자로 들어갈 수 있는데 루비에서는 인자도 아니면서 루비의 다른 문법들과 비교해도 예외적인 방식으로 전달되고 yield로 호출된다. 순수 OOP라고 주장하는 루비에서 루비의 가장 큰 장점이라는 루비 블럭이 객체가 아닌 것이다. 루비 블럭의 syntax sugar인 {} 때문에 실제로 클로저가 필요한 게 아니라 단순히 함수가 필요한 경우에도 클로저를 많이 쓰는데 그러다보니 이 클로저는 객체가 아닌지라 리팩토링하기가 좀더 어렵고 그래서 많은 루비 코드에 클로저의 중복이 심하게 나타난다.
사실 따지고 보면 이 두 가지 문제는 비슷한 이야기다. 하나하나의 개념은 좋지만 그런 개념들이 지나치게 잘게 나뉘어 있고 개수가 너무 많기 때문에 Ruby는 공식 스펙 문서조차 만들기 힘든 복잡한 언어가 되버린 것이다.
요즘 또 많이 사용하는 언어인 JavaScript와도 여러 면에서 비교가 되는데 내가 보기엔 JavaScript가 문법적으로는 좀더 깔끔하고 명쾌한 것 같다. 그러면서 Ruby에서 할 수 있는 건 거의 다 할 수 있고 개념 자체가 몇 개 안되서 배우기도 정말 쉽다. TDD할 때도 Ruby는 dynamic language치고는 MockObject 만들기가 좀 불편한데 JavaScript는 PrototypeBasedLanguage라서 이런 게 정말 편리하다. 파이썬과 비교해도 파이썬 쪽이 좀더 쉽고 간결한 것 같다. python 코드들은 실제 사용하는 라이브러리 코드를 봐도 정말 잘 만들었다고 감탄한 적이 많은데 루비는 예제로 나오는 코드들만 와~ 하게 만들 뿐, 오픈소스로 퍼져 있는 루비 라이브러리들은 그런 느낌이 드는 경우가 별로 없다.
그래도 루비의 장점을 몇 가지 꼽을 수는 있다. 가장 크게 다가오는 장점은 함수 호출 시 ambiguous하지 않을 때는 괄호를 생략할 수 있다는 것. 이것이 미려한 DSL을 만드는데 결정적인 역할을 하는 것 같다. 모든 메쏘드가 마지막 문장을 return 값으로 갖는다는 룰도 상당히 편리하다. statement 뒤에 if나 unless로 조건을 걸 수 있다는 것도 장점. 파이썬에도 이번에 비슷한 문법이 추가되었는데 괜찮은 것 같다.
루비 이야기만 하다가 길어져서 레일즈 이야기는 다음에...
여기에 대한 반론으로 [http://myruby.net 강모씨]가 보내온 코드. 참고로 [http://myruby.net 강모씨]는 언젠가 "그래, 자바스크립트가 루비보다 문법은 명확하고 쉬워서 좋긴 하지."라는 발언을 한 바가 있다는...
>> def test_method >> yield >> end >> test_method { puts 1 } 1 >> p = lambda { puts 1 } => #<Proc:0x00546dc0@(irb):11> >> test_method &p 1 >> def test_method2 p >> p.call >> end >> test_method2 p 1 >> test_method2 lambda { puts 1 } 1
아마도 블럭도 객체로 사용할 수 있다..라는 반론을 하고자 한 듯. 그러나 여전히 p를 넘겨야 하나 &p를 넘겨야 하나, yield를 해야 하나 p.call을 해야 하나.. 개개의 feature는 그럴 듯하지만 feature의 개수가 쌓이면서 문법의 복잡도가 증가하는 문제는 여전히 남은...
그리고 Symbol과 String에 대해서도 다음과 같은 코드를 보내왔는데...
>> hash = {} => {} >> hash['test'] = 'val' => "val" >> hash[:test] = :val => :val >> hash.inspect => "{:test=>:val, "test"=>"val"}"
오류 정정이라 하는데 무슨 오류인지 설명 안해줘서 잘 모르겠삼. Symbol과 String이 equavalent 하지 않고 다르다는 말을 하고 싶었던 것일까. What I meant is there are two similar ways - but not so different - to do the same thing.