블로그 이사
Tuesday, February 9th, 2010전부터 옮기고 싶었는데, 드디어 블로그를 옮깁니다. URL도 바뀌었습니다. (기존 도메인에서 .pe가 빠졌습니다.)
새 블로그는 Tumblr에서 운영합니다. RSS는 FeedBurner로 유지되니까 아마도 그대로 냅둬도 될 겁니다.
Programming, Writing, Reading, Thoughts…
이 블로그는 더이상 운영되고 있지 않습니다. 단지 예전 URL을 유지하는 용도로만 남아 있습니다. 새 블로그의 주소는 blog.dahlia.kr입니다.
전부터 옮기고 싶었는데, 드디어 블로그를 옮깁니다. URL도 바뀌었습니다. (기존 도메인에서 .pe가 빠졌습니다.)
새 블로그는 Tumblr에서 운영합니다. RSS는 FeedBurner로 유지되니까 아마도 그대로 냅둬도 될 겁니다.
지난번에도 IBM developerWorks 세미나에서 페차쿠차 형식으로 발표를 했었는데, 이번에도 기회가 생겨서 했다. 그때와 굉장히 관련이 깊은 주제로 역시 OpenAPI에 관한 발표를 했다. 제목은 “괜찮은 오픈 API 제공하기 + VLAAH API 소개”. VLAAH API를 디자인하면서 경험했던 것들을 공유하는 내용이다. 별 것 없지만 발표 자료를 공유한다.
덧. 이런 파일 올릴 때마다 어떻게 해야할지 모르겠다. 사실 DivShare 같은 서비스는 중간 페이지가 있어서 짜증나는데, 막상 내가 직접 파일을 호스팅하기는 싫다.
PHP로 Lisp 방언을 구현했다. 이름은 Lisphp다. 사실 첫 동기는 내가 Lisp을 구현하면 얼마나 걸릴지 시간을 측정해보고 싶어서였는데, 예상보다는 조금 늦게 약 하루만에 기본적인 평가기(evaluator)를 구현했다. 그게 약 한달 전이고, 막상 거기까지 해놓으니 좀더 완성된 모습을 원하게 되어서 지금까지 회사일이 참 하기 싫을 때마다;;; 조금씩 커밋을 해서 꽤 많이 만들었다. 곧 첫번째 태깅을 할 생각이다.
현재의 프로젝트 목적은 배포되는 웹 애플리케이션이나 호스팅하는 웹 서비스의 스크립팅을 돕기 위함이고, 그래서 샌드박스도 구현했다. 기본적으로 두 가지 환경(environment)을 제공한다.
Lisphp_Environment::sandbox()Lisphp_Environment::full()use 매크로를 통해 가져다 쓸 수 있다.use 매크로는 현재 함수와 클래스, 상수를 불러올 수 있게 만들었다. 클래스를 불러오면 정적 메서드도 함께 불러온다. 근데 아직 인스턴스 메서드를 가져오게 하진 않았다. 이것만 구현하면 Git 리파지토리에 첫번째 태깅을 할 생각이다.
(use strrev <PDO>)
(strrev "Hello!") #=> "!olleH"
(<PDO> "mysql:dbname=testdb;host=127.0.0.1" "dbuser" "dbpass")
자세한 설명은—사실 그리 자세히 써두진 않았지만—README.txt를 참고하면 된다. 마지막으로 웹에서 가지고 놀 수 있게 REPL도 만들어봤다. (커맨드라인 REPL은 리파지토리에 lis.php 파일로 제공된다.)
아직 디자인을 하지 않아서 못생겼는데 기능은 잘 작동한다. 물론 이쪽은 샌드박스 환경이라 use 매크로는 쓰지 못한다. 소스 코드는 GitHub에서 MIT 라이센스로 배포하고 있다.

2년 동안 붙잡고 있던 야간개발팀의 서비스 VLAAH가 오늘 정식 오픈했다. 누구나 가입이 가능하다.
더불어 VLAAH API 1.0도 공개했다. 애플리케이션 키를 발급받기 위해서는 VLAAH에 계정을 만들어야 한다.
Markdown Extra는 PHP Markdown를 작성한 Michel Fortin이 Markdown에 없는 테이블, 헤딩 ID, 정의 목록 등을 추가한 확장 스펙으로, 요즘에는 Pandoc 등 다른 구현에서도 많이 구현해서 거의 de facto standard로 받아들여지는 분위기다. 나도 이 확장 스펙을 거의 당연시하며 사용하고 있는데, 오늘 이 스펙의 정의 목록이 생각보다 잘 디자인되어 있다는 것을 깨달았다.
이 문법은 대충 다음과 같은데,
Apple
: Pomaceous fruit of plants of the genus Malus in the family Rosaceae.
Orange
: The fruit of an evergreen tree of the genus Citrus.
저걸 HTML로 바꾸면 아래와 같이 렌더링된다.
- Apple
- Pomaceous fruit of plants of the genus Malus in the family Rosaceae.
- Orange
- The fruit of an evergreen tree of the genus Citrus.
그런데 이게 표준 Markdown 스펙으로 해석을 하게 되면, <dt> 부분의 줄과 :으로 시작하는 <dd> 부분의 줄 사이에 공백이 없기 때문에 하나의 문단(<p>)으로 처리되게 된다. 그래서 제대로 된 정의 목록으로 보이지는 않지만, 그럭저럭 한 문단 안에 콜론이 들어있는, 사람이 보기에는 정의 목록과 비슷하게 보이는 HTML로 렌더링된다.
Apple : Pomaceous fruit of plants of the genus Malus in the family Rosaceae.
Orange : The fruit of an evergreen tree of the genus Citrus.
이 문법은 나름대로 하위호환성을 고려하고 디자인되었던 것이다.
덧. 어쩐지 이 블로그는 Markdown을 전문적으로 취급하는 블로그가 되어가는 느낌이 든다.
전역 변수(global variables)나 싱글톤 패턴(singleton pattern)을 두고 어떤 사람은 악이라고도 하고, 어떤 사람은 필요악이라고도 한다. 어쨌든간 나는 저러한 구체적인 것에 대해 좋으니 나쁘니 하는 것은 사실은 별 생각이 없다는 증거라고 생각한다.
나는 전역적인 상태(global state)가 문제라고 보는데, 방점을 찍을 부분은 “전역”보다는 “상태”이다. 반대로 상태를 가지는 것이 아니라면 전역적이어도 아무런 문제가 없다는 뜻이다. 기본적으로 전역적인 것은 아무데서나 참조 가능하다는 이야기다. 그런 식이면 클래스 이름이나 함수 이름도 결국은 전역적인 것이 된다. 그러나 아무도 전역 상수(global constants)에 대해 뭐라고 떠들지는 않는다. 결국 실행 시간에 바뀌는 것이 아무데서나 간섭받으면 프로그램이 꼬이기 십상이라는 말이다. 바뀌지 않으면 문제가 되지 않는다.
값 객체(value object)라고도 하고 상태 없는 객체(stateless object)라고도 하고 변형되지 않는 객체(immutable object)라고도 하지만, 이름이야 어쨌든 객체는 변경되지 않도록 디자인되는 게 좋다고 생각한다. 모든 연산은 가능하면 자기 자신을 수정하는 대신, 새로운 객체를 반환하는 편이 낫다. 성능의 문제가 있지만, 이런 부분은 컴파일러 등의 언어 구현이 잘 해내야 할 몫이라고 생각하는 편이다.
나는 비슷한 관점에서 Ruby의 열린 클래스(open class) 정책이 안 좋은 면도 많이 포함한다고 생각한다. 물론 Python 등의 언어도 클래스가 런타임에 수정 가능한 객체이긴 하지만, Ruby 공동체는 실행 시간에 클래스를 수정하는 짓을 장려하는 문화를 갖고 있다는 점이 큰 차이다. 게다가 Ruby 언어는 그것을 깔끔하게 해낼 수 있도록 문법이 설계되어 있다.
전역적인 상태의 또 다른 문제는 여러 상태가 존재해야할 필요성이 생기면 바로 문제가 생기는 디자인이라는 점이다. 예를 들어 간단한 웹 서버를 내장한 애플리케이션이 있다고 치자. 원래는 80 포트에 하나의 웹 서버만 있으면 됐지만, 둘 이상의 포트에 연결되어 여러 웹 서버를 돌릴 필요가 생기게 되는 식의 상황은 생각보다 적지 않다. (웹 서버 대신 예를 들 다른 것들도 많을 것이다.) 처음에 전역적인 웹 서버를 가지고 있었다면, 여러 웹 서버 인스턴스를 생성할 수 있도록 고치는 작업은 꽤 성가신 일이 될 것이다. 게다가 어떤 객체가 전역성을 띄게 디자인되었다는 것은 그 객체가 다중화될 여지가 없다는 확신이 있어야 가능하기 때문에, 그러한 확신 아래 코드 곳곳에서 신나게 전역 객체와 직접적으로 참조를 하고 있을 가능성이 높다.
서상현 씨가 프레임워크와 라이브러리의 차이에 대한 훌륭한 아포리즘을 적으신 적이 있는데,
내가 부르면 라이브러리고, 내가 불리면 프레임워크다. 같은 값이면 라이브러리가 프레임워크보다 낫다.
프레임워크 역시 그 자신이 전역적인 상태를 가진다는 점에서 같은 문제를 공유한다. 여러 프레임워크 인스턴스를 가질 수는 없다. 만약 그게 가능하다면 라이브러리라고 불러야 한다. “같은 값이면 라이브러리가 프레임워크보다 낫다”는 이야기는 정말 지당한 말이다.
물론 여기서도 문제가 되는 것은 그 객체가 상태를 가질 때 뿐이다. 상태를 가지지 않는다면 굳이 다중화할 필요가 없다. 결국 라이브러리는 상태를 갖지 않거나 전역적이지 않고(둘 중 하나라도 만족하면 라이브러리다), 프레임워크는 전역적인 상태를 갖는다고 표현할 수도 있다.1
결국 정말 문제가 되는 것은 전역성이 아니라 상태를 가지고 있는지 여부이다. 전역성은 상태를 가지고 있는 객체의 문제를 특히 두드러지게 하는 요소일 뿐이다.
쓰고 보니 동어반복. ↩
Pandoc은 Markdown, HTML, LaTeX, ReST, DocBook 등 굉장히 많은 포맷 간의 상호 변환을 해주는 도구인데,
Pandoc is a Haskell library for converting from one markup format to another, and a command-line tool that uses this library. It can read markdown and (subsets of) reStructuredText, HTML, and LaTeX, and it can write markdown, reStructuredText, HTML, LaTeX, ConTeXt, PDF, RTF, DocBook XML, OpenDocument XML, ODT, GNU Texinfo, MediaWiki markup, groff man pages, and S5 HTML slide shows.
커맨드라인 툴이기도 하고, Haskell 라이브러리이기도 하다. Pandoc의 특징은 모든 마크업 포맷을 내부 중간 표현 형식으로 한번 만든 다음에, 그것을 다시 출력한다는 점이다. 그래서 HTML 문서를 읽어서 MediaWiki 마크업으로 출력한다던가, Markdown 문서를 읽어서 유닉스 man 페이지를 만든다던가 하는 게 가능하다.
게다가 단순히 Markdown 구현으로 봐서도 사실 다른 구현과 비교하기 힘든 정확도와 성능을 가지고 있기도 해서, 내가 가장 애용하는 Markdown 구현이기도 하다.
아무튼 최근에 Ruby에서 이걸 써야할 일이 있어서 인터페이스 모듈을 만들어봤다. Ruby가 일본제니까 약간 일본 느낌 나게 이름을 Pandoku라고 지었다.
http://github.com/lunant/pandoku
처음에는 C 인터페이스로 라이브러리를 만들어서 붙이는 것을 생각했지만, Haskell에서 그게 쉽지 않아서 파이프로 통신하게 했다. Gem 배포는 Gemcutter를 이용해서 하고 있다. http://gemcutter.org/gems/pandoku
gem install pandoku
Pandoc 바이너리가 필요한데, 없으면 cabal로 설치하면 된다. Cabal은 Haskell 패키지 매니저다.
cabal install pandoc
Pandoku는 기본적으로 PATH 환경변수에서 pandoc을 찾는다. 만약 PATH에 없다면 별도로 PANDOC_PATH 환경변수를 정의해도 된다.
export PANDOC_PATH="$HOME/.cabal/bin/pandoc"
Ruby에서의 사용은 String#pandoku 메서드를 통해 가능하다. (좀더 복잡한 방식이 있긴 하다.)
require 'pandoku'
result = document.pandoku(:markdown => :html)
내가 무수히 많은 언어 가운데 Python을 유독 좋아하는 이유는, 들여쓰기로 블럭을 다루는 문법 때문이 아니라(사실 난 이거 안 좋아한다) 일반화된 인터페이스를 잘 정해놓았기 때문이다. 크게 두 가지가 있는데, 하나는 함수 객체(callable object라고 부른다)이고, 다른 하나는 어느 언어에나 다 있는 반복자이다.
함수 객체는 Java나 Ruby 같은 언어에는 없고 C++ 같은 언어에는 있는 개념인데, 람다 같은 것을 말하는 것은 아니다. 그냥 어떤 객체든 호출 연산자 ()를 정의할 수 있고, 그렇게 정의된 객체는 함수처럼 호출 가능하다는 것인데, 반대로 얘기하면 일반 함수들도 사실 그냥 호출 연산자만 정의한 객체라고 볼 수도 있다. 실제로 Python 클래스는 함수 객체다. 처음 접하는 사람은 그냥 new 키워드를 안쓰는구나 하고 말지만, 조금 써보고 나면 Python 클래스 객체가 호출 연산자를 인스턴스를 생성하도록 정의한 것뿐이라는 사실을 알게 된다.
Python의 반복자는 단순히 next 메서드를 정의한 객체를 뜻한다. 이 메서드는 뭔가 계속 값을 반환하다가 더이상 반환할 게 없다면 StopIteration 예외를 낸다. None을 반환하는 대신 예외를 내는 이유는 None 자체도 반복 대상이 될 수 있기 때문이다. 만약 길이가 무한하다면 StopIteration 예외가 영원히 던져지지 않을 수도 있다. 그런 반복자는 보통 itertools.takewhile 같은 것을 써서 필요한 만큼만 쓴다. 뭐 이런 개념은 Haskell 같은 언어를 조금만 접했어도 이해하기 쉬울 것이다. 내가 생각할 때 다른 언어의 반복자는 어떨지 몰라도 Python의 반복자는 단순한 추상화가 아니라 효율과 성능을 유지하기 위한 추상화이다. 이것은 C++랑 비슷한 면이 있다(나만 그렇게 생각하나?).
(이하 “반복자”는 Python에서의 iterator를 뜻한다. 모든 프로그래밍 언어의 얘기가 아니다.)
반복자의 가장 좋은 활용은 지연 평가(lazy evaluation)에 있다고 생각한다. 예를 들면 강성훈 님이 만든 vlaah-python 같은 것이 있다. 여기서는 의견의 목록을 단순히 하나의 거대한 리스트로 다루게 하고 있다. 적어도 보기에는 거대한 리스트 같다. 그렇지만 내부적으로는 필요한 만큼만 조금씩 요청하고 로딩한다. 만약 그 거대해 보이는 리스트에서 맨 처음 의견 하나만 사용한다면, 실제로 vlaah-python은 내부적으로 단 한 번의 요청만 하고 끝낸다. 무식하게 모든 의견을 다 가져오지 않는다는 뜻이다.
Python이 반복자 인터페이스를 잘 정의했다는 뜻은, 단순히 “next 메서드 있는 모든 객체는 다 반복자다”라는 심플함을 말하는 것이 아니다. Python의 모든 표준 라이브러리가, 단순히 리스트를 받는 대신 아무 반복자나 받기 때문에 좋은 인터페이스라고 생각한다. 예를 들어 내가 당장 리스트의 모든 합을 구하는 함수를 만든다고 생각해보자.
def sum(numbers):
total = 0
for number in numbers:
total += number
return total
저 함수를 작성하면서 내가 반복자 인터페이스를 염두한 부분은 정말 하나도 없다. 그렇지만 Python의 for문은 반복자를 받기 때문에 이미 저 함수는 내 의도 이상으로 반복자를 완벽하게 지원하게 되었다. numbers에는 리스트 뿐만 아니라 모든 반복 가능한 객체를 넣을 수 있는 것이다. 단순히 for문만 그런 것이 아니다. 완벽하게 절차적으로 작성했던 저 함수를 다르게 고쳐보자.
import operator
def sum(numbers):
return reduce(operator.add, numbers, 0)
이 함수 역시 난 신경쓰지 않았는데 결국에는 반복자를 완벽하게 지원하게 되었다. 왜냐면 reduce 내장 함수 역시 반복자를 받기 때문이다. 이와 같이 Python의 거의 모든 표준 라이브러리가 반복자를 당연하듯 지원하고 있기 때문에, 그것을 쌓아서 올리는 거의 모든 사용자 정의 함수들 역시 반복자를 당연하게 지원하게 된다. 이러한 점 때문에, Python 프로그래머는 반복자만 구현하면 그것이 어떤 곳에서라도 사용 가능하다는 확신을 가지게 되고, 그런 점이 반복자를 더 많이 쓰도록 돕는다.
거기에 제너레이터(generator)와 제너레이터 표현식(generator expression) 같은 것까지 생각해보면, 확실히 Python은 반복자를 잘 활용해야 그때부터 제대로 쓰는 셈이다. 다행히 Python은—앞서 말했듯—의식하지 않더라도 반복자를 잘 활용할 수 있게끔 만들어진 언어이다.
items = {}
for key in keys:
items[key] = get_value(key)
위 코드는 items 딕셔너리를 만드는 코드인데, dict 클래스의 생성자를 이용하면 아래와 같이 바꿀 수도 있다.
pairs = map(get_value, keys)
items = dict(zip(keys, pairs))
이것으로도 충분히 간결하긴 하지만, 제일 개간지가 나는 것은 아래와 같은 코드라고 생각한다.
items = dict((key, get_value(key)) for key in keys)
생성자 안에 쓰인 것이 제너레이터 표현식이고, 저것은 반복자를 반환하는 코드이다. 당연히 공간 효율도 이전의 map 함수를 쓰는 코드보다 좋다. 만약 keys의 길이가 굉장히 길다면 이전 코드는 map이 매우 긴 리스트를 만드느라 시간과 공간을 낭비했을 것이다. 제너레이터 표현식은 제너레이터 객체를 만들어내는 문법인데, 제너레이터 객체는 실제로 어떤 데이터를 가지고 있지는 않고, 어떤 데이터로부터 어떤 데이터를 뽑아내거나(map) 걸러내라(filter)는 지시만을 가지고 있다. 실제로 데이터가 생성되는 것은 dict 생성자 안쪽에서다. 제너레이터 표현식 말고 제너레이터를 쓰면 훨씬 복잡한 순차열(심지어 길이의 끝이 정의되지 않는 Haskell의 무한 리스트와 같은 것조차)을 절차적으로 간단하게 작성할 수 있다.
import math
def pager(length, selection=1, step=9):
length = int(length)
selection = int(selection)
step = int(step)
half = math.floor(step / 2)
if length > step and selection > half + 2:
yield "first", 1
i = length - step + 1 \
if selection + half >= length \
else selection - half
else:
i = 1
to = min(i + step, 1 + length)
for i in xrange(i, to):
yield "selected" if i == selection else i, i
if max(selection, to) + 1 < length:
yield "last", length
위 함수는 제너레이터 객체를 반환하는데, 게시판 아래쪽에서 쓰이는 페이저(pager)를 만들어낸다. 만약 Ruby를 썼다면 블럭으로 했을텐데, 짐작할 수도 있겠지만 Python 제너레이터는 Ruby의 블럭 역할을 대체하는 용도로 쓰이기도 한다. 하지만 이것은 단순히 반복자 인터페이스를 재활용했을 뿐이라는 점에서 더 재미있다.
for klass, page in pager(length, page):
print """
<li class="%s">
<a href="?page=%d">%d</a>
</li>
""" % (klass if isinstance(klass, basestring) else "", page, page)
Ruby라면 이렇게 쓸 수 있도록 블럭을 받는 메서드로 구현했을 것이다.
page(length, page) do |klass, page|
puts %{
<li class="#{klass.is_a?(Symbol) ? klass : ''}">
<a href="?page=#{page}">#{page}</a>
</li>
}
end
Ruby에서 블럭 메서드로 구현했을 때와 다르게, 단순히 반복자이므로 순차열 자체로도 이용 가능하다.
>>> pager(123, 12)
<generator object pager at 0xb7dda98c>
>>> list(pager(123, 12))
[('first', 1), (8, 8), (9, 9), (10, 10), (11, 11), ('selected', 12), (13, 13), (14, 14), (15, 15), (16, 16), ('last', 123)]
Python은 Haskell처럼 모든 인자가 지연 평가되는 언어는 아니지만 워낙 모든 함수가 반복자를 주고 받게 되어 있다보니 반복자의 연쇄가 깊게 이뤄진다. 반복자의 연쇄가 깊다는 것은 함수 호출 스택이 깊어도 맨 끝에서 아래까지 “의도”1가 잘 전달된다는 뜻이다. 예를 들어 아래의 코드를 보자.
odd_numbers_to_10 = itertools.takewhile(lambda i: i <= 10, (x for x in xrange(1000) if x % 2))
너무나 작위적인 예제지만, 의도가 깊게 관통하는 코드의 예로서는 읽을만 하다. 결국 최종적으로는 “0 이상 10 이하의 홀수 목록”을 원하는 건데, 최초로 제공되는 소스인 xrange(1000)은 10을 초과하는 숫자는 생성하지 않는다. 의도가 잘 전달된다는 것은 이러한 뜻이다. 의도가 전달되지 않는 예를 만드려면 xrange를 range로 바꾸면 된다.2
odd_numbers_to_10 = itertools.takewhile(lambda i: i <= 10, (x for x in range(1000) if x % 2))
이 코드는 최종적으로 얻고자 하는 값이 결국 10 이하의 숫자들뿐임에도 range(1000)이 1000개의 수가 담긴 큰 리스트를 만들어내는도록 냅둔다. 공간도 낭비고 시간도 낭비다. 만약 우리가 xrange(1000)나 range(1000) 자리에 무한개의 숫자를 만들어내는 함수를 넣는다면 결정적인 차이가 발생한다. 만약 그 함수가 반복자를 반환한다면 우리가 처음 xrange(1000)을 썼던 코드와 효율에 차이가 없겠지만, 리스트를 만들어낸다면 리스트를 만들다가 메모리가 꽉 차서 뻗고 말 것이다.
이런게 실제로 프로그래밍할 때는 별로 중요하지 않을 것 같아도 의외로 큰 차이를 만드는 경우도 많다. 예를 들어 RoR의 ActiveRecord를 쓰면 find 메서드가 정말 배열을 반환한다. 그러니까 뷰로 넘기기 전에 미리 DB에 접근해서 데이터를 가져온다는 뜻이다. 그리고 기본 템플릿 엔진인 ERB는 전체 HTML 문자열을 다 생성해낸 다음 그것을 웹 서버로 전달한다. 이렇게 되면 가져오는 데이터가 클 경우 사용자 입장에서는 로딩하는 동안 브라우저의 흰 화면을 계속 보고 있어야 한다. 데이터를 가져오고 전체 complete HTML을 만들어내기 전까지는 응답을 할 수 없기 때문이다. 그러다가 로딩이 끝나면 한번에 퍽 하고 모든 화면이 렌더링된다. 아웃풋 버퍼링이 속도에 도움을 줄 때도 많지만, 내 경험상 웹 개발에서의 모델 컨트롤러 뷰 서버 스택에서는 버퍼링 없이 모든 데이터가 스트리밍되는 게 체감 속도가 훨씬 좋다.
요즘에는 Elixir(SQLAlchemy) + Tornado + Jinja2를 쓰고 있는데 일단 SQLAlchemy의 ORM 쿼리 객체가 기본적으로 반복자고, Jinja2 역시 부분적으로 HTML을 조금씩 만들어내게끔 반복자를 반환하는 기능이 있다. 이렇게 되면 HTML을 생성하는 와중에 필요한 데이터를 DB로부터 조금씩 가져오고, 또 금방 만들어진 HTML을 바로바로 서버를 통해 유저 에이전트로 전달하므로 브라우저에는 처음부터 데이터가 쌓이듯이 렌더링되게 된다. 당연히 사용자 입장에서 피드백이 바로 나오므로 더 쾌적하게 느껴진다.
글이 정리하기 힘들게 길어졌지만 얘기하고 싶은 바는 하나다. 지연 평가는 생각보다 훨씬 유용하며, Python 반복자와 제너레이터는 그것을 쉽게 할 수 있도록 도와준다는 것.
아래는 LangDev IRC 채널에서 7월 15일에 했던 대화. (중간에 껴있는 다른 주제의 이야기는 제외.)
11:49 <@sanxiyn> 또다시 yak shaving의 신비한 세계
11:51 <@sanxiyn> yak shaving이 뭔지 다 아시죠?
11:51 <디토군> 방금 찾아보고 왔음
11:51 <@mana> (조용히 설명을 기대중)
11:51 <@sanxiyn> 나무를 베려고 하는데
11:52 <@sanxiyn> 도끼질을 하다가
11:52 <@sanxiyn> 도끼가 더 잘 들면 나무를 쉽게 벨텐데 해서
11:52 <@sanxiyn> 도끼 날을 세우다가
11:52 <@sanxiyn> 도끼 가는 돌이 더 좋으면 도끼 날을 더 빨리 세울텐데 해서
11:52 <@sanxiyn> 좋은 숫돌이 있는 곳을 수소문해 보니
11:52 <@mana> …
11:52 <&홍민희> 그거 전형적인 제 행동이네요
11:52 <@sanxiyn> 저 멀이 어디에 세계 최고의 숫돌이 난다고
11:52 <@sanxiyn> 거기까지 야크를 타고 가려다가
11:52 <@mana> 항상하던 짓이라서 타이핑을 할 수 없었습니다
11:52 <@sanxiyn> 야크 털을 깎아서…
11:52 <@sanxiyn> etc.
11:52 <@mana> …
11:53 <@sanxiyn> Jargon File에는 자세한 내용은 안 나오네요.
11:53 <@sanxiyn> http://www.catb.org/~esr/jargon/html/Y/yak-shaving.html
11:55 <@sanxiyn> 근데 저도 현재 yak shaving 중
11:55 <@sanxiyn> 리커전이 너무 깊어서 도저히 이런 공개적인 자리에서 말할 수가 없다는;
11:55 <@sanxiyn> 창피;
11:58 <디토군> 흠 나도 yak shaving을 하고 있던가
11:58 <@sanxiyn> 디토군: 너무 깊지 않으면 소개좀~
11:58 <@mana> 다들 살다보면 그런경우 꽤 많지 않나요?
11:58 <디토군> 딱히 그런 것 같지는…
11:58 <@mana> 나만 그런가
11:58 <@mana> …
11:58 <@sanxiyn> mana: 프로그래머들만 그래요
11:58 <디토군> 그래도 일단 Django에 붙은 걸로 yak shaving은 해결된 듯
11:58 <@sanxiyn> 뭐랄까 우수한 프로그래머들의 직업병
11:59 <디토군> 애니메타 코드를 재작성하고 있긴 한데
11:59 <디토군> (…….)
11:59 <@sanxiyn> yak shaving이라 함은 예를 들어
11:59 <@sanxiyn> 애니메타 코드를 재작성하고 있는데
12:00 <@sanxiyn> 테스트하다가 CSS가 어디 미운 곳을 찾았는데
12:00 <@sanxiyn> 고치려니 짜증나서 CSS 마크업 언어를 찾다가 LESS를 발견했는데
12:00 <@sanxiyn> LESS 그냥 쓰면 되는데 루비로 되어 있어서 파이썬으로 다시 짜려다가
12:00 <@sanxiyn> 테스트 수트가 없어서 LESS 테스트 수트를 만든다거나
12:00 <@sanxiyn> 이런 걸 말하는 것임
12:00 <@sanxiyn> (특정인을 가리키려는 의도는 없구요)
12:00 <디토군> ㅋ
난 yak shaving으로 엄청난 시간을 낭비하는 사람이다. 예를 들어 VLAAH를 만들기 위해 인하우스 웹 프레임워크를 만들고, PostgreSQL을 사용하는데 마땅한 ORM이 없어서 PostgreSQL 전용의 ORM 프레임워크를 만들고, 데이터베이스 스키마 마이그레이션을 하기 위해 RoR에 있는 것과 비슷하게 또 만들고, ORM에서 블럭을 사용하거나 웹 프레임워크에서 액션을 고차함수로 주고 받고 싶어서 Phunctional을 만들고1… 그래서 지금은 VLAAH 코드의 중요한 부분에 모두 나의 책임이 엮여져 있기 때문에 야간개발팀에서 레거시 코드에 대한 다굴을 가장 많이 당하는 것도 나다.
이 블로그를 오랫동안 구독하셨던 분들은 기억하실 수도 있겠지만, 나는 Metaphor라는 언어도 하나 만들고 있다. 이것 역시 동기는 결국 yak shaving이다. 고등학교 2학년 때(5년이나 되었네?) 게임 제작 동아리에서 활동했는데, 게임에서 사용할 스크립트 언어를 찾아보라는 재석 선배2의 지시에 따라 Lua 같은 미니멀한 디자인의 언어를 찾다가 ‘아 뭔가 다 좀 부족해’(솔직히 그 게임에 쓰기엔 충분하고도 남았다)라는 생각을 하게 되었고, 그 뒤로 언어 구상을…
이르니아 연대기 사이트를 거의 7년 넘게 안 만들고 있는 이유도 비슷하다. 이르니아 연대기 사이트를 만들어야지. 근데 이건 일반적인 RDBMS로 설계하긴 애매한 데이터 모델이야. 온톨로지 데이터베이스가 적당하겠군? 하지만 이미 있는 것들은 쿼리를 위한 DSL이 이미 정해져있네? 난 SQL 싫어서3 완벽한 ORM을 추구하는 사람이야. 객체 맵핑을 쉽게 할 수 있는 온톨로지 데이터베이스를 먼저 만들어야겠군. 그런데 클라이언트 언어로 자연스럽게 쿼리할 수 있게 하려면 Lisp 같이 homoiconic한 언어를 써야겠네? 근데 난 S-expression을 그렇게까지 좋아하지도 않고 마침 내가 구상중인 언어 Metaphor도 homoiconic하지. 아 그럼 일단 Metaphor를 구현해야겠다. 근데 Metaphor는 성능 때문에 그냥 내 취향상 컴파일러를 만들어야겠어. 근데 역시 컴파일러는 GHC처럼 부트스트랩을 해야 개간지가 나잖아? 그럼 일단 Metaphor를 Python으로 최소한의 명세만 구현해놓고, Metaphor로 Metaphor 컴파일러를 구현해야겠네. Python으로 파서를 만드려면 역시 pyparsing이 편한가? 근데 pyparsing의 DSEL은 내 마음에 안들어. 더 깔끔하게 내가 직접 파서 프레임워크를 작성…
이게 내가 5년 넘게 만들어낸 게 별로 없는 이유 같다.
사실 VLAAH 만드려고 Phunctional을 만들었던 것은 아니지만… VLAAH에서 사용하기 위한 기능을 많이 추가한 것은 사실. 근데 PHP 5.3은 어설프지만 람다를 지원하게 되었다. 그래서 Phunctional은 이제 메인테인 안함. ↩
shinvee 님. 지금은 야간개발팀 팀장인데, 그 때는 게임 동아리 선배였다. ↩
근데 내가 회사에서 사용하는 언어의 비율은 SQL이 50%, Python 30%, PHP 20% 정도. 게다가 주로 작성하는 SQL들은 짧은 것들이 아니라 SELECT 문 하나인데 보통 15줄이 넘어가고 길게는 50줄 넘는 경우도 있다. 난 불행하지 않아… 난 행복해… ↩
LESS는 Sass나 CleverCSS와 같은 CSS 전처리기이다. 그런데 내가 볼때는 LESS가 최고다. 이유는 다음과 같다.
이전에 내가 CSS 프레임워크에 대해 이야기했는데, 이미 존재하는 CSS 프레임워크는 아무 것도 건들지 않고 LESS 프레임워크로도 사용 가능해진다.
CSS 프레임워크라는 게 처음 나왔을 적부터 황당해하며, 그 뒤로도 계속 이런 생각을 해왔는데, 막상 이런 물건이 없는 것이 다른 사람들은 나처럼 마크업에 결백증이 별로 없나 싶기도 한다. 꼭 Sass에 의존할 필요 없이, 간단히
.note { /* blueprint: span-7, colborder */ }와 같이 쓰면 해당 주석을 치환하는 전처리기(preprocessor)와 함께 배포해도 될 것 같다. (그렇게 해주면 왠지 정말 프레임워크라는 말에 어울릴 것 같기도 하다.) 누가 이런거 만들어주면 난 정말 애용할텐데.
이제 그럴 필요도 없다. LESS로 이렇게 작성하면 된다.
.note {
.span-7;
.colborder;
}
덧. 근데 이런 좋은 언어는 표준화가 될 필요가 있다. 좋은 테스트 스위트(test suite)도 구비되어야 하고. (내 트위터 업데이트)
오늘(정확히는 6월 27일 어제) 한국 IBM developerWorks 세미나 ‘생산적인 개발 노하우 나누기’ & ‘개발자들의 수다’에서 페차쿠차 발표를 했다. 제목은 “API로 개발 (야비하게) 날로먹기.” 주제는 예상할 수 있듯 상한 떡밥 Open API + 슬라이드를 채우기 위한 약간 관련 없는 것들이었다;; 아무튼 발표 자료를 올려둔다.
아는 사람이 CSS 프레임워크에 대해 어떻게 생각하냐고 물었다. 그래서 난 CSS 프레임워크를 좋아하지 않는다고 대답했다. CSS 프레임워크는 나쁘다. 정확히 말해, 현존하는 CSS 프레임워크들의 구현 방식은 마크업을 더럽힌다.
CSS가 왜 생겼는지 생각해보면 CSS 프레임워크의 구현 방식이 왜 문제가 되는지 쉽게 알 수 있다. 문서의 내용과 그 표현 방식을 구분하기 위한 것이 CSS이다. 그 외에 다른 많은 효율성을 제공해주지만, 그것들은 부수 효과일 뿐이다. 마크업에는 모양에 대한 것이 들어가서는 안된다.1 모든 모양에 관련된 것들은 CSS로 선언한다. 외부 CSS에서 마크업된 문서에 수직적으로 간섭하기 위해 고안된 것이 바로 셀렉터(selector)이다.
그런데 CSS의 본래 의도와 상관 없이, 함께 제공되는 편의를 위해 다음과 같은 마크업을 하는 사람도 있다.
<div class="left">
<p class="red small">Some important note goes here…
저 마크업은 CSS를 과소평과하는 행위다. 결국 아래처럼 해도 똑같다.
<div align="left">
<p><font color="red"><small>Some important note goes here…</small></font>
레이아웃 상에서 해당 부분이 현재는 왼쪽에 있지만, 오른쪽으로 바꿔야 할 경우 마크업에서 div.left를 div.right로 바꾸던가,
.left { text-align: right; }
와 같이 이름과 반대되는 선언으로 CSS를 수정해야 한다. 이 모든 것이 모양을 언급했기 때문에 일어나는 일들이다. 애초에 <font color="red">를 class="red"로 바꾼다거나 align="left"를 class="left"로 바꾼다는 것은 딱 봐도 눈가리고 아웅하는 식의 마크업이 아닌가. CSS를 쓰겠다는 건지 말겠다는 건지 모르겠다.
쓰기 싫으면 차라리 쓰지 마라.
올바른 마크업은 아래와 같이 되어야 한다.
<div class="note">
<p><strong>Some important note goes here…</strong>
하지만 CSS 프레임워크들을 사용하게 되면 결국 마크업에 모양이 잔뜩 섞이게 된다. 가장 유명한 CSS 프레임워크인 Blueprint를 보자. 예제 페이지를 보면 다음과 같은 마크업이 보이는데…
<div class="container">
<h1>A simple sample page</h1>
<hr>
<h2 class="alt">This sample page demonstrates a tiny fraction of what you get with Blueprint.</h2>
<hr>
<div class="span-7 colborder">
<h6>Here's a box</h6>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
</div>
<div class="span-8 colborder">
<h6>And another box</h6>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat laboris nisi ut aliquip.</p>
</div>
<div class="span-7 last">
<h6>This box is aligned with the sidebar</h6>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip.</p>
</div>
<hr>
<hr class="space">
…뭐지? 내 눈에는 span-8이 <font size="어쩌고">로 보인다. 저 마크업은 디자인이 바뀌면 HTML을 처음부터 다시 새로 작성하겠다는 뜻인가.
하지만 그럼에도 CSS 프레임워크의 편리함은 무시하기 힘든 유혹이다(그러니까 쓰겠지). 사실 CSS를 어느 정도 해본 사람은 알겠지만 Internet Explorer 맞추느라 한바탕 고생을 하고 나면 이미 CSS는 * html이니 *+html이니 하는 요상한 해킹들과 편법들로 이미 너덜너덜해진 뒤다. 그렇게라도 되면 다행이고, 과정 자체가 일단 무척 짜증나고 힘들다.
나는 기본적으로 CSS 프레임워크의 필요성 자체에는 동의한다. 하지만 지금과 같은 방식으로는 안된다. 내가 생각하는 CSS 프레임워크의 올바른 구현 방법은, 예전에 한 번 소개한 적 있는 Sass 같은 도구와 함께 쓰는 방식이다.2 Sass에는 mixin 기능이 존재하고, C++의 템플릿과 같이 컴파일 타임에만 영향을 줄 뿐, 실제 생성된 목적 CSS 자체에는 사용된 mixin이 모두 인라이닝된다. 만약 이걸 이용해 CSS 프레임워크를 만든다면 지금처럼 ‘클래스의 집합’이 아니라 ‘mixin의 집합’이 될 것이다. 그럼 사용자는 CSS 프레임워크를 HTML에서 사용하는 대신 CSS 자체에서 호출할 수 있게 된다.
아래와 같이 CSS 프레임워크를 사용하고, (Sass 문법이다. +abc는 abc라는 이름의 mixin을 상속받는다는 뜻.)
.note
+span-7
+colborder
실제 코드에서는 <div class="span-7 colborder">라고 쓰는 대신
<div class="note">
위와 같이 사용하면 된다. 이렇게 되면 마크업의 note 자체는 간접층일 뿐이고, 모양을 바꿀 때는 마크업에 손댈 필요 없이 중간 Sass 코드를 수정하면 된다.
CSS 프레임워크라는 게 처음 나왔을 적부터 황당해하며, 그 뒤로도 계속 이런 생각을 해왔는데, 막상 이런 물건이 없는 것이 다른 사람들은 나처럼 마크업에 결벽증이 별로 없나 싶기도 한다. 꼭 Sass에 의존할 필요 없이, 간단히
.note {
/* blueprint: span-7, colborder */
}
와 같이 쓰면 해당 주석을 치환하는 전처리기(preprocessor)와 함께 배포해도 될 것 같다. (그렇게 해주면 왠지 정말 프레임워크라는 말에 어울릴 것 같기도 하다.) 누가 이런거 만들어주면 난 정말 애용할텐데.
다음과 같은 마크업은 어떨까?
<p style="color: red;">이 문장은 적색으로 보여야 한다.</p>
난 위와 같은 마크업은 괜찮다고 생각한다. 문장 자체가 빨갛다는 것이 내용의 일부라고 보기 때문이다. 하지만 이것은 정말 예외적인 경우이다. ↩
비슷한 것으로 CleverCSS가 있는데, 이것은 Python 구현과 Haskell 구현이 존재한다. 그런데 CleverCSS에는 mixin 기능이 없다. 후배 이흥섭 군이 mixin 기능을 추가한 패치를 만들긴 했는데(게다가 만든지 반년이 다 되어가는데) 이상하게 공개를 안 하고 있다. ↩
Me2PHP를 공개한지 거의 2년이 다 됐다. 사실 작년 중순까지만 해도 API 명세가 바뀌면 최대한 빠르게 반영해왔고, 그런 점 때문에 많은 분들이 Me2PHP를 사용해 주셨다.1 하지만 너무 자주 바뀌는 API 명세에 맞게 유지하기는 점점 힘들어졌고, 근 반 년 정도는 Me2PHP에 손을 놓은 상태였다. 워낙 많은 분들이 Me2PHP를 사용하고 있는 상황이라 애초 마음과 달리 재미가 아닌 의무감에 의한 유지를, 치명적인 버그가 발생했을 때만, 조금씩 해왔을 뿐이다. 그나마도 모든 치명적인 버그를 다 해결한 것도 아니었다.
여담으로, 이참에 미투데이 API의 명세 업데이트에 관한 중요한 문제점을 하나 지적하고 넘어가자. 기본적으로 미투데이의 명세 업데이트는 최대한 하위호환성을 염두하는 듯하다. 하지만 이 하위호환성은 “에러가 나지 않을 정도”의 호환성이지, “버그가 나지 않을 정도”의 호환성을 뜻하지는 않는다. 예를 들어 제공하던 애트리뷰트를 빼려고 하는데, 아예 제거하면 오류가 날 수도 있으니 공백 문자열을 넣어주는 식이다. 난 이런 어중간한 친절함이 항상 좋다고 보진 않는다. 오히려 에러가 났다면 빨리 발견할 수 있는 버그를 오랫동안 남게 만들 수도 있다.
Me2PHP 같은 경우 더 큰 문제가 생긴다. Me2PHP는 객체 모델 맵핑을 하는 디자인이고, 예를 들어 포스팅을 가져오는데도 $person->getPosts(array('offset' => 0, 'limit' => 200, 'from' => '2009-01-01', 'to' => date('Y-m-d')))와 같은 형태를 쓰지 않고 반복자를 사용해 $person->posts->from('2009-01-01')->slice(0, 200)2과 같이 쿼리를 가능하게 한다. 하지만 백조가 헤엄치듯 클라이언트 코드에서만 예쁘고 내부 구현은 지저분하게 되어 있다. 예를 들어 미투데이 API가 예전에는 포스팅을 가져오는데 갯수 제한이 없었지만, 이제는 최대 100개만 가능하게 되었다. 그래서 200개를 가져온다거나, slice()를 생략하고 모든 포스팅을 가져오려고 하면, 내부적으로는 100개씩 가져오는 요청을 알아서 여러번 해야 한다. 최근 미투데이 API 명세가 업데이트되는 것들을 보면, 스트레스 문제 때문인지 이런 식으로 한 요청에서 가능한 범위를 줄이는 것들이 많이 있다. 당연히 Me2API는 기존의 행동을 유지하기 위해 더 지저분한 고려를 내부적으로 하게 된다.
하지만 정말 중요한 문제는 이것이 아니라, 모든 명세의 변경과 그 공지가 일방적이라는 데에 있다. 명세가 변경되면 문서가 바뀐 다음, MDN에 공지 글이 올라온다. 그리고 정말 중요한 변화인 경우 꽃띠앙 님의 미투데이에도 포스팅이 된다. 꼼꼼한, 혹은 나처럼 중간 라이브러리를 만들었기 때문에 의무감이 생긴 개발자라면 명세가 올라오는 스프링노트도 구독하고, MDN 메일도 꼬박꼬박 읽고, 꽃띠앙 님의 미투데이도 자주 확인하겠지만, 그렇지 않다면 업데이트 소식을 모를 수도 있다. 그렇다고 명세를 2년 전 그대로 유지해야 하나.
Me2PHP를 만들고 유지한 경험이 내게 큰 도움을 줬다고 생각한다. VLAAH의 API를 설계하는 데에 있어서. 애초에 VLAAH API는 설계할 때부터 프로토콜 버전을 자연스럽게 올릴 수 있도록 염두를 많이 했다. 클라이언트 라이브러리 개발자, 매시업 개발자로서 유지에 어떠한 고충이 있는지 꽤 많이 겪어본 뒤이기 때문에, 명세를 디자인하는데 이 부분의 우선 순위는 가장 높았다. VLAAH API가 선택한 방법은 HTTP의 내용 협상(content negotiation)과 같은 방식이다. VLAAH API에서는 그것을 프로토콜 협상(protocol negotiation)이라고 한다. 요청시 X-Accept-Protocol: 1.0; q=0.5, 0.9; q=1.0 헤더를 함께 보내면, 이것은 API 0.9를 우선으로 하되, 차선으로 API 1.0 형식도 괜찮다는 뜻이다. 만약 API 0.9가 여전히 지원되고 있다면, X-Protocol: 0.9와 같은 응답을 할 것이다. 그렇지만 API 0.9가 유지되고는 있으나 곧 사라질 운명이라면, X-Protocol: 0.9; expires=2009-12-25와 같은 응답을 하게 된다. 이미 API 0.9의 유지가 끝났다면, X-Protocol: 1.0으로 응답할 것이다. 요청 시에 X-Accept-Protocol 헤더를 보내지 않으면 그것은 X-Accept-Protocol: *; q=1.0과 같다. 항상 최신 버전의 포로토콜로 응답할 것이다.3
그리고 이러한 프로토콜 협상에 관한 사항은 API 문서의 맨 처음에 언급된다. 또한 직접 HTTP 요청 코드를 만드는 대신, 이미 있는 클라이언트 라이브러리를 쓰라고 권고한다. 클라이언트 라이브러리들에는 이미 버전 협상에 관한 것과, 협상에 실패했을 때의 예외 처리, 곧 파기될(deprecated) 프로토콜 버전에 관한 경고에 대해 잘 고려해서 작성되어 있기 때문이다. 현재 있는 클라이언트 라이브러리는 VLAAH-Ruby, vlaah-python 둘 뿐이지만, 모두 해당 부분에 관해 잘 고려되어 있다.
또 다른 문제가 하나 있다면, 회귀 테스트(regression test)가 불가능하다는 점이다. 가능한데 못 찾는게 아니라, 정말 불가능하다. 나는 그렇게 결론지었다. 이것은 꼭 미투데이 API만의 문제는 아니라고 생각한다. API에서 아예 샌드박스(sandbox)를 지원하기 전까지는 회귀 테스트는 요원하다. 그렇지 않으면 일시에 명세가 업데이트될 경우 테스트가 실패하도록 구성할 방법이 없다. 업데이트 공지를 발견하고 부랴부랴 가짜 응답 코드를 고쳐야 하기 때문이다. 아니면 API가 읽기 전용(read only)이여야 한다. 읽는 것만 가능하면(현재 VLAAH API가 그렇다) 그나마 테스트가 가능해진다. 예를 들어 sandbox라는 아이디로 계정을 만들어서 픽스쳐(fixture)를 구성할 수 있다. 하지만 데이터를 조작하는 API는 테스트하기 힘들다. 이를테면 포스팅하는 API를 테스트하고 그것을 삭제해야 하는데, 포스팅과 삭제가 동시에 잘 되는지 둘 다 작동하지 않는지는 가려내기 힘들다. 결정적으로 미투데이는 낙장불입이다. 한번 포스팅을 해버리면 픽스쳐가 영구히 변경된다;;;
여하튼 위와 같은 많은 고충들이 있어서, 결국 나는 손을 놓기로 결정했다! 대신 내 후배인 이흥섭이 프로젝트를 포크했다.
http://bitbucket.org/heungsub/me2pheungp/
프로젝트 이름이 Me2PHeungP랜다. 작명 진짜 구리다. 여하튼 이건 최근의 API 명세를 확실히 반영하고 있다. 앞으로는 이 라이브러리를 이용하길 권한다. 어차피 기존 Me2PHP를 포크했으므로 인터페이스는 동일하게 유지된다. 다만 내가 유지하는게 아니니 디자인이 약간 달라질 수도 있다. 그리고 가끔 나도 패치를 제공할 예정이다. 그러니까, 내 Me2PHP 리파지토리에는 이제 커밋이 되지 않을 예정이다.
주변에 Me2PHP를 쓰는 사람이 있다면 이 소식을 널리; 알려주시기 바란다.
사실 공개된 Me2API 클라이언트 라이브러리 중에서 Me2PHP의 API 디자인이 가장 편하게 되어 있었다(고 자부한다). 대개 HTTP 요청을 추상화하고, 인증키에 nonce를 덧붙여 해싱하는 것을 감싸주는 선에서 얇은 랩핑(wrapping)을 하고 그쳤는데, Me2PHP는 ORM마냥 객체 모델로 아예 맵핑을 한 형태다. 나는 지금도 좋은 클라이언트 라이브러리는 이런 형태여야 한다고 생각하고, VLAAH-Ruby도 그렇게 디자인했다. 그리고 vlaah-python 역시 내가 작성한 것은 아닌데 다행히(!) 그런 디자인이어서 정말 만족했다. ↩
to()가 생략된 것을 볼 수 있다. 생략되었다면 범위는 당연히 현재까지가 된다. ↩
이상의 정교한 프로토콜 협상 방식을 만드는데 강성훈 님의 기술적 조언이 큰 도움이 되었다. 이 분이 vlaah-python을 명세에 의도에 정확하게 부합하는 형태로 만들어 주신 것도, 애초에 API 디자인을 거의 함께 했기 때문. ↩

VLAAH 점수를 이용해서 대결 구도 뷰로 보여주는 애플리케이션을 만들었다. 이름은 VV. “븨븨”라고 읽어주면 된다. VLAAH Versus의 약자다.
http://vlaahversus.appspot.com/
대결 구도라고 하면 잘 모를 수 있는데, 어떤 식으로 나오는지 보면 단번에 알 수 있다.
아래와 같은 것들을 조립해서 만들었다.
사실은 몇달 전에 Ruby에 Sinatra를 이용해서 만들고 있었는데, 중간에 재미가 없어 손을 놓고 까맣게 잊고 있다가 소스 코드를 잃어버리게 됐다. 그래서 이번에 Google App Engine에 올릴 생각으로 다시 만들어본 것이다. 훈련 끝나고 좋은 워밍업이 된 것 같다.
다만 문제가 좀 있는데, GAE에서 외부 URL 접근에 타임아웃 제한이 있다보니, 항목 수가 많아지면 오류가 나버린다. 이건 사용하는 사람이 알아서 살살 피해갈 수밖에 없을 것 같다. 그리고 CSS는 하다가 짜증나서 대충 마무리했다. text-shadow 속성을 쓰는데, 이게 Safari와WebKit과2 Opera에만 구현이 되어 있고, Mozilla Firefox는 3.5 버전부터 추가되기 때문에 글씨가 잘 안 읽힐 수도 있다. Internet Explorer는 처음부터 고려하지 않았다. 심심풀이로 한 건데 스트레스 받기가 싫었다.
여기까지는 인지하고 있는 문제들이고, 이 외에 버그가 있으면 알려주기 바란다.
원래 신변잡기를 좋아하지는 않지만, 블로그에 글을 올린지 오래되서 소식이라도 전하기 위해 쓴다.
최근에 있었던 가장 중요하고 많은 시간을 보냈던 일은 논산 육군훈련소에서의 4주 훈련이다. 4월 9일에 입소하여 5월 7일에 무사히 훈련을 수료하고 퇴소했다. 느낀 점이 매우 많고, 내게 무척 큰 영향을 끼친 경험이었다. 정말로 살인하는 방법에 대해 배운다는 생각이 들었고, 군대라는 조직에 대해서도 조금 이해하게 되었다. 원래 RSS를 구독하고 있던 블로그의 주인공도 우연히 만나게 되었는데, 바로 고어핀드 형이다. 훈련소 전우끼리는 결국 다 친해지긴 하지만, 아무래도 어느 정도 말이 통하는 사람과 특별히 친해질 수밖에 없더라.
그리고 훈련을 받는 동안 내 서버가 사용 기간이 만료되어 죽어있었다. 그리고 오늘에서야 다시 살렸다. 내 서버에 있던 Me2PHP 등도 물론 접속이 안됐고, 후배 이흥섭 군의 홈페이지도 접속이 안됐다고 한다.
입소 전, 훈련중, 입소 후에서도 조금씩 진행하고 있던 일이 하나 있는데, 지금까지 주로 아이디어를 모으고 헐렁한 구상만 해왔던 Metaphor 언어의 명세 작성이다. 아무래도 훈련소에서는 심심할 때가 종종 있기 때문에 연습장에 뭔가 끄적일 일이 잦았는데, 대체로 Metaphor에 관한 것들이었다. 명세 외에도 언어 디자인에 대한 간단한 회고록 같은 것들도 끄적였다. 그나저나 안되는 저질 영어 실력으로 작문하기가 참 힘들다.
그 외에도 나와 관련된 IRC 채널들1의 서버를 HanIRC에서 강성훈 님이 새로 운영을 시작하신 오징어로 옮겼다. 이제 CP949 대신 UTF-8을 쓸 수 있다. 야간개발팀도 둘이나 빠져서2 활동이 지지부진했는데, 다시 활기를 띄기 시작했다. 회사도 오늘 퇴소 후 첫 출근을 다시 했고, 업무를 파악하고 있다.
이제 큰 난관 하나가 해결되었으니, 앞으로는 모든 일이 순조롭게 되길 바라고 있다.
LangDev 채널 #langdev, 이르니아 연대기 채널 #ernia, Vlastic 개발자 채널 #vlastic 등. ↩