Page history of 스프링노트_기술보고서



Title: 스프링노트_기술보고서 | edited by Youngrok Pak at 11 years, 1 month ago.

 

스프링노트를 오픈하고 나서 그 동안 사용했던, 그리고 경험했던 기술들을 하나씩 평가해 본다.

Ruby on Rails

ActiveRecord

RoR의 가장 큰 장점은 ORM인 것 같다. 현재 Rails류의 프레임웍이 많지만 완성도 면에서 RoR의 ActiveRecord만큼 높은 건 별로 많지 않다. 하지만 디자인 측면에선 맘에 안드는 부분도 있다. 순수 OOP적인 모델 설계를 지향하고 있는 SQLObject나 Hibernate, GORM에 비해 ActiveRecord는 테이블을 기술하는 방식에서 SQL에 더 가깝다. 코드로 한 번 비교해보자. 다음은 Rails에서 User라는 모델이 name과 password라는 프로퍼티를 가지는 경우이다.

  class AddANewTable < ActiveRecord::Migration
    def self.up
      create_table :users do |table|
        table.column :name,  :string, :null => false
        table.column :password, :string, :limit => 32, :null => false
      end
    end

    def self.down
      drop_table :users
    end
  end

이렇게 migrate를 만든다. 그리고 migrate를 실행하면 name, password라는 컬럼이 있는 users 테이블을 만들고 이 테이블을 ActiveRecordPattern으로 User 객체와 매핑시킨다. User 객체는 그냥 다음처럼 쓰기만 하면 된다.

class User < ActiveRecord::Base
end

user = User.new
puts user.name
user.password = 'xxx'

즉, 메타 정보를 담는 migrate 코드와 User 클래스 코드가 합쳐져야 하나의 모델이 되는 것이다. 게다가 메타 정보를 기술하는 방식은 SQL에 아주 가까운 방식이다. 그러면서 User의 메소드는 또 User class에 기술한다. 그래서 User class를 봐도 무슨 프로퍼티가 있는지 알기가 어렵다. SQLObject랑 비교하면 이 차이는 더 드러난다.

class User(SQLObject):
    name = StringCol(length=100)
    password = PasswordCol(default='')

user = User('a')
print user.name
user.password = 'xxx'

User 클래스 하나에서 모든 정의를 한다. 그래서 조금 더 응집성이 높고 간결하다. GORM도 이런 방향을 지향하고 있다. Django도 DRY 원칙이나 OnceAndOnlyOnce의 입장에서 이런 방식을 채용했다.

하지만 Real World에서는 꼭 SQLObject 쪽이 좋다고 할 수 없다. SQLObject도, Hibernate도 지원이 부실한 부분이 바로 스키마의 migration이다. Rails는 앞서의 migrate 코드를 실행하면 self.up 코드를 실행하고 또 migrate back을 하면 self.down을 실행한다. 그래서 스키마 변경 코드도 migration으로 만들 수 있다. 이를테면 위의 예에서 User에 email 필드를 추가한다고 해보자. Rails는 다음과 같은 migration 코드를 만들고 rake db:migrate 명령을 내린다.

  class AddANewTable < ActiveRecord::Migration
    def self.up
      alter_table :users do |table|
        add_column :email,  :string, :null => false
      end
    end

    def self.down
      alter_table :users do |table|
        remove_column :email
      end
    end
  end

그러면 self.up 부분이 실행되서 스키마를 바꾼다. migrate는 역도 가능하다. 이게 꼭 매끄럽게 되는 건 아니지만 역 migrate도 가끔 필요하다. 그래서 이런 시점에 migrate에 필요한 작업들을 해줄 수 있다. 하지만 SQLObject나 GORM 등에서는 그냥 클래스 정의만 바꾸면 이게 테이블에 자동으로 반영된다. 이 자동이란 게 편하기도 하지만 가끔 데이터를 날려 먹는 수가 있다. 그래서 이런 건 조금 더 SQL에 가깝고 수동으로 할 여지를 많이 열어 놓은 Rails가 유리한 점이 있다.

하지만 어차피 migration 코드에 model 객체를 이용한 코드를 집어 넣으면 migration이 가끔 안되는 수가 있다. 이론적으로는 migration 파일이 1부터 10까지 있으면 1부터 10까지 쭉 실행하면 데이터가 다 자동으로 변환되면서 스키마가 변경되어야 하지만 1단계에는 있는 모델 클래스가 5단계에는 없을 수 있고 또 그 반대도 있기 때문에 모델이 들어간 migration 코드는 종종 class not found 에러를 낸다. 그래서 어차피 migration은 sql을 이용해서 하거나 수동으로 script/console을 열고 하는 경우가 많다. 팀에서 한 사람이 쭉 migrate를 쌓아 놓은 것을 다른 개발자들이 제때 제때 따라가지 못하면 migrate를 할 수 없어서 개발 DB를 다른 사람 껄 덤프 떠와야 하는 일도 간혹 있다. 이를 대비해서 schema:load 같은 명령도 있지만 잘 안된다. 이런 식이라면 SQLObject도 충분히 할 수 있기 때문에 큰 차이는 아닐 수 있다. 어쨋건 Rails의 ActiveRecord가 나름 실용성과 디자인의 중간을 찾으려고 애를 썼고 두 마리 토끼를 절반씩은 잡았다는 점은 Rails의 중요한 장점이다. 사실 익숙해지면 ActiveRecord의 응집성 문제는 그다지 annoying하지 않다.

성능은 조금 문제가 있다. Django 같은 경우는 QuerySet을 iterate할 때 쿼리가 날아가기 때문에 그 앞에서는 안심하고 여러 가지 조합을 해줄 수 있는데 Rails는 좀더 많은 종류의 코드들이 직접 쿼리로 날아간다. 그래서 모델 4, 5개 정도 사용하는 페이지에서 무려 80여개의 쿼리가 날아가기도 한다. 결국 다 만들어 놓고 캐싱하고 최적화 하는데만도 엄청난 시간을 투자했으니 ORM으로 인해 초반에 얻은 장점을 후반 가서 다 까먹는다고도 할 수 있을 것 같다. 하지만 이부분은 꾸준히 개선되고 있는 듯하니 지켜볼 일이다.

RHTML

JSP랑 똑같다. 스크립트렛 문법은 거의 완전 똑같다. JSP의 모든 장점과 단점을 다 그대로 계승하고 있다고 보면 된다. 한 가지 짚고 싶은 것은 RHTML식 태그 라이브러리(?). JSP에서 태그 라이브러리로 하는 것들을 RHTML은 모두 ruby 코드를 그대로 노출시켜서 하고 있다. 이걸 helper 메소드라고 부른다. 태그가 코드보다 낫다는 말을 하려는 것은 결코 아니다. 오히려 이 점은 RHTML의 장점이다. JSP에서 태그 만들려면 해야 되는 삽질이 좀 된다. JSP 2.0에서 좀 나아졌다고는 해도 여전히 Rails에 비하면 불편하다. Django에서 사용하는 템플릿도 태그를 따로 만들려면 일반 파이썬 메소드가 아니라 태그 인터페이스에 맞춰야 하기 때문에 불편한데 RHTML은 그대로 ruby 코드로 모든 것을 할 수 있어서 좋다. 물론 이걸 단점이라고 말하는 사람도 많지만 JSP의 태그 라이브러리가 스크립트렛보다 전혀 낫다고 생각하지 않는 나로선 이건 분명 장점이다. 문자열 길이 제한하는 코드 좀 써넣자고 태그 라이브러리를 만들어야 하는 것보다는 간단히 [..50] 같은 걸로 쓰는 게 훨씬 가독성도 높고 편리하다.

그렇지만 Rails 태그 라이브러리들 자체는 별로 옹호하고 싶지 않다. JSP 태그 라이브러리들의 단점 중 많은 부분을 그대로 계승하고 있기 때문이다. JSP에서 가장 싫은 부분이 form을 좀 편하게 써보자고 다 태그 라이브러로 만드는 것이다. 하지만 그래서 편해지기는 커녕 호환성 떨어지는 코드가 되고 form 태그와 많은 면에서 중복이 된다. 그래서 임의의 속성 달기도 어렵고 HTML만 아는 UI 개발자랑 협업하기도 어렵다. 둘다 도대체 form을 왜 그렇게 괴롭히는지 모르겠다. 여기서 Rails가 좀더 나쁜 부분은 JavaScript까지 헬퍼 메소드로 집어 넣고 있다는 것이다. 이걸 두고 루비 개발자들은 나대지 않는 자바스크립트니 어쩌니 하면서 좋아하지만 이것 역시 UI 개발자와의 협업을 어렵게 만들고 지저분한 HTML 소스 코드를 생성할 뿐더러 자유도까지 왕창 떨어뜨린다. 제발 HTML에서는 루비 코드가 나대지 말았으면 하는 게 내 소망이다. 루비가 DSL을 지향하는 언어라면 HTML 속에서는 HTML스러운 코드를 써야 한다. 근데 HTML 안에서도 루비스러운 코드를 써보자고 온갖 문제를 만들어내는 헬퍼를 남발하고 있다. 그래서 실제로 RHTML 코드들을 보면 몇 년 전 사람들이 JSP 처음 쓸 때 이상으로 지저분하다.

템플릿 재활용에 관한 부분은 좋은 편이다. Tiles나 SiteMesh 같은 자바 쪽 템플릿에 비해 구조화하기가 훨씬 수월하다. 자바는 반성하라! header와 body를 따로 넣을 수도 있고 부분만 분리하거나 상속 비슷한 개념을 활용할 수도 있다. 하지만 파이썬의 cheetah에 비하면 조금 부족한 느낌이다.

webrick & mongrel

Rails가 자바보다 나은 건 rails만 그냥 설치하면 자바처럼 톰캣 깔고 설정하고 할 필요 없이 바로 script/server로 띄워볼 수 있다는 것이다. 하지만 이것도 cherrypy 같은 프레임웍에서 훨씬 일찍 보여준 것이고 cherrypy는 그냥 그대로 띄워도 production scale이 나오는데 webrick은 절대 production에서 쓸 수 없다. 그래서 webrick에서 속도에 민감한 부분을 C로 바꾼 mongrel을 쓴다. 하지만 이것도 성능은 그닥 좋지 않아 보인다. 그리고 루비는 자체 쓰레드 모델이 없기 때문에 실제 서비스에서는 직접 프로세스를 여러 개 띄워야 한다. 그러다보니 자바의 서블릿 컨텍스트 같은 개념도 없고 start/stop도 실제 서비스에서는 간단치 않다. apache랑 연동하지 않으면 서비스할 수 없는 것은 물론이고. 차라리 mod_ruby 쪽으로 최적화시키는 게 성능 면에서나 관리 면에서나 더 낫지 않을까 싶다.

어쨋든 현재 스프링노트에는 초당 10~15 request가 들어오는데 CPU 사용률이 30~70%를 오락가락한다. tomcat에서 웬만큼 무거운 SQL과 로직을 돌려도 초당 40 request에 CPU 20%도 안 먹었었던 걸 생각하면 Rails does not scale이라고 해도 할 말 없을 것 같다. 물론 앞서 언급한 것처럼 ORM의 성능 문제가 더 크긴 하지만 Rails 자체도 우분투에서 제 성능이 안 나오는 등 아직 문제가 많다.

RSpec on Rails

RSpec은 BehaviorDrivenDesign의 생각에 따른 새로운 UnitTestFramework이다. BDD는 TDD와 같은 개념이지만 용어들을 좀더 인간적으로 말이 되게 하는데 초점을 맞췄다. 코드로 비교하면 이렇다. (이건 비교를 위한 샘플이고 실제로 이런 테스트를 만들진 않는다.)

  • TDD
    class UserTest < Test::Unit::TestCase
      def test_create
        user = User.create(:name => 'Karl')
        assertEquals user.name, 'Karl'
      end
    end
  • BDD
    context "user's behavior" do
      specify "should be invalid without a username" do
        user = User.create(:name => 'Karl')
        user.name.should_be_eql 'Karl'
      end
    end

용어들이 조금씩 다르다. 보다시피 rspec이 좀더 자유롭게 spec을 서술할 수 있고 테스트 코드도 뭔가 좀더 말이 되는 것 같아 보인다. 이외에도 user.should_be_valid 같은 메쏘드를 실행하면 user.valid? 메소드를 실행해서 그 결과로 assert를 해 주는 등의 장점이 좀 있다.

그러나, 사실 큰 차이는 아닌 것 같다. 그리고 TDD에 익숙해진 사람들에게 조금 거슬리게 다가오는 부분이 하나 있다. 보통 TDD에서는 assert를 할 때 expected를 먼저 쓰고 result를 뒤에 쓴다. 하지만 BDD에서는 result.should_be expected와 같은 형식이 된다. 호불호의 문제일 수도 있겠지만 나에겐 전자가 좀더 명확해 보인다. 괜히 메이저도 아닌 언어가 두 개의 테스팅 프레임웍을 가진다는 게 그닥 좋아보이지 않는다.

사실 더 큰 문제는 RSpec on Rails에 있다. 이건 RSpec에서 오는 문제보다 Rails에서 오는 문제가 더 크다. RSpec on Rails의 테스트 코드들을 보면 다음과 아주 유사한 코드가 아주 많다.

fixtures :users
controller :sessions

specify ... do
  get :login, id=>@first_user.id, :passwd => 'xxx'

  @first_user.should_be_logged_in
end

얼핏 보기엔 아주 깔끔하고 좋은 코드 같아 보인다. 근데 문제는 저 코드를 실행하면 실제 Rails의 Controller가 내부적으로 떠서 동작한다는 것이다. Java의 Cactus처럼 100% 다 뜨는 것은 아니지만 핵심 코드들은 다 로딩되기 때문에 엄청나게 느리다. 느린 것도 문제거니와 테스트 해야 하는 것은 controller의 로직인데 저 코드는 일종의 FunctionalTest처럼 되서 안에서 모델 사용하는 로직이 있으면 실제로 DB도 다 갔다 온다. 위에서 선언된 fixtures를 통해서 테스트에 필요한 데이터들을 미리 DB로 로드하고 테스트에서는 그 데이터에 실제로 쿼리를 날리는 것이다. 그리고 그 결과로 assert를 한다. 그래서 Test의 granuality가 좀 어중간하다. 아예 FunctionalTest라면 FIT 등으로 쉽게 만들 수도 있는 부분인데 나름 UnitTest 프레임웍이라서 그런 지원이 없다보니 위와 같은 코드가 주욱 늘어서는 중복 아닌 중복이 발생하게 되는 것이다. UnitTest의 입장에서도 테스트하려는 부분만 떼서 테스트할 수 없기 때문에 TDD의 가장 큰 효과, 좋은 디자인을 만들도록 유도하는 효과가 적다. 결과적으로 spec 하나만 놓고 보면 깔끔한 코드지만 spec들이 주욱 늘어서면서 중복이 되고 또 정작 실제 코드의 디자인에는 긍정적인 영향을 주지 못하는 것이다.

테스트 실행이 느린 것도 문제다. Java에서 보통 프레임웍의 Action 테스트 100개 정도 돌리면 거의 10초도 안 걸리는데 Rails의 controller 테스트를 돌리면 듀얼코어에서도 100개에 4,5분은 족히 걸리는 듯하다. 컨테이너 띄우고 DB까지 다 갔다 오니 그럴 수 밖에 없다. 그나마 TextMate에서는 spec 하나 단위로 실행할 수 있고 이상하게도 Mac에서는 상당히 빠르지만 다른 툴에서는 테스트 돌리기가 꺼려질 정도이다.

이 문제의 원죄는 RSpec on Rails가 아니라 Rails 자체에 있다. 일반적으로 Java나 Python 프레임웍에서 controller를 테스트할 땐 그냥 new로 객체 생성하고 메쏘드 실행하면서 mock object만 약간 넣어주면 땡이다. 대부분의 경우 mock object조차도 필요 없다. 하지만 Rails는 controller 코드들이 컨테이너 없이 동작하게 만들려면 상당히 많은 수고를 해야 한다. 그래서 위에서 보는 것처럼 get, post 같은 걸로 쉽게 요청을 때릴 수 있는 툴들을 제공하는 것이다. 하지만 그보다 좀더 가볍게 controller를 mock으로 만들 수 있게 했다면 controller의 디자인 자체도 더 좋아졌을 것이고 테스트하기도 더 쉬웠을 것이다. 테스트하기 좋은 코드가 좋은 코드라는 말이 이럴 때 보면 정말 맞는 것 같다.

이 문제는 controller만의 문제가 아니다. Rails의 모든 컴포넌트들이 다 독립적으로 테스트하기가 어렵다. 컨테이너가 뜨지 않으면 제대로 테스트할 수 있는 건 lib 정도일까. 머, 어쨋든 해결 불가능한 문제는 아니다. mock object를 미리 좀 만들어두고 재사용하면 그나마 편하게 테스트할 수 있다. 사실 그보다 더 큰 문제는 Rails 커뮤니티에서 위와 같은 방식을 권장하고 있을 뿐더러 이런 문제를 개선하려면 Rails 자체에 많은 변화가 가해져야 한다는 것이다. Convention over Configuration의 약점은 이럴 때 드러난다. Rails 전반에 걸쳐 너무 많은 Convention을 강요하고 있기 때문에 작은 변화 하나 일으키기도 쉽지 않다.

루비라는 언어

난 루비가 The Principle of least surprise에 의거해서 디자인되었다는 것에 절대 공감할 수 없다. 오히려 내가 아는 언어 중 가장 surprise가 많은 언어가 루비다. Functional Language에 대한 개념이 전혀 없던 상태에서 Haskell을 써봤을 때도 이렇지는 않았다. 루비스트들은 이렇게 하면 될 것 같다라는 추측으로 코드를 쓰면 되는 경우가 많아서 좋다고 이야기하는데 난 오히려 그게 불만이다. 이렇게 하면 될 것 같다라고 생각해서 코딩했는데 안 되는 경우도 적지 않기 때문이다. 또, 이렇게 하면 안될 것 같은데...라는 느낌으로 썼는데 되는 경우도 있고 코드를 보면서 이런 것도 된단 말야하고 놀랄 때도 있다. 다른 언어는 그렇지 않다. 되는 코드와 안 되는 코드는 한 번에 알 수 있다. 이런 모호함이 난 좀 싫었다.

또 순수 OOP 언어라고 주장하지만 실제로 그렇지 않은 것도 문제다. 실제로 루비에서 가장 중요하다는 루비 블럭은 객체가 아니다. 메쏘드도 객체가 아니고 함수도 객체가 아니다. 때문에 python이나 javascript류의 언어에 비해 mock을 만드는 것이 상당히 귀찮은 편이다. 사실 순수 OOP라는 것 자체가 별로 가치 있다고는 생각하지 않지만 루비는 여러가지 개념들로 OOP를 어지럽히고 있으면서도 순수 OOP라고 주장하는 것이 좀 거슬린다.

그럼에도 불구하고 루비는 매력이 있다. 내가 느끼는 가장 큰 매력은 괄호를 적게 쓸 수 있다는 것이다. 그래서 마치 원래 그런 문법이 있는 것처럼 DSL을 상당히 높은 자유도로 만들 수 있다. 루비 블럭도 그런 DSL을 만드는데 많은 도움을 준다. 블럭이 단점도 많지만 그래도 간결한 코드를 만드는데 도움이 되는 것은 사실이다. 잘 짠 파이썬 코드는 눈에 구조가 확 들어오면서 이해가 빠른 장점이 있다면 잘 짠 루비 코드는 술술 읽으면 말이 되고 보기에 미려하다는 장점이 있다.

하지만 나에게 또 루비로 프로젝트를 하고 싶냐고 묻는다면 대답은 No다. 난 아름다운 코드보다는 읽기 쉬운 코드가 좋다. 루비스트들은 그 아름다운 코드란 것에 많이 끌려서 루비를 선택하지만 실질적으로 많은 가치를 주는 것은 아닌 것 같다. 오히려 루비를 하면서 smalltalk를 깊이 배워보고 싶다는 생각이 든달까.

Third party

루비의 또 하나의 약점은 Third Party Library의 완성도가 낮다는 것이다. 웬만한 게 다 있긴 하지만 완성도가 떨어지는 게 많다. 그러다보니 다른 언어 라이브러리에 바인딩해서 쓰는 경우가 많고 또, 그러다보니 개발 과정에 설치해야 할 것이 많아서 여러 개발자가 같이 개발할 때 어려움이 좀 있다. 특히, 중요한 라이브러리들이 *Nix 플랫폼 기준으로 개발되어 있어서 윈도우에서 개발할 때는 에로사항이 꽃핀다. jar만 떨구면 되는 Java와 비교할 수는 없다 해도 같은 계열인 파이썬이나 펄에 비해 플랫폼 독립성이 많이 떨어진다.

Eclipse vs TextMate

루비 개발자들이 가장 선호하는 개발툴은 Mac 위에서 돌아가는 TextMate다. 근데 이넘을 뜯어보면 그닥 유쾌한 툴이 아니다. Eclipse 같은 경우는 컨텍스트 메뉴가 강력해서 단축키를 몰라도 오른쪽 버튼 눌러서 기능 쓰면서 단축키 하나하나 익힐 수 있다. 하지만 TextMate는 단축키 모르면 전체 메뉴를 헤매면서 기능을 찾아서 써야 한다. 단축키 매핑도 Mac이라 그런지 일반적인 에디터의 단축키와 너무 다르다. SCM 지원도 빈약하고 프로젝트 관리도 부실하다. 좋은 점은 Rails에 특화된 기능이 많다는 것. RSpec이나 Rails랑 바로 붙는 기능들이 많아서 Rails 개발할 때는 은근히 편리한 점이 많다. 하지만 좀처럼 익숙해지기 어려운 툴이라는 점에서 vi와 같은 한계를 갖고 있다.

이에 비해 Eclipse는 안정적인 플랫폼에 꽤 괜찮은 루비 지원을 해주지만 몇 가지 아쉬운 점이 있다. 하나는 spec 하나 단위로 RSpec을 돌릴 수 없다는 것. 그리고 pydev에 비하면 코드 어시스트가 없어서 모든 것을 외워서 써야 한다는 것. 어쨋든 난 Mac도 없고 Eclipse 밖에 대안이 없어서 쓰고 있는데 TextMate보다는 좀더 편한 것 같다.

bash, ant, perl vs rake

개발에서 또 하나 중요한 것이 빌드, 배치 등의 사이클을 관리하는 스크립트다. 루비에선 이걸 rake로 하고 있는데 나름 ant와 make의 장점을 흡수하려 애쓴 것 같긴 하지만 결과적으로는 그냥 another ant 정도인 것 같다. XML이라는 ant의 불편함을 제거하고 보다 가독성 높고 확장성 높은 루비 언어로 할 수 있다는 것은 좋으나 여전히 루비 코드들에서 공통적으로 나타나는 프레임웍성의 구조가 마음에 안든다. 차라리 groovy의 gant가 낫지 않나 싶다. 그리고 실상 의존성 체크가 별로 중요하지 않은 dynamic language에서는 그냥 bash 스크립트가 더 나은 것 같기도 하다. perl을 배워두면 bash 기반으로 쓰다가 약간 확장해야 할 때 perl을 쓸 수 있다. 이게 자동화 관련에서는 가장 편리한 조합이 아닐까 싶다. python도 몇 차례 스크립팅에 활용해봤는데 perl에 비하면 이런 류의 작업에는 좀 덜 기민한 것 같다.

Naming Convention

주로 자바 프로젝트들을 해왔기 때문에 CamelCase에 익숙한데 이번에 루비를 하면서 underscore를 구분자로 사용하는 걸 많이 썼다. CamelCase에 비해서 타이핑 부담이 클거라고 생각했는데 의외로 별 차이 없었다. 가독성 역시 비슷하다. 장단점이 약간 있는데 CamelCase에 비해 한 identifier 내의 단어 구분은 좀더 명확한 반면, 기호를 많이 쓰고 괄호가 적은 ruby 코드에서는 다른 identifier들과 혼동이 되는 경우가 종종 있었다. 이것 때문에 ruby 코드가 보기에 따라서 자연 언어에 더 가깝다고 하기도 하고 또 좀더 지저분해보인다고 하기도 하는 것 같다.

JavaScript

내가 JavaScript를 제대로 써본 건 이번 프로젝트가 거의 처음이다. 2004년 쯤 웹 표준에 관심을 가지면서 HTML, CSS는 상당히 많이 공부를 했었는데 JavaScript는 그냥 생각보다 괜찮은 언어 정도로 인식하고 있었다. 근데 이번에 정말 JavaScript를 제대로 써보면서 정말 많이 감탄했다. 핵심 문법을 단 몇 줄로 설명할 수 있을 정도로 간결함에도 표현력이 아주 높다. 자세한 건 이미 JavaScript 페이지에 정리를 많이 해놨으니 여기선 패스.

XHTML, microformat, WYSIWYM

웹 표준, 시맨틱 웹과 관련해서 XHTML, microformat이 많이 뜨고 있다. WYSIWYM(What You See Is What You Mean)이란 용어도 생겼다. 스프링노트도 이 문제로 많은 노력을 기울였다. 하지만 난 좀 회의적이다. 사실 XHTML microformat을 완벽하게 하더라도 WYSIWYM과도 무관한 얘기다. 그저 또 하나의 DocBook이 될 뿐. 이 문제에 관련해서 많은 사람들이 의미론과 형식론을 혼동하고 있다. 제목을 h1 태그에 넣고 문단은 p로 구분하고 리스트는 li 안에 넣으면 형식적으로 잘 꾸며진 문서는 될 수 있지만 의미론적으로는 아무 도움을 주지 못한다. 그런데 시맨틱 웹이니 WYSIWYM이니 하는 용어와 얽어서 마치 사용자에게 어떤 시맨틱한 장점을 줄 수 있는 것처럼 오도하고 있다. 실상은 표현력은 제약하면서 실질적인 이득은 거의 없다. 웹 브라우저의 기본 에디터가 microformat이 아닌데 굳이 microformat을 맞추려면 적지 않은 희생을 치러야 하기도 하고.

좀더 pragmatic하게 접근해야 하는 문제다. XHTML이 기술적으로 제공하는 장점은 어느 정도 있다. validation을 통해서 심각한 오류는 사전에 잡아낼 수 있는 경우가 많다는 것, XML과 섞어서 사용하기 쉽고 XSLT 등의 XML 관련 기술을 쉽게 활용할 수 있다는 것. 하지만 microformat이 줄 수 있는 장점은 그다지 많지 않다. microformat을 기반으로 뭔가 해주는 도구들이 많다면 어느 정도 자동화의 장점을 누릴 수 있기는 하다. 이를테면 검색 엔진에서 h1~5나 strong 태그 등에 중요도를 높인다든지, HTML 문서를 PT로 자동 변환 한다든지. 하지만 아직 microformat을 기준으로 동작하는 툴은 많지 않은 반면 microformat을 제대로 지키기 위한 비용은 HTML 에디터에서 쉽게 감당할 만한 것이 아니다. 호환성은 오히려 HTML 4.01 transitional보다 훨씬 떨어진다. 스프링노트로 외부 HTML 문서를 붙여 넣거나 반대의 동작을 할 때 뷰가 많이 깨지는 것도 microformat에 원죄가 있다. 사용자를 위한 HTML 에디터를 만들고 싶은 거였다면 microformat은 하지 말았어야 했다.

REST

REST는 HTTP method인 GET/POST/PUT/DELETE와 resource에 대한 URI의 결합으로 애플리케이션의 모든 동작을 정의하는 URL 스킴이다. 스프링노트에서 페이지를 예로 들면 ID가 150인 페이지를 읽을 땐 GET으로 /pages/150에, 내용을 업데이트할 때는 PUT으로 /pages/150에 요청한다. 예전 같으면 /pages/150?action=read 와 같은 식이거나 혹은 /pages/update?id=150 처럼 표현했을 것을 같은 URI에 HTTP 메소드로 CRUD를 구분하는 것이다. 이것도 요즘 유행인 듯 하다.

먼저 REST에 대한 비판 하나를 짚고 넘어가면, 애플리케이션의 모든 동작이 REST의 네 가지 동작으로 쉽게 정의되지 않는다는 비판이 있다. 우리가 개발하는 애플리케이션은 너무 복잡해서 CRUD로 단순하게 나눌 수 없어! 라는 이야기. 하지만 이것은 좀더 잘게 모델링을 해야 하는 문제일 뿐이다. 이를테면, 스프링노트에 페이지 잠금을 요청하는 URL이 있는데 이걸 단순하게 /pages/150?lock 과 같이 요청하는 것이 일반적인데 이런 건 lock이라는 resource를 새로 정의하면 쉽게 해결된다. 잠금 요청은 새 잠금을 만드는 것이니 /pages/150/lock을 POST로 요청하면 된다. 로그아웃은 /user?logout이 아니라 DELETE /session이 되는 것이다.

이런 식으로 REST를 사용하면 URL의 의미가 좀더 명확해지고 애플리케이션에서 모델만 잘 정의하면 컨트롤러는 거의 반자동으로 작성할 수 있게 된다. 외부에 좀더 hackable한 URL을 제공할 수 있기도 하고.

하지만, 이런 REST의 장점을 가로 막는 중요한 제약사항이 하나 있다. 웹 브라우저를 포함한 대부분의 HTTP 클라이언트들이 디폴트로 GET/POST만 지원하고 PUT/DELETE는 지원하지 않는다는 것이다. 그래서 실전에서는 PUT /pages/150은 /pages/150?_method=PUT 과 같은 방식으로 사용한다. 이 무슨 삽질인가! 이래서는 /pages/150?action=update와 별반 다를 바가 없다. 그리고 좀더 hackable한 URL이긴 하지만 실제로 더 hackable하진 않다. 대부분의 HTTP 라이브러리들이 PUT, DELETE를 기본으로 지원하지 않기 때문이다.

또 하나의 문제는 MVC에서 설계한 model을 좀더 작은 resource로 나눠야 한다는 것이다. 앞서의 비판이 가능/불가능의 이야기라면 부적절한 비판이지만 비용의 문제가 된다면 쉽게 무시할 수 없는 비판이 된다. model을 작게 나누는 것이 반드시 좋은 디자인이 되는 건 아니기 때문이다. 그래서 model은 그냥 내버려두고 controller에서만 따로 처리하는 경우가 많은데 이로서 REST는 기술적인 장점을 거의 상실하게 된다.

실질적으로 스프링노트에서 REST는 득보다 실이 더 많았다. REST 라이브러리가 어떻게 된 건지 자동화가 안되서 매 resource를 다 routes에 정의해줘야 했다. Rails의 중요한 장점 중 하나인 ConventionOverConfiguration을 잃은 것이다. 그러면서 간혹 REST가 아닌 URL들도 섞여 들었다. 그러다보니 점점 URL과 controller가 어떻게 매핑될지 예측하기가 힘들어졌고 그래서 View 단에서 작업하는 것도 더 힘들었다. 좀더 자동화된 개발 도구가 나오기 전까지는 REST 도입은 자제하는 것이 좋을 것 같다.

CSS

CSS는 예전에도 꽤 많이 했었기 때문에 그렇게 새롭게 익혀야 하는 것이 많진 않았다. 근데 하면서 UI 개발자들의 코딩 컨벤션에 몇 가지 불만이 있었다. 예를 들면 다음과 같은 코드가 있다.

div#main table tr td.data {
    background-color: yellow;
    ...
    ...
}

csskorea에서 권장하는 방식인지 어떤지 모르겠는데 내가 접한 UI 개발자들은 대개 다 이런 식으로 css selector를 사용한다. 즉, select해야 하는 대상 엘리먼트에 이르는 경로를 쭈욱 기술해주는 것이다. 이렇게 하면 CSS 코드만 봐도 HTML 구조가 보이기 때문에 찾아가기가 쉽다고 한다. 하지만 사실 난 그 점을 공감할 수 없었다. 어차피 css selector 문법으로 찾아갈 수 있다면 사람도 쉽게 찾아갈 수 있다. 나는 selector에 최소주의를 선호한다. 해당 엘리먼트를 선택할 수 있는 최소한의 경로만 써주는 것이다. 위와 같은 경우라면 .data만 써도 웬만하면 다 될 것이고 정 불안하면 #main td.data 정도를 쓰면 될 것이다. 중간 경로는 사실상 무의미하다.

경로를 다 써주는 방식의 문제는 크게 두 가지다. 하나는 중복 문제다. 스프링노트의 경우 dialog가 20여개 되는데 dialog 전체에 공통적인 css가 대부분인데도 모든 dialog의 CSS가 다 따로따로 있다. 그래서 스타일 바꾸려면 이걸 다 찾아서 바꿔야 하고 selector에서 실수하기도 쉽다. 파일 크기가 커지는 것도 문제다. 로딩 타임과 랜더링 타임이 느려지는 것도 문제거니와 관리해야 하는 파일 사이즈가 커지니까 유지보수하기도 어렵다. 그냥 클래스만 공통으로 주고 공통 속성들을 dialog 클래스에만 주면 이런 중복은 다 제거할 수 있다. 또 하나는 HTML 구조에 의존하기 때문에 구조를 쉽게 바꿀 수 없다는 것이다. 개발하다보면 HTML의 구조를 뜯어고칠 일이 적지 않게 있는데 그 때마다 CSS의 selector를 다 바꿔줘야 하기 때문에 구조 변경의 부담이 크다.

내 생각엔 selector에는 특수한 경우에만 태그를 명시해주고 그 외에는 최대한 id나 class만을 사용하려고 노력하는 것이 좋을 것 같다.

linux (RHEL vs ubuntu)

RHEL은 NHN에서부터 줄곧 써왔지만 사실 잘 모른다. 늘 소스 컴파일로 설치를 해서 썼기 때문에 패키지는 거의 사용하지 않았다. 반면 우분투를 쓸 때는 패키지가 있는 건 특별히 패키지 구성이 심하게 맘에 들지 않는 이상 패키지를 우선으로 썼다. 그래서, 사실 제목은 RHEL vs ubuntu지만 내용은 웹 서버에 애플리케이션을 설치할 때 소스 컴파일을 하는 것이 좋은가, 패키지로 설치하는 것이 좋은가에 대한 이야기다.

과거 자바를 주로 개발할 때는 아파치와 DoS 방지 모듈, jk 커넥터 정도만 컴파일해서 썼고 Java와 tomcat 등은 그냥 갖다 풀기만 하면 되서 별로 어려운 작업이 아니었다. 라이브러리는 죄다 jar로 떠 놓으면 되기 때문에 모두 CVS로 관리가 되고 변경될 때마다 소스와 같이 배포가 되기 때문에 라이브러리 관리 문제는 거의 없었다. 하지만 Rails와 PHP를 쓰게 되니 이야기가 많이 달랐다. 아파치에 설치해야 하는 모듈만 6,7개 정도에 memcache, mysql client 등 컴파일해야 하는 게 한두 개가 아니었다. 그래서 모든 설치를 다 마치는데 서버 한 대당 거의 1시간이 넘게 걸렸다. 시간도 시간이지만 이것들을 모두 설치하고 세팅하는 과정이 너무나 짜증스러웠다. 우분투를 썼다면 거의 5분 안에 완료할 수 있는 작업인데 컴파일하고 세팅하고 하느라 삽질하다보니 정말 시간이 아까웠다. RHEL을 선택한 이유는 Rails가 우분투에서 제 성능이 안 나와서인데 RHEL 라이센스 구매가 늦어져서 레드햇의 패키지 관리 시스템은 못 쓰고 소스 컴파일을 하게 된 상황인데 차라리 우분투에서 제 성능 나오게 하는 삽질을 하는 게 더 적은 비용이 드는 일이 아니었을까 싶다. 결과적으로는 이 삽질도 내가 Rails를 싫어하게 된 계기 중 하나가 되었다.

우분투에서 제공하는 아파치 패키지 구조가 다른 배포판과는 약간 다른데 알고보면 상당히 편리하다. 대부분의 모듈이 패키지로 있기 때문에 설치하기도 쉽고 설정 파일도 직관적으로 나눠져 있다. 특별한 이유가 없다면 우분투에서는 아파치 패키지를 그냥 쓰는 것이 제일 좋은 선택일 것이다. Rails도 그냥 패키지로 설치해도 쓰기에 별 무리가 없다. 어차피 EdgeRails를 쓰는 경우는 Rails 패키지가 아무 버전이나 깔려만 있으면 되기 때문에 상관 없기도 하고. mysql도 그냥 패키지로 설치해도 별 문제 없다.

실제로 개발 데스크탑으로 우분투를 쓰고 있다면 서버도 그냥 우분투를 쓰는 것이 더 나은 선택일 것이다. 로컬에서 실험해본 것을 거의 그대로 활용할 수 있으니까. 그래서 이번에 Django로 개발하는 프로젝트는 로컬에서 설정한 그대로 서버에 적용해서 비교적 쉽게 설정할 수 있었다. 우분투의 전략 중 하나가 데스크탑을 널리 퍼트려서 개발자들이 데스크탑에서 우분투를 쓰게 되면 자연히 서버도 우분투를 쓰려고 할 것이라는 것인데 내가 넘어간 걸 보면 꽤 괜찮은 전략인 것 같기도-_-

Subversion

아직 CVS를 쓰고 있다면 Subversion으로 옮겨 타지 말라. Subversion이 CVS보다 나은 점은 없다. 같은 개발진이 만들었다면서, 더 낫게 만들려고 했다면서 왜 이 모양인지. 가장 심각한 문제는 툴의 완성도가 낮다는 것이다. 이클립스에서 편리하게 쓸 수 있는 CVS에 비해 Subversion을 이클립스에서 쓰려면 대단한 인내심이 필요할 것이다. 버그가 너무 많다. 커밋이 잘 안되는 경우는 부지 기수고 merge도 잘 안되고 삭제나 이동도 일반적인 기대와 다르게 동작한다. subclipse는 정말 후졌고 subversive는 그나마 조금 쓸만하지만 여전히 CVS 플러그인에 비하면 너무 부족하다. 더 심각한 문제는 커맨드 라인에서 써도 버그가 발생한다는 것이다. 많은 파일과 디렉토리를 한 번에 커밋하면 락이 걸려서 안되기도 하고 엄청나게 느린 속도로 되는 경우도 있다.

버그가 좀 있어도 기능이 더 좋다면 이해해 줄 수도 있다. 그러나 실제로 기능을 체감하는 것은 도구를 통해서인데 실질적으로 이클립스의 CVS 플러그인보다 나은 클라이언트를 갖지 못한 Subversion은 기능적으로 더 떨어진다고도 할 수 있다. 그리고 Subversion이 내세우는 장점인 changeset 단위의 revision number도 장점보다는 단점인 것 같다. 실제로 commit은 파일 단위로 일어나는 경우가 훨씬 더 많다. 한 번 생각해보라. commit하는 단위가 하나의 의미적 작업 단위와 일치하는지를. 두 개 이상의 작업 단위를 한 번에 commit하는 경우도 있고 파일에 따라 다른 commit log를 남겨야 해서 한 작업 단위를 여러 번에 나눠서 commit하는 경우도 많을 것이다. 이런 상황에서 changeset 단위 rollback이 큰 의미가 있는 것은 아니다. 어차피 CVS도 시간과 태그로 changeset 단위와 비슷한 개념을 활용할 수 있기 때문에 파일별 revision이 더 나은 것 같다. changeset 때문에 한 저장소에 여러 프로젝트를 관리하기 어렵다는 것도 문제다. revision이 공유되기 때문에 다른 프로젝트의 변경 내용 때문에 내 프로젝트의 revision이 올라가는 현상이 생기는 것이다. 이것 때문에 내 프로젝트의 이전 버전을 찾는데 고생할 때도 있고 export하거나 할 때도 revision이 너무 많아서 오래 걸리는 문제가 있다. 태깅도 좀 문제가 있다. subversion 태깅은 단순 copy라서 더 쉽다고 하는데 그래서 오히려 더 오래 걸리고 무거운 동작이 되버려서 잘 활용 안하는 경향이 있다. 물론 changeset 개념이 있기 때문에 반대로 태깅이 별로 필요 없기도 하지만.

CVS는 오랫동안 많은 사람들이 써왔고 몇 가지 부족함이 있긴 하지만 그 부족함은 대부분 툴로 커버할 수 있다. 그리고 거의 모든 배포판에 기본으로 설치된다. 별다른 이유가 없다면 그냥 CVS 쓰기를 권한다. ClearCase 등 상용 SCM도 좋은 기능을 제공하긴 하지만 실제 사용할 때 CVS보다 월등히 나은 점을 발견하기는 쉽지 않을 것이다. 단순 기능 목록에 현혹되지 말고 자신이 실제로 사용하는 기능에 초점을 맞춰서 도구를 선택해야 한다.

총평

무수히 많은 신기술을 도입한 프로젝트였지만 불행히도 내 맘에 드는 건 별로 없었다. JavaScript에서 도입한 prototype 정도? 무비판적으로 유행을 쫓은 대가가 아닐까 싶다. 검증되지 않은 것을 도입하지 않는 게 좋다는 말을 하려는 건 아니다. 검증되지 않았으면 스스로 검증해보고 도입해야 한다는 것이다. 오픈마루가 전반적으로 누가 미는 기술이 있으면 별 말 않고 따라가는 경향이 있는데 프로젝트가 혼자 하는 게 아닌 이상 중요한 기술적 결정을 내릴 때는 기술 검토를 충분히 거쳤어야 했다. 그리고 기술적인 이슈에 사용자 가치를 어정쩡하게 개입시키는 것도 경계해야 한다. XHTML이나 REST 같은 것도 사용자 가치를 많이 이야기하지만 실제로 사용자에게 주는 가치는 거의 없다. 그럼에도 불구하고 표준의 논리가 개입되면서 기술적인 단점들을 묻고 지나가곤 한다. 좀더 실용적인 기술 평가가 필요하다.

 

Wiki at WikiNamu