Robert Glass의 이 책은 내가 읽은 소프트웨어 엔지니어링에 관한 문헌들에서 자주 인용되기에 꼭 한번 읽어봐야지 하고 생각하고 있던 책이다.
제목 그대로 이 책은 소프트웨어 엔지니어링에 관한 사실들과 오해들을 열거하고, 각각에 관한 논쟁과 저자의 의견들을 정리해놓은 책이다. 각각의 항목들은 다른 유명한 저작들에서 언급된 내용이 많아서 ‘집대성’의 느낌이 든다. 이 책의 가장 큰 목적은 저자가 서론에 언급한대로 ‘기본적이면서도 중요’한 사실들인데도 ‘자주 잊혀지’는 사실들을 사람들이 ‘반복해서 배우게’하는 것일 것이다. 한편으로는 사실들의 원저작에 해당하는 개개의 저작들을 읽을 때와는 다르게, 여러 사실들과 논쟁을 한곳에 정리해놓음으로써, 소프트웨어 엔지니어링에 대한 생각을 정리하고 관점을 만들어낼 수 있도록 도와준다.
대부분의 사실들은 말그대로 ‘사실’로 통용되는 것들이지만, 몇몇은 아직 논쟁거리인 경우도 있고, 개인적으로 동의하고 싶지 않거나 또는 그동안 믿지 않았던 것들도 있었다. 사실 이러한 점들을 읽는 도중에 메모를 해서 정리했어야 하는데, 그러지 못한 것이 후회된다. 하지만, 이 책은 한번 더 읽어볼 기회가 있을 듯하고, 그 때는 반드시 그에 관한 글을 따로 써야겠다.
저자가 채택한 사실에서 몇가지 경향을 찾아본다면, 다음과 같다.
사람이 중요하고 도구와 기술은 그보다 중요하지 않다.
어떤 문제에서든 은탄환은 없다.
소프트웨어 개발 프로세스의 초기 단계들(요구사항, 설계)은 중요하다.
소프트웨어 개발 프로세스의 몇몇 단계들은(테스팅, 검토, 유지보수)은 대단히 중요하지만 과소평가되고 있다.
대규모 또는 범용적인 재사용은 어렵다.
저자는 소프트웨어 엔지니어링 학계에서 널리 받아들여지고 있는 사실들은 단정적으로 받아들이고 있으며, 소프트웨어 엔지니어링에 있어서의 최근의 변화에 대해서는 어느 정도 긍정적인 점들은 인정하기는 하나 약간 망설이는 느낌이 든다. 한마디로 말해 보수적이다. 어떻게 보면, 학계가 바라보는 현재의 소프트웨어 엔지니어링에 대한 시점이 이 책에 그대로 반영되어있다는 느낌이 든다. (이를테면, 학계에서도 인정이 되고 있는 패턴에 대해서는 항목을 하나 할애해서 재사용에 대한 해법 중 하나로 인정하고 있다. 반면 오픈소스나 XP에 대해서는 약간 방어적이다.)
하지만, 그러한 태도가 이 책의 단점이라기보다는 장점이라고 생각한다. 소프트웨어 엔지니어로서 우리는 새로운 기술, 도구, 프랙티스가 우월하므로 도입해야만 한다는 강박관념에 항상 시달려왔다. 우린 오히려 좀 더 신중해야할 필요가 있다.
이 책을 읽으면서 자꾸만 내가 겪었던 현실들이 떠오르면서 나 또는 다른 사람들이 기본적이면서도 중요한 사실들을 얼마나 자주 잊고 있는가를 되돌아보게 되었다. 저자에게 동의하지 않을 수 없는 것은 이러한 사실들은 정말로 반복해서 배우는 수밖에 없을 것이라는 점이다.
Charles Nutter가 Paving the Road to JRuby 1.0: Unicode 글에서 JRuby 1.0에서는 Java와 Ruby 사이에 문자열이 전달될 때는 Ruby 문자열이 UTF-8로 인코딩되어있다고 가정하는 정책으로 가겠다는 의지를 밝혔습니다. Charles Nutter가 설명하는대로, Java 문자열과 Ruby 문자열의 고유한 방식을 보존하는 한, 이러한 방식이 거의 유일한 방식이 아닌가 합니다.
Ruby strings are byte[] and conform to Ruby string semantics
Java strings passing into Ruby code will be encoded as UTF-8, with the implication that you should expect to be working with UTF-8 byte[] in the receiving code
Ruby strings passing out of Ruby into Java libraries will be assumed to be UTF-8, and the resulting string on the Java side of the call will reflect that assumption.
JRuby 0.9.x의 Non-Ascii 문자열 처리 방식에 실망하고, 당분간 아예 Unicode 지원에 대한 의지가 전혀 없는 줄 알았는데, 그나마 다행입니다. JRuby에서의 Ruby 2.x 문자열 구현을 시작한다는데, Ruby 2.x 문자열의 Unicode 지원은 어떻게 될 지 궁금하군요.
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)를 다음과 같이 설정해준다.
리팩토링이 없다면, 소프트웨어의 생명 주기가 진행될 수록 소프트웨어의 질은 점점 떨어질 수 밖에 없다. 현대적인 관점에서 소프트웨어 개발은 더이상 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을 사용하여, 다음과 같은 코드를 사용할 수 있다.
결론부터 얘기하자면, Java의 variable number of arguments(이하 varargs)를 가진 Java 메서드를 JRuby 코드에서 호출하려면, 여러 argument들을 나열하거나 Ruby Array를 사용하면 안되고, Java array 객체를 생성해주어야한다. Java의 varargs는 Java array와 동일하게 취급되므로 당연히 Java array의 경우에도 마찬가지다. 그렇다면, Ruby Array는 무엇이랑 대응될까? 바로 java.util.List다.
다음은 이러한 동작을 테스트하기 위한 간단한 Java 클래스. 위에서 언급한대로, Java array parameter를 varargs로 보면 되겠다.
import java.lang.String; import java.util.List; import java.util.ArrayList; import java.util.Arrays;public class Song {
public enum Category { POP, ROCK, CLASSIC, JAZZ }
private String title; private List<Category> categories = new ArrayList<Category>();
public Song(String title) { this.title = title; }
public void setCategory(Category category) { this.categories.clear(); this.categories.add(category); }
public void setCategoryList(List<Category> categories) { this.categories.clear(); this.categories.addAll(categories); }
public void setCategories(Category[] categories) { this.categories.clear(); this.categories.addAll(Arrays.asList(categories)); }
public String toString() { return "Title: " + title + " Categories: " + categories; }
}
다음은 위의 Java 클래스를 사용하는 JRuby 코드.
require 'java'
include_class 'Song'
song = Song.new 'Waiting On The World To Change' puts song
좀 뒤늦은 소식이지만, Sun의 JDK 5.0 Update 10에 epoll 지원이 들어갔습니다. JDK 6.0에 epoll 지원이 들어가면서 JDK 5.0에도 반영된 것 같습니다. nio가 생길 때 당연히 epoll을 지원하겠거니 생각했는데, 의외로 대단히 늦게 지원하기 시작했군요. 그럼 이전에는 select()나 poll()로 구현되어있었다는 얘기군요. epoll을 사용했을 때의 performance, scalability상의 이점은 Blackdown JVM 1.4.2를 이용한 벤치마크에서도 알 수 있습니다. Sun JDK 6.0의 nio 벤치마크 자료는 모르겠지만, 이희승님의 벤치마크에 의하면 10% 정도 향상이 있었다고 합니다. 어쨌든 희소식입니다. C/C++의 성능 우위를 뒤따라잡기 위해서 Java도 열심히 노력하고 있군요.
Support for epoll
The Linux downloads of this update release include an implementation of java.nio.channels.spi.SelectorProvider that is based on the epoll I/O event notification facility. The epoll facility is available in the Linux 2.6 kernel, and is more scalable than the traditional poll system call. This epoll-based implementation may improve the performance of server applications that use the New I/O API and that register hundreds of channels with a selector. For more information, refer to the epoll(4) and poll(2) man pages.
The epoll-based implementation of SelectorProvider is not selected by default. To select it, specify a property value from the command line as follows: