JRuby String doesn’t seem to be compatible with Java String에서 JRuby 0.9.2는 Non-Ascii 문자를 포함한 Ruby String과 Java String이 서로 호환되지 않음을 지적했습니다. JRuby 0.9.8에서는 이 문제가 더 심각해져서, Non-Ascii 문자를 포함한 String을 제대로 출력하는 것은 Ruby String에서도 Java String에서도 불가능해졌습니다.
이를테면, 지난 번 코드에서 다음과 같은 결과가 나옵니다.
java_string = Foo::getString # yields ‘meme메롱’ in java.lang.String form puts java_string # outputs ‘memeTq’ System.out.println java_string # outputs ‘memeTq’
Java String을 System.out.println을 사용해 출력하는 경우도, Ruby String을 puts로 출력하는 경우도, Non-Ascii 문자 부분인 ‘메롱’은 ‘Tq’로 출력됩니다. ‘Tq’의 정체는 ‘메롱’의 codepoint에서 lower byte 를 취한 값이죠.
JRuby 0.9.2에서는 RubyString이라는 내부 클래스에서 java.lang.CharSequence를 통해 Unicode 문자를 보존하고, 이를 Ruby와 Java에서 서로 다르게 취급한 반면, JRuby 0.9.8에서는 org.jruby.util.ByteList라는 내부 클래스를 사용하며, org.jruby.util.ByteList로 변환하는 과정에서 ByteList.plain 메서드를 통해 lower byte만 취하는 것으로 보입니다.
결과적으로, JRuby 0.9.8에서 Non-Ascii 문자를 포함한 String을 사용하는 것은 어떤 식으로든 불가능해보입니다. 메일링 리스트를 보면, 사실상 byte 배열에 불과한 Ruby String과의 호환성과 Unicode 지원 사이에서 고민하고 있지만, 단순히 lower byte를 취하면 될까’ 또는 ‘Unicode 지원 안하면 문제가 될까’ 식으로 생각하고 있어서 암울해집니다. 제 생각으로는 lower byte만 취하기보다는 UTF-8 인코딩 등을 일관되게 사용해주는 방법이 나을 듯 한데요. 호환성 문제 때문에 상당히 저자세를 취하고 있는 것 같습니다. 자기네들은 별 문제가 없으니까 당연히 별로 심각하게 생각하지는 못하는거겠죠. (lower byte를 취하면 umlaut까지는 보이니까 말이죠.)
Non-Ascii 문자에 관한 문제는 Tim Bray에 의해 JRUBY-65로 간단하게나마 보고되어 있는 문제지만, 코드나 메일링 리스트 논의를 보아하니, 해결은 기약이 없을 듯 하군요.
한국어 환경에서 JRuby (특히 0.9.8) 사용을 심각하게 고려하고 계시다면 당분간은 사용하지 않는 것이 좋을 듯 합니다. 제가 실수하지 않았다면요.
여러 Java 구현이 존재하지만, 아직은 Sun Java SE가 표준 호환성, 기능, 성능, 소스 접근성, 업데이트 주기 등의 면에서 가장 나아보인다. 따라서, 특별한 이유가 있지않은 한, Sun Java SE를 사용하는 것을 추천한다.
Sun의 Java SE는 최근까지 라이센스상 배포에 제약이 있었기 때문에 리눅스 배포판에 기본적으로 포함되지 않았다. 물론, 리눅스 배포판 별로 따로 배포하는 패키지들이 있지만 불편한 것이 사실이다. 이를테면, Ubuntu Linux에서는 기본적으로 GCJ의 인터프리터가 설치되어있지만, Sun의 Java SE을 사용하기 위해서는 multiverse component를 apt repository 리스트에 추가하고 설치해야할 뿐만 아니라, /usr/bin/java의 링크를 사용하기 위해서는 update-alternatives를 사용해서 적절하게 설정해야만 한다. 이 모든 방법들을 사용하더라도, 자기가 사용하고 싶은 최신 버전 패키지가 repository에 존재하지 않을 수도 있다.
조만간 배포판들이 공식적으로 지원하면서 좀 더 편해지겠지만, 아직은 직접 설치하는 것이 여러가지 문제를 피할 수 있어서 오히려 편하다.
Sun Java SE는 http://java.sun.com/javase/downloads/ 에서 배포되며, 현재 릴리즈인 Java SE 6.0은 Latest Release에서, Java SE 5.0은 Previous Releases에서 다운로드 받을 수 있다. 리눅스용 배포는 RPM과 self-extracting executable의 두가지 형태로 배포되는데, 본인은 후자를 선호한다.
Installing Apache Ant
Apache Ant는 Make에 해당하는 Java의 de-facto standard 빌드 툴이다.
Installing JUnit
JUnit은 Java의 de-facto standard 테스팅 툴이다. JUnit 4를 선호하는데, 아직 JUnit 3.8만 패키지로 제공하는 경우가 있어서 따로 설치할 수 밖에 없다. Ant의 JUnit task를 사용하려면, JUnit Task 페이지에 나온대로 약간의 설정이 필요하다. 본인이 선호하는 방법은, $ANT_HOME/lib에 junit.jar 링크를 만들어주는 것이다.
Installing Eclipse
Eclipse는 Java의 de-facto standard IDE이다. 현재 버전은 3.2.2이나, 리눅스에서 프린트를 하려면 3.3 버전의 마일스톤 빌드를 사용할 수 있다. 본인은 3.3 버전을 선호한다.
Setting up Shell Environtment
적절하게 설치한 후 bash용 초기화 스크립트($HOME/.bash_profile 또는 $HOME/.bashrc)를 다음과 같이 설정해준다.
어릴 적엔 부족한 군것질 용돈을 충당하기 위해서 빈병 모아다가 구멍가게에 가져다주곤 했다. 이것이 바로 공병 보증금제도 인데, 소비자가 제품의 비용에 추가적인 비용을 예치(deposit)하고, 나중에 돌려받는다고(refund)해서 Deposit-Refund System이라고 부른다. 현재 대부분의 에스프레소 커피전문점들은 일회용 컵에 대해 이와 같은 ‘환경 보증금’이라는 이름의 제도를 실시하고 있다.
환경 보증금은 물론 좋은 취지의 제도이고, 환경 문제를 항상 환기 시켜주는 등의 긍정적인 역할을 하지만, 환경 보증금 자체가 경제적인 인센티브가 되기는 힘들다.
예를 들어, 평생동안 하루에 한 잔씩 일회용 컵을 사용해 라떼를 마시고, 컵은 사무실에 가져와서 버린다고 해보자. 이 때 포기하는 기회비용은 얼마일까?
50원 x 365일 x 100년 = 182만 5천원
182만 5천원은 꽤 큰 돈이지만 적어도 한 잔에 3000원 이상인 에스프레소 커피를 소비하는 사람의 평생 수입에 비하면, 별 것 아닌 비용이다. 2006년 상장사의 대졸 초임이 약 2906만원이라고 한다. 환경 보증금의 기회비용은 이 사람 수입의 0.06%에 해당한다. 상식적으로, 연봉 3천만원 받는 사람이 지하철 요금에 인색할까? 지하철 요금이 900원임을 감안할 때 과연 50원의 비용에 꿈쩍이나 할까?
물론 이 기회비용의 가치는 상대적이다. 현재의 최저 임금 시간급 3100원을 받는다고 하면 182만 5천원은 주당 40시간 기준 14주를 일해야 벌 수 있는 돈이고, 평생 벌 수 있는 돈의 0.3%나 된다. 역시 상식적으로 시간급 3100원을 받는 사람이 과연 에스프레소 커피를 마음 놓고 매일 마실 수 있을까?
대부분의 사람들이 커피전문점에서 컵을 되돌려주고 보증금을 되돌려 받지 않는 이유는 단순히 충분한 경제적 인센티브가 되지 못하기 때문이다. 50원의 귀중함도 모르냐는 식의 접근은 좀 곤란하다. 차라리 보증금을 인상해보는 것은 어떨까? 여기서 질문. 사람들은 컵을 돌려주는데 얼마 정도의 인센티브가 필요할까? 나라면? 글쎄, 200원-500원 정도? 물론, 이 정도의 인센티브도 어떤 사람들에게는 전혀 인센티브가 되지 못한다.
환경론자들은 환경을 보호하는 행위는 단기적으로도 당신에게 도움이 되는 행동이라고 얘기하는데 열을 올리지만, 몇몇 경우를 제외하고는 환경을 보호하는 행위는 추가적인 비용과 불편함을 동반하는 것이 일반적이라고 생각한다. 물론, 법적인 기반과 경제적인 제도를 사용해 사람들의 행동을 유도하는 것도 단기적으로는 좋지만, 역시 궁극적으로는 환경을 보호하는 것이 중요한 일이라는 합의를 이끌어내고 그것이 매우 자랑스러운 일이라는 의식을 가질 수 있도록 교육하는 일이 가장 중요하다.
저장 용량 대비 저장 장치의 가격은 쉬지 않고 떨어지고 있지만, 저장할 정보의 크기도 커지고 있다. 이러한 추정치와 저장 장비들의 가격을 통해서 정보의 저장에 관한 단기적인 예측이 가능할 것이다. 이를테면, 다음의 문제를 생각해보자. 정보의 크기는 커지고 있지만, 당연하게도 그 가치가 크기에 비례하는 것은 아니다. 저장 기술을 발전시켜서 모든 정보를 저장할 것인가, 아니면, 정보의 가치를 측정할 수 있는 기술을 발전시켜서 불필요한 정보를 저장하지 않을 것인가. 이러한 거시적인 저장 전략은 저장 기술의 진로에 따라 영향 받을 것이다.
물론, 이러한 종류의 예측들은 여러 기관들에서 내놓고 있지만, 공짜는 아니어서 접근하기가 용이하지 않다. 한편으로는, 이러한 예측들을 기관에 의존해야만 하는가 하는 생각도 든다. 이런 종류의 예측을 위해 정보를 수집하려면, 필연적으로 시간에 따른 정보의 조회가 필요한데, 아직 웹과 같은 공공 정보는 시간에 따른 정보의 조회에 적합하게 구조화되어 있지 않다. 대중을 위한 웹기반 정보 서비스는 일반적으로 시간에 따른 정보 제공이 불필요하다. 필연적으로 시간에 따라 구조화된 정보를 소비하고자 하는 사람은 초기에는 어느 정도의 비용을 치루어야만 할 것이다. 하지만, 모든 산업이 그렇듯이 정보의 소비비용은 점점 저렴해질 것이고, 시간에 따라 구조화된 정보의 소비도 오늘날의 웹과 같이 공짜로 개방될 것이다.
예전엔 매뉴얼에서 쉽게 찾아볼 수 있는 단편적인 사실을 물어볼 땐 RTFM이라고 대답하는 것이 일종의 농담처럼 여겨졌지만, 사실은 옳은 방식이다. RTFM을 알고 있어도 그것을 자신의 습관으로 굳히기에는 쉽지 않아보인다. 그것이 바로 단편적인 사실을 물어보는 것에 RTFM이라고 ‘대답해주어야’ 하는 이유다.
같은 이유로 신입자의 문제를 도와줄 때는 모든 답을 제공해주어서는 안된다. 물론, 그렇다고 해서 영원히 시지프스의 노역을 하고 있는 것을 두고보라는 얘기도 아니다. 문제를 해결할 수 있는 길을 보여주고 조용히 지켜봐주는 것이 바로 멘터의 중요한 역할이다. 물론 그 첫걸음은 바로 RTFM이다. (요즘은 ‘Google it’이지만.) 또는, 키가 되는 중요한 사실 하나만을 알려줄 수도 있을 것이다. 이런 식으로, 신입자에게 문제를 해결하는 방법을 익히게 해줄 수 있을 뿐만 아니라, 신입자의 능력 또는 지식을 파악하고 무엇을 가르칠 것인가를 알 수 있다.
사람들이 배우는 방식은 사람들마다 차이가 있다. 어떤 사람들은 단편적인 사실을 배우는 것에만 집착하고, 그것을 어떻게 배워야하는가는 신경쓰지 않는다. 어떤 사람들은 어떻게 배워야하는가를 알려주면, 나머지는 스스로 알아서 배우려고 한다. 단순히 열정의 문제가 아니다.
일반적으로 인간은 Context Switching에 익숙하지 못하며, 그렇게 해야만 하는 경우, 실수를 하거나 비효율적이 되기 쉽다. 회사에서 있었던 일이다. (내게 회사 말고 달리 심각한 사회적 관계가 있겠는가) A와 업무를 대화를 하고 있던 중에 대뜸 B가 자신의 업무를 해결하기 위해 다가와서 A와 대화를 시도한다. 덕분에 나는 멀뚱멀뚱 가만히 있어야하는 상황이 되어버린다. 나는 다른 업무로 돌아갈 수도 없다. 아니, 이메일, 메신저와 같은 비동기적인 통신 방법은 두었다가 무엇하는가.
제 OpenID도 만들고 싶어서, MovableType을 개발하는 Six Apart에서 제공하는 TypeKey를 쓸까하다가, 그냥 myid.net에서 만들었습니다. 제 OpenID는 당연히 lastmind.net (alias of josephjang.myid.net)입니다.
리팩토링이 없다면, 소프트웨어의 생명 주기가 진행될 수록 소프트웨어의 질은 점점 떨어질 수 밖에 없다. 현대적인 관점에서 소프트웨어 개발은 더이상 Rocket Science처럼 고정된 결과물을 산출하는 활동이 아니다. 뛰어난 소프트웨어 아키텍트라 하더라도 소프트웨어 개발 프로젝트의 초기에 그 소프트웨어를 바라보는 방식 (또는 디자인)이 후반에까지 변함없이 지속될 확률은 매우 낮다. 하물며 여러 사람이 함께 일하는 팀 소프트웨어 개발에서야 더이상 말할 것도 없다. 특히, 사용자의 요구사항이 다 떨어져서 더이상 변경할 것이 없어지지 않는 한, 소프트웨어 질의 하락은 점점 빨라질 뿐이다.
이 책은 리팩토링에 관한 바이블이다. Martin Fowler는 더이상 언급할 필요도 없는 유명한 저자다. 이 책은 리팩토링과정 예시를 통한 리팩토링에 관한 소개, 리팩토링의 정의와 중요성, 리팩토링을 언제, 어떻게 적용할 것인가에 관한 가이드, 리팩토링 패턴 카탈로그로 이루어져 있고, 리팩토링의 간략한 역사와 리팩토링의 도입 방법, 리팩토링도구에 관한 의미있는 에세이들으로 끝맺고 있다.
Martin Fowler는 리팩토링을 다음과 같이 정의하고 있다.
Refactoring: a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.
리팩토링의 목적은 소프트웨어를 좀 더 이해하게 쉽게 만들거나 그 과정에서 소프트웨어를 좀 더 이해함으로써 소프트웨어를 수정하는 비용을 줄이는 것이다. 반대로 말하면, 소프트웨어를 수정할 필요가 없다면, 리팩토링을 할 필요는 없다는 얘기가 된다. 또한 여기에는 한가지 가정이 있는데, 소프트웨어를 좀 더 이해하기 쉽다면, 수정하기도 쉽다는 것이다. 당연한 얘기같지만, 많은 개발자들은 어떤 이유에서건 이를 중요하게 생각하지 않는다.
리팩토링의 정의는 리팩토링의 가장 중요한 측면 또한 언급하고 있는데, 바로 겉으로 드러나는 소프트웨어의 동작 방식을 변경하지 않는다는 것이다. 리팩토링 패턴 카탈로그는 바로, 소프트웨어의 동작 방식을 변경하지 않는다는 제약을 위해서 어떤 방식으로 Refactoring을 수행해야하는가에 관한 패턴들을 모아둔 것이라고 볼 수 있다. 그리고, 이러한 제약을 보장하기 위해서 Unit Testing을 제안하고 있는 것이다.
패턴 카탈로그를 읽는 것은 (항상 그렇듯이) 매우 지루한 일이었다. 패턴 카탈로그는 패턴이 갖는 이점들을 제공하지만, 카탈로그를 처음부터 끝까지 읽어볼 정도로 가치가 있지는 않다. 특히, Mechanics 부분은 읽지 않아도 무방하다. 어느 정도 숙련된 개발자라면 리팩토링의 개념만 잘 알고 있다면, Mechanics를 직접 읽어볼 필요 없이 같은 것을 수행할 수 있을 것이다. 특히, 많은 수의 리팩토링 패턴들이 도구를 통해 자동화가 되어있는 현재 시점에서는 더더욱 그러하다. 내가 추천하는 방법은, 패턴의 이름(Name), 맥락(Context) 부분과 동기(Motivation) 부분만 읽고, 이해가 가지 않는 경우에만 예시(Example) 부분을 읽어보는 것이다. 내게는 패턴들을 통해 어휘를 확장시킬 수 있었던 것이 가장 큰 도움이 되었다. 예를 들어, 실제로 리팩토링을 수행할 때, 커밋 로그(Commit Log)에 리팩토링 패턴을 적어넣을 수 있어서 편리했다. 사실, 만약 이 책을 가장 효과적으로 읽고 싶다면, 패턴 카탈로그만 빼고 다 읽고 나서, 패턴 카탈로그들은 이름만 익숙해질 정도로만 훑어보라고 조언해주고 싶다.
패턴 카탈로그 읽는 것을 마치고 수십 페이지 남겨놓은 상태에서는, 마치 이미 이 책을 다 읽었다는 듯 생각을 하고 있었는데, 이 책의 마지막 장들에 들어있는 에세이들은 상당히 중요한 문제 제기와 인식을 제공해주었다.
사실상 리팩토링이라는 단어를 처음으로 만들고 초기부터 연구를 수행한 William Opdyke의 에세이는, 왜 실제 세계에서 프로그래머들은 리팩토링을 하지 않으려하는가를 따져보고, 어떻게 하면 그런 문제를 넘어 리팩토링을 적용할 수 있을 것인가에 관해서 얘기하고 있다. 자세히 설명하기 보다는 다음 문단을 인용하도록 하자.
Within Lucent/Bell Labs I found that encouraging application of reuse and platforms required reaching a variety of stakeholders. It required formulating strategy with executives, organizing leadership team meetings among middle managers, consulting with development projects, and publicizing the benefits of these technologies to broad research and development audiences through seminars and publications. Throughout it was important to train staff in the principles, address near-term benefits, provide ways to reduce overhead, and address how these techniques could be introduced safely. I had gained these insights from my refactoring research.
이 내용은 비단 리팩토링에만 해당되는 것은 아니다. 주로 단기적인 성과를 중요시하는 기업 환경에서, 장기적인 투자를 필요로 하는 무언가를 추구하기 위해서는 위에서 언급한 모든 일들을 할 수 있을 정도로 부단히 노력해야한다는 생각이 든다.
두번째 에세이는 리팩토링 도구에 관한 얘기를 하고 있는데, 이 책이 쓰여진 1999년에는 어느 정도 널리 퍼진 리팩토링 도구가 없었겠지만, 2007년 현재에는 주요 언어인 Java의 주요 IDE들이 리팩토링을 직접 지원하고 있기 때문에, 기술의 변화를 지면을 통해 느낄 수 있게 해준다.
이 책의 마무리에 해당하는 Kent Beck의 에세이는 리팩토링을 할 때 가져야할 마음가짐에 관해서 얘기하고 있는데, 중요한 항목들은 여기에 인용할 가치가 있을 것이다.
Get used to picking a goal
Stop when you are unsure
Backtrack
Duets
이 책을 읽기 시작하면서 바로 떠오른 생각은 패턴들로 리팩토링하는 리팩토링 패턴이 이 책에는 별로 없다는 것이다. 저자는 이 책에서 리팩토링 패턴의 가장 작은 단위들을 우선적으로 다루고 싶어했고, 그 목적은 의미가 있었던 것 같다. 그리고 이 책에서도 같은 아이디어를 여러번 언급하고 있다. 실제로 이 아이디어는 2004년에 출판된 Refactoring to Patterns라는 책으로 실현되었다. 시간이 되는대로 Refactoring to Patterns를 읽어볼 예정이다.
JRuby String 즉, JRuby 상에서의 Ruby String과 Java String이 서로 호환되지 않는 문제를 발견했다. ASCII의 경우에는 별 문제가 없지만, 한글의 경우에는 깨지는 문제가 발생한다. JRuby 사용자가 별로 없어서 이 문제가 내 환경에 국한된 문제인지 실제로 JRuby의 문제인지 확인은 할 수 없다. JRuby 코드를 약간 들여다보아야 할 것 같다.
이 문제를 테스트하기 위해서는 다음의 Ruby 코드를 사용하면 된다. Java String은 System.out.println으로 출력했을 때만, Ruby String은 puts로 출력했을 때만 정상적으로 출력된다. 서로 호환이 된다면, 모두 정상적으로 출력되어야할 것이다.
public class Foo { public static String getString() { return new String("meme메롱"); } }
이 문제를 피해가기 위한 한가지 workaround는 ASCII 문자로 된 intermediate form을 두고 Java String과 Ruby String을 서로 변환하는 것이다. intermediate form은 어떤 것을 사용해도 되지만 percent encoding을 사용하여, 다음과 같은 코드를 사용할 수 있다.