저자: 엘리어트 러스티 해롤드(Elliotte Rusty Harold), 역 한빛리포터 이상화
지난 몇 년간 리팩토링(refactoring, 메소드와 클래스들의 이름을 새롭게 바꿔주어 점진적으로 코드 기반을 향상시키는 과정, 일반적인 기능을 새로운 메소드나 클래스들로 끌어내는 것, 그리고 대부분의 1.0 시스템에서 뒤죽박죽인 상속 관계를 깨끗이 정리하는 것)은 많은 지지(adherents)를 얻고 있다. Eclipse와 IDEA 같은 통합 개발 환경(IDEs)은 이제 자동적으로 리팩터(refactor) 코드를 만들어 주고 있다.
그렇지만 코드가 리팩토링을 필요로 하지 않는다면 무슨 상관인가? 언어(language) 자체가 모순(inconsistencies), 비능률성, 고쳐져야 할 명백한 바보 같은 행동을 가지고 있다고 해도 무슨 상관인가? 이러한 점을 살펴볼 때 자바 전체는 여타의 다른 코드 기반과 꼭 닮았다고 할 수 있다. 자바에는 약간의 훌륭한 부분, 기능적인 부분, 그리고 모두다 머리를 긁으면서 "도대체 무슨 생각을 가지고 있는 걸까?"라는 질문을 던질 수 있는 부분들을 골고루 가지고 있다.
제임스 고슬링(James Gosling)이 OAK에서 일을 시작 한지 이제 11년 정도, 그의 언어(language)는 결국 자바가 되었고, 썬이 처음 자바를 공개(1995년) 한지 7년이 되었다. 언어(language), 클래스 라이브러리, 가상 머신(VM)으로 이루어진 ‘자바‘는 이제 나이에 어울리는 행동을 보여주고 있다. 자바에는 대부분의 사람들이 동의하고 있듯이 수정해야 할 부분이 많았지만 호환성을 역행(backwards)한다는 이유로 고칠 수 없었다. 지금까지 자바 수정사항들은 ’하위(upwards) 호환성‘을 유지하기 위해 시도해온 것이었다. 따라서 모든 초기 코드들은 이후 자바 버전에서도 변경 없이 실행되어야 한다. 이로 인해 자바에서 이루어져야 할 많은 수정사항들에 제한이 가해졌으며 썬이 이와 같은 명백한 문제점들을 수정하는 것을 방해해 왔다.
이 기사는 지난 10년 간의 짐들을 버린 ‘자바 3’를 상상하고 있으며, 핵심 언어(language), 가상 머신(VM), 클래스 라이브러리에 대한 많은 변화를 제안하고 있다. 여기서는 주로 호환성 역행의 이유로 많은 사람(썬의 많은 개발자를 포함)들이 정말 원했지만 할 수 없었던 변화에 초점을 맞추어 기사를 작성하였다.
본 기사에서 필자는 특별히 현재 Java 2에 포함된 새로운 모습(아마도 유용하게 쓰이는)들에는 초점을 맞추지 않을 것이다. 그런 것들은 JCP(Java Community Process)를 통해서 충분히 이루어 질 수 있기 때문이다. 그 대신 필자는 오늘날 자바가 하고 있는 일을 우리가 어떻게 똑같이 할 수 있는지에 대해 초점을 기울여 본 기사를 작성해나갈 것이다. 예를 들어 내가 자바 언어의 기본적인 부분으로서 데이터 타입에 대해서 보기를 원한다면 기존의 코드를 변경하지 않고 Java 1.5에 추가될 수 있다는 것을 뜻한다. 반면 기존의 char 타입을 2 바이트에서 4 바이트로 변경할 경우 기존 코드와 전혀 호환되지 않을 것이다.
마찬가지로, 나는 오늘날 우리가 좋아하고 잘 알고 있는 언어(language)로 자바가 남아 있기를 바라고 있다. 필자는 언어(language)에 대한 리팩토링(refactoring)에 대해서만 얘기하고 언어의 재창작(reinventing)에 대해서는 언급하지 않겠다. 코드 라인 끝에 붙이는 세미콜론(;)이나 코드 들여 쓰기 같은 단순한 문장 구성법에는 관심이 없다. 이러한 종류의 변화들은 파이썬과 F같은 다른 언어를 위한 바이트 코드 컴파일러에 의해서 쉽게 수행되어 질 수 있다. 다행스럽게도 그런 컴파일러는 현재 존재하고 있다. 이 기사에서 필자가 설명하고자 하는 변화들은 좀더 근본적인 것으로 종종 언어(language), 라이브러리, 그리고 가상 머신(VM)에 대한 경계들을 넘나들 것이다. 이런 것들을 염두에 두고 Java 3에 대한 refactorizations의 10가지 목록을 살펴보자. (중복(redundancy)의 결여와 단순성(simplicity)의 용어 정의를 위해서 고슬링의
"Design Principles" 슬라이드를 참고 할 것)
10. 사장된(deprecated) 메소드, 필드, 클래스, 인터페이스들을 지우자
별로 어렵지 않은 일이다. Java 1.4.0에서는 22개의 사장된 클래스, 8개의 사장된 인터페이스, 50개의 사장된 필드, 그리고 300개가 넘는 사장된 메소드와 생성자들이 있다.
List.preferredSize(),
Date.parseDate()을 비롯한 몇몇은 그것과 동일하거나 같은 일을 수행하는 더 좋은 메소드들이 있기 때문에 사장되었다.
Thread.stop(),
Thread.resume()과 같은 몇몇은 처음부터 좋은 생각이 아니었고 위험해 질 수 있기 때문에 사장되었다. 사장된 메소드의 이유가 무엇이든 간에 우리에게는 실제로 그것을 사용해야 할 의무가 없다.
썬의
공식적인 방침은 "현재 사장된 메소드와 클래스를 시스템에서 완전하게 제거할 계획은 없지만, 프로그램을 수정하여 사장된 메소드와 클래스들의 제거를 권고한다"는 입장이다. 이제 새롭게 시작할 때가 온 것이다. 이로 인해 자바는 더욱더 간단하고 단순하면서 안전하게 될 수 있다.
9. 잘못된 이름붙이기 관습을 고치자
코드 가독성과 관련된 자바의 공헌 중에 하나는 비록 컴파일러에 의해 강제적이지는 않지만 일관성 있는 이름이었다. 클래스 이름들은 모두 대문자로 시작하는 명사였다. 반면 필드, 변수, 메소드들은 소문자로 시작한다. 이름이 정해진 상수들은 단어 사이에 "_"로 나뉘어진 모든 대문자로 쓰여있다. 따라서 경험을 갖춘 자바 프로그래머들의 코드와 내가 작성한 것은 같을 수 밖에 없다고 장담할 수 있다.
그러나 처음 Java 1.0이 작성되었을 때 모든 프로그래머들이 자바의 이름 관습에 대해서 받아들였던건 아니었다. API를 통해서 별 것 아니지만 귀찮은 많은 모순들이 존재했다. 예를 들어 color 상수들은
Color.RED,
Color.BLUE,
Color.GREEN 대신에
Color.red,
Color.blue,
Color.green이다. Java 1.4에서 결국 대문자화 된 상수가 더해졌지만 여전히 틀린 소문자 버전들이 존재했기 때문에, 이러한 클래스에서 필드(field)의 수가 두 배로 증가하게 되었다. 이러한 모순들은 반드시 목록이 만들어져야하고 수정되어야 한다.
자바의 다른 유용한 코딩 관습은 생략하지 않은 전체 이름을 사용한다는 것이다. 그러나 대부분의 자바 메소드들 중에 일부는 생략되어 있다. 예를 들면
System.collectGarbage() 대신
System.gc()을 쓰고 있는 것을 들 수 있다. 이렇게 한다고 해서 이런 메소드가 12자리의 글자를 치는 것보다 생략하는 것이 마치 시간을 절약하는 것처럼 보인다는 것을 말하는 것은 아니다. 마찬가지로
InetAddress 클래스도 실제로는
InternetAddress로 명명되어야 한다.
이러한 것을 따라서
javax 패키지에 있는 JDBC로 옮겨가 보자. JDBC는 중요하지만 거의 핵심적인 언어(† 역자 주: 기본적인 java 패키지에 포함되어 있는 것들)는 아니다. JDBC가
javax에 없는 유일한 이유는 JDBC가 이미 Java 1.1에 있는 JDK에 처음으로 포함되었고 그때는
javax 이름 관습이 없었기 때문이다. 따라서 JDBC를 가지고 작업하는 프로그래머는 아직도 그렇게 쓰고 있고 대부분의 사람들도 그것을 간단히 무시할 수 있다.
8. 기본 데이터형(primitive data types)을 제거하자
이것은 의심할 필요도 없이 가장 논쟁의 여지가 있는 제안이다.
int,
float,
double,
char, 이 외에 다른 타입들을 완전히 없애자는 얘기가 아니다. 단지 클래스, 메소드, 상속, 등등과 같이 완전한 객체로 만들고 싶을 뿐이다. 이것은 자바의 타입(type) 시스템을 좀더 명확하게 만들 것이다. 우리는 더 이상 기본 데이터형을 리스트(list)나 해시 테이블(Hash table)에 추가하기 위해서 타입-래퍼(wapper) 클래스를 사용할 필요는 없다. 모든 변수와 데이터에서 작동하는 메소드를 쓸 수 있기 때문이다. 모든 타입은 클래스가 되며 모든 클래스는 타입이 될 것이다. 또한 모든 변수, 필드, 매개변수들도
Object의 인스턴스가 될 것이다. 따라서 자바는 결국 완전한 객체 지향 언어가 될 것이다.
자바가 초기에 기본 데이터 타입을 쓴 이유는 속도 때문이었다. 특히 스몰토크(Smaltalk)와 같은 완전한 객체 지향 언어가 코드를 느리게 생산한다고 주장했기 때문이다. 그러나 무어의 법칙이 적용된 7년 후, 컴퓨터는 매우 빨라지고 예전보다 더 큰 메모리를 가지게 되었다. 더욱더 중요한 것은, 컴파일러 기술이 적절한 선에서 기본 데이터 타입의 바이트 코드를 객체 기반의 코드로 바꾸는 것이 어렵지 않은 수준까지 발전해왔다는 것이다. 이미 Modern Eiffel, C#, 스몰토크(Smalltalk) 컴파일러가 이런 일을 하고 있다. 본질적으로, 좋은 컴파일러는
ints나
BigIntegers를 사용할 때 둘 사이를 투명성 있게 바꿀 수 있어야 한다.
새로운
byte,
int,
long,
double,
float,
char 클래스는 오늘날과 똑같은 문자적 형태를 가질 것이다.
String s ="Hello"가
String 객체를 생성하는 것처럼
int i = 23은 새로운
int 객체를 생성할 것이다. 마찬가지로 컴파일러는
+,
-,
*와 같은 통상적인 연산자들을 인식하여 클래스 안에 있는 적절한 메소드로 지정할 것이다. 이것은 컴파일러가 문자열을 연결하기 위한 + 연산자를 처음으로 인식하는 것보다 더 복잡하지 않다. 대부분 기존의 수학적 코드는 지금과 똑같이 작동할 것이다.
int/char/double/float/boolean 객체들은 불변할 것이기 때문에 이러한 객체들은 스레드에 안전할 수 있고, 메모리를 저장하는데 사용될 수 있을 것이다. 이 클래스는 아마도 안전과 성능을 위해서 final(† 역자 주: 클래스 지정자로 더 이상 확장이 불가능한 클래스를 뜻함)이 될 것이다.
나는 또한 자바의 수학적인 법칙들이 맞는지 생각하고 있다. float 연산은 IEEE 754에 정의되었고 다른 언어(language)와 하드웨어의 호환성을 위해 이를 지키는 것은 아주 중요하다. 그러나 정수 타입은 개량될 여지가 남아있다. 2조 더하기 2조가 -294,967,296가 된다고 한 사실과 같이 수학적으로 틀렸고, 현재의 자바에서도 아직까지 고쳐지지 않았기 때문이다.
적어도 크기에 상관없는 하나의 정수 타입이 존재해야 하고, 아마도 그것은 기본 타입이 되어야 할 것이다. 만약 그렇게 된다면
short,
int,
long 타입을 손쉽게 포괄할 수 있을 것이다.
byte 타입은 여전히 I/O를 위해서 필요하다. 그리고 드물지만 비트 단위 조작이 꼭 필요한 이미지 필터에 남아 있어야 한다. 그렇지만 정수형 위에서 비트 연산자
<<와
&는 인터페이스와 함께 수행을 혼란스럽게 하고 객체 지향의 근본적인 법칙을 위반한다. 자바 API전체에 사용되는
Font.BOLD와
SelectionKey.OP_ACCEPT와 같은 다양한 비트 상수들은 타입에 상관없는
enums 자료형의 getter, setter(† 역자 주: 값을 가져오거나 저장하기 위한 사용자 정의 메소드) 메소드들로 대체되어야 한다.
결국 정수형은 수학적인 조작을 위한 것, 바이트형은 메모리 조작을 위한 것이 되어야 한다는 이야기이다. 반대로 어쩌면 바이트형 위에서의 수학적인 연산을 금지해야 할 것이다. 지금도 2 바이트를 더하는 것은 가상 머신(VM)이 int형 보다 더 세밀한 타입을 지원하지 않기 때문에 자동적으로 그들을 ints로 바꾸어준다.
이러한 계획들이 여타의 완전한 객체 지향 언어에서 효율적으로 수행되어지고 있다는 상당한 증거가 있다. 그럼에도 불구하고 이러한 개념은 성능을 중시하는 사람들로부터 상당한 저항을 받을 것이다. 이런 실행은 기존의 자바 코드(이미 메가바이트에 대해 인색하지 않은) 보다 메모리를 더 요구한다. 이것은 특히 J2ME나 소형의 환경에서 특별한 문제로 대두될 것이다. 따라서 J2ME는 아마도 J2SE나 J2EE와는 다른 길을 선택할 것이다.
J2ME는 기본형과 객체형으로 나뉘며, 수학적으로 2+2=-1, 모든 문제를 필연적으로 수반하는 Java 2 개발 기반을 계속할 것이다. 이러한 환경 속에서 이동(† 역자 주: 양분된 환경에서의 이동)의 가치는 비용보다 중요하지 않다. 그러나 자바는 더 이상 값싼(과거에도 절대 저렴했던 적은 없었지만...) 셋탑 박스(† 역자 주: 자바는 초기 셋탑 박스(유선 티비를 수신하기 위한 장치)를 위한 언어였음)가 아니다. 데스크탑과 서버가 요구하는 사항이 휴대폰과 전자시계가 요구하는 것과는 이제 더 이상 같지 않다. 이러한 환경에서 프로그래머들은 그것들을 위해 각각 입맛대로 맞춘 언어를 원한다. 하나의 사이즈를 가지고는 모두에게 맞지 않는다.
7. chars형을 4 바이트로 늘리자
char 타입이 기본형이건 객체 이건 간에, 유니코드가 2 바이트 문자 집합이 아니라는 것은 사실이다. 이것은 유니코드가 다국어 계획 이외에도 다음 세대에까지 실현될 가능성을 단지 이론적으로만 가지고 있기 때문에 중요하게 취급되지는 않을 것이다. 그러나 3.2버전에서 유니코드는 2바이트를 넘는 약 30,000 개의 문자들을 가지고 있다. 4바이트 문자들은 많은 수학적 기호와 대부분의 악보 기호들을 포함한다. 미래에 유니코드는 Tolkien"s Tengwar, Linear B를 비롯한 사용되지 않는 언어들도 포함할 것이다. 현재, 자바는 대리 쌍(surrogate pairs)을 통해서 이 문제에 도전해 보기를 시도했지만 이러한 문제를 다루기 위한 시도는 정말 귀찮을 뿐만 아니라 이미 이러한 귀찮은 문제를 다루어야 하는 XML parser와 같은 시스템에서 주요한 문제를 일으켰다.
자바가
char 타입을 객체로 만들건 아니든 간에 4바이트로 문자 모델을 채택하는 것은 필요하다. 만약 자바가 완전한 객체 지향 타입으로 간다면, 자바는 여전히 내부적으로 UTF-16 혹은 UTF-8을 공간을 절약하기 위한
chars와
strings를 위해서 사용할 수 있기 때문이다. 외부적으로는 모든 문자들은 똑같이 생성되야 한다. 어떤 경우에는 2개의
char로 표현해야 할 경우도 있지만 이는 너무 혼란스러우며 대부분의 문자를 표현하기 위해서는 하나의
char만으로도 괜찮다. 문자열에 악보 기호나 수학적 기호를 표현하기 위해 유니코드 전문가가 될 필요는 없다.
6. 쓰레드를 고치자
자바는 특별한 목적의 애드온(add-on) 라이브러리 보다 근본적인 특징을 가지고 멀티스레딩을 통합한 최초의 언어이다. 따라서 이 분야에서 많은 설계자들이 약간의 실수와 잘못된 단계를 밟았다고 해도 이는 전혀 놀랄 일이 아니며 이러한 모든 것들은 다음과 같이 수정되어야 한다.
- 썬의 조슈아 브로크(Joshua Bloch)는 “스레드 그룹들은 매우 성공하지 못한 실험으로 보여진다. 따라서 그들의 존재를 단순히 무시할 수도 있다."(Effective Java, Addison-Wesley, 2001)라고 썼다. 썬은 자신들이 제공하기로 계획했던 보안을 제공하지도 못했고, 이미 제공한 소수의 기능들은 쉽게 Thread 클래스 자체로 옮겨질 수 있다.
- stop(), suspend(), resume() 메소드들은 잠재적으로 객체를 모순된 상태로 만들 수 있기 때문에 완전히 사장되었다. 따라서 Thread 클래스로부터 완전하게 제거 되어야 한다.
- destroy() 메소드는 실행되지 않는다. API를 어지럽히므로 제거해야 한다.
- 자바 메모리 모델이 ‘스레드의 시맨틱스(semantics), 락(lock), volatile 변수(† 역자 주: 변수를 다룰 때마다 동기화를 시킴), 데이터 race’에 대한 지원이 잘 되지 않는 것은 널리 알려지게 되었다. 사실, 이것을 고치기 위해 JCP 안에서 doubles와 longs의 비원자적인(non-atomic) 성질은 64비트 연산을 효율적으로 할 수 없는 구조에서는 좋을 수 있다(† 역자 주: 보통 하드웨어에서는 몇몇 연산에 대해서 64비트를 32비트 2개로 나누어서 수행). 이것은 요즘들어서는 과거에 그랬던 것처럼 그리 중요한 논쟁거리는 아니지만 몇몇 VM들은 이에 대해서 아직도 장점을 가지고 있다. 만약 이러한 타입들이 객체로 만들어지지 않는다면, 그것들은 다른 단일 바이트 타입들처럼 원자성을 가져야 한다.
- 마지막으로, 우리는 모니터(† 역자 주: OS 수준에서 Thread를 관리하기 위한 방법)들이 객체가 다른 연산을 위한 다중 모니터를 가질 수 있도록 객체를 분리할 수도 있다는 가능성에 대해서 심각하게 생각해 봐야 한다. 필자는 스레드 전문가는 아니다(물론 스레드 전문가 행세를 해야 할 때마다 당황한다). 이러한 점들에 대해서 양쪽의 논쟁을 들어왔지만 그들 대부분은 나의 한계를 넘는 것이었다. 만약 자바 스레드를 재설계 해야 한다면, 우리는 이러한 논의들을 영양가 없는 잡담에서 진지한 논의로 옮겨서 양쪽을 화해시키는 방법에 대해 생각해 봐야 할 것이다.
이러한 변화들은 상당히 까다롭고, 그것들은 모든 3가지 레벨(API, 언어 명세서, VM)의 변화를 요구한다. 그러나 자바가 미래의 멀티 프로세스 시스템에서 신뢰할 수 있고 효과적으로 남아 있으려면 이것들은 중요한 요소이다.
5. 파일 포맷을 XML로 변환하자
이미 JCP에서는 Servlet 환경 설정 파일과 Ant 빌드(build) 파일과 같은 차세대의 파일 포맷인 XML을 사용하고 있다. XML은 간결하고, 편집하기 쉽고, 쉽게 파싱(parse)할 수 있을 뿐만 아니라 디버깅이 쉽다. 새로운 파일 포맷을 설계할 때 대부분의 프로그래머들의 선택한 첫 번째는 바로 XML이었다. 물론 XML은 자바가 발표된 이후 2년 정도가 지나서 개발되었다. 그래서 자바는 XML로 포팅되지 못하는 파일 포맷들을 여럿 가지고 있다. 이런 파일들에는 JAR파일의 요약 정보(manifest) 파일이나, 속성(property) 파일, 직렬화된 객체도 포함되어 있다. 이런 것들 모두는 반드시 잘 정의된(well-formed) XML로 될 수 있어야 한다.
직렬화된 객체는 이진 데이터이고 XML은 텍스트이기 때문에 XML로 직렬화된 객체는 아마도 가장 놀랄만한 제안일 것이다. 그러나 대부분에 객체 안에 존재하는 데이터는 단지 소문자인 텍스트와 숫자들이며 이것 모두는 XML로 지원될 수 있다. 자바 객체 안에 있는 제한된 이진 데이터는 쉽게 Base-64 인코딩 방법으로 변환될 수 있다. 가장 놀라운 사실은 이러한 결과의 포맷이 오늘날의 이진 직렬화보다 더 작고 빠르다는 것이다. 많은 개발자들이 벌써부터 객체 직렬화를 위한 맞춤 XML 포맷들을 개발했다. 그리고 이러한 것들 중의 많은 것이 자바의 이진 포맷보다 더 효과적이란 것도 증명되었다. 사실, 일반적인 믿음과는 반대로 이진 포맷은 동일한 텍스트 포맷보다 반드시 작거나 빠르지 않다. 그리고 직렬화된 자바 객체는 특히 최적화되지 못한 이진 포맷이라고 할 수 있다. 썬은 이미 Java 1.4에서
java.beans.XMLEncoder와
java.beans.XMLDecoder 클래스를 만들어 자바 빈즈를 위한 XML 기반 직렬화 포맷을 수행하고 있다. 이제 모든 직렬화된 객체를 다루기 위해서 다음 단계를 밟아가야 한다.
4. AWT를 버리자
두 개의 GUI API는 너무 많다. 대부분의 자바 개발자들은 스윙(Swing)으로 자신의 작업을 통일시킨다. 나도 그들과 같다.
Component와
JComponent 클래스,
Frame과
JFrame 클래스,
Menu와
JMenu 클래스 등등을 이제는 통합해야 할 시간이다. 어떤 경우 클래스들은 Swing(
JPasswordField,
JTable)으로부터 나왔으며 그 외 나머지 다른 것들은 AWT(
Font,
Color, 등등)로부터 왔다. 여전히 다른 것들(
Frame,
JFrame)은 통합되어야 하며 전형적으로 대부분 코드들은 Swing으로부터 가지고 와야 하지만 대부분 명확한 AWT 이름은 남겨두어야 한다. 종합적으로 이것은 자바에서 GUI 개발을 대단히 간소화시킬 것이며 주목할만하게 자바의 크기를 줄일 것이다.
우리가 이런 것에 열중하는 동안, Java 1.0 이벤트 모델의 잔재들을 제거해야 할 때이다. 모든 컴포넌트가 더 이상
handleEvent(),
mouseDown(),
keyDown(),
action(), 비슷한 이벤트와 같은 일련의 혼란시키는 것들을 가지고 있어야 할 이유가 없다. 만약 이것들이 여전히 하부구조의 일부로 구조 뒤에서 사용되고 있다면 적어도 그들을 비공개(non-public)로 생성해야 하겠지만 그것들은 많은 노력을 들이지 않고도 완전하게 제거될 수 있을 것이라 생각된다.
3. 컬렉션(collection)들을 합리적으로 설명하자
자바의 현재 컬렉션 API들은 여러 시간들에 걸쳐 다른 설계로 이루어져 있기 때문에 혼동 그 자체이다. 어떤 클래스는(
Hashtable,
Vector) 스레드에서 안전하지만 어떤 것들은(
LinkedList,
HashMap)은 그렇지 못하다. 어떤 컬렉션은 누락된 요소가 요청되면 널(null)을 리턴 하지만 다른 것들은 예외를 발생시킨다. 다른 방법보다도 어떤 표준화된 어법과 비유를 결정한 후, 이에 맞게 모든 클래스를 설계하자. 아마도 이것을 할 수 있는 가장 쉬운 방법은
Vector와
Hashtable을 완전하게 없애는 것이다.
ArrayList는
Vector가 할 수 있는 모든 일을 할 수 있고
HashMap은
Hashtable을 대체할 수 있다.
2. I/O를 재설계 하자
원래 Java 개발자들은 유닉스 프로그래머, 윈도우 사용자, 맥 애호자들이었다. 그들이 개발한 I/O API는 어쨌든 간에 다소 유닉스 중심적이었다. 예를 들면 처음에 그들은 단일 루트(root)를 가지는 파일 시스템을 가정했다. 이것은 유닉스에서는 맞는 얘기지만 윈도우나 맥에서는 아니다. 새롭거나 이전 I/O API들은 파일의 완전한 내용을 스트림으로 접근할 수 있다고 가정하고 있다(이는 윈도우와 유닉스에서는 맞지만 맥에서는 아님).
특별히 국제화에 관한 몇몇 문제들은 Java 1.1에서
Reader와
Writer 클래스와 그들의 하위 클래스로 고쳐졌다. Java 1.2 에서는 파일 시스템 API의 불완전한 것을 명백하게 고쳤다. 여전히 새로운 I/O API와 함께 Java 1.4에서도 많은 것들이 수정되고 있다.
아직 작업이 완전하게 끝난 것은 아니다. 예를 들어 심지어 Java 1.4에서도 파일을 복사하고 이동(여러분도 동의할만한 아주 기본적인 연산)하기 위한 신뢰 있는 방법을 가지고 있지 못하다. 더 좋은 파일 시스템 API를 위한 설계 시도는 이전의 Java 1.0 I/O와의 하위 호환성을 유지하기 위한 요구 때문에 침몰하고 있다.
java.io.의 모든 것에 대해 재고를 해야 할 시간이 왔다. 가장 급하게 필요한 몇몇 변화는 다음과 같다.
- File 클래스는 파일의 이름을 나타내기보다는 파일 시스템에 있는 실제 파일을 나타내야 한다. 또한 반드시 파일의 메타데이터에 대한 완벽한 접근, 다양한 이름 관습 지원, 복사, 이동, 삭제 일반적인 파일 그 자체에 대한 연산 허용, 단지 한 바이트만 차지하고 있는 파일의 인식까지 제공해야 한다.
- PrintStream은 예기치 못한 불행이다. 이것은 PrintWriters가 대신할 수 있는 System.out과 System.err가 반드시 제거 되어야 하는 것을 의미한다. (썬은 이러한 변화를 Java 1.1에서부터 계획했지만, 결정된다면 기존 코드의 많은 수가 멈추게 될 것이다.)
- DataInputStream과 DataOutputStream에 있는 readUTF()와 writeUTF() 메소드들은 실제로 UTF-8을 지원하지 않는다. 이것들이 지원하는 것은 실제 UTF의 90%와 meat-byproduct의 10% 정도이다. 실제로 그들이 지원하는 포맷에는 이상이 없다. 예외는 이것이 UTF-8을 읽고 쓰기 위해 사용할 때 경험 없는 사용자에게 문제를 일으킨다는 것이다. 그것은 그들이 다른 언어로부터 conformant 소프트웨어를 가지고 데이터를 교환할 때 코드가 멈추는 것에 대해서 이상하게 생각할 것이다(† 역자 주: 아시아 언어에서 주로 발생한다). 이런 메소드는 반드시 readString()이나 writeString()로 이름이 바뀌어야 한다.
- Reader와 Writer 클래스는 Writer가 안전하게 출력할 수 있도록 도움을 주는 getCharacterSet()이 반드시 필요하다.
- 인코딩은 자바에서 이름을 만든 8859_1과 UTF16가 아니라 반드시 IANA에 등록된 이름인 ISO-8859-1 나 UTF-16 과 같은 것으로 식별되어야 한다.
- 버퍼링 I/O는 프로그램이 만들 수 있는 가장 중요한 성능 최적화들 중의 하나이다. 따라서 반드시 기본적으로 설정되어 있어야 한다. 기본적인 InputStream, OutputStream, Reader, Writer 클래스는 그것들을 BufferedInputStream/ BufferedOutputStream/ BufferedReader/ BufferedWriter로 연결하여 버퍼링 하기보다는 그들 자신의 내부 버퍼를 가져야 한다. 필터는 스트림이 자신의 내부 버퍼 사용 여부에 앞서 연결된 스트림이 존재하는지에 대해서 검사할 수 있다.
1. 클래스 로딩(loading)에 대해 인간 인터페이스 요소를 염두하며 출발점에서부터 재설계 하자
새로운 사용자에게 클래스 패스처럼 혼란스러운 단독 주제는 없다. 매일 받는 초보자들의 이메일 중에는 그들이 보고 있는 에러 메시지 "Exception in thread "main" java.lang.NoClassDefFoundError: HelloWorld"에 대한 설명을 부탁하는 내용이 주로 많다. 나는 7년 동안 자바를 사용했지만 여전히 종종 클래스 로더 부분은 당황스럽다. (깜짝 퀴즈: 인터페이스 B를 상속한 클래스 A가 인터페이스 B의 인스턴스가 되지 않는 경우는? 정답: A와 B가 2개의 서로 다른 클래스 로더들에 의해서 로드되었을 때. 개인적으로는 지난주에 반나절 동안을 이것 때문에 시간을 낭비했다. 나의 문제를 메일링 리스트에 언급했더니 어떤 재능 있는 프로그래머 친구는 자신도 같은 버그로 2주 동안 시간을 낭비했다고 말했다.)
클래스 로더가 어떻게 수정되어야 하는지 모른다는 것을 솔직하게 인정하겠다. 분명히 자바가 가진 까다로운 부분 중에 하나이기 때문이다. 현재 이와 관련된 시스템이 꽤 어렵다는 것을 알고 있으며 이러한 문제에 대해 좀더 나은 방법이 있어야 할 것이다.
요약
위에서 나열한 열 개의 목록은 단지 시작에 지나지 않는다. 만약 우리 자신이 하위 호환성이라는 두꺼운 옷을 벗어 던진다면 자바가 개선되어 질 수 있는 분야는 매우 많다. 정수형 상수를 타입형과 상관없는
enums로 바꾸고,
StringTokenizer와
StreamTokenizer 같은 혼란스럽고 불필요한 클래스들을 삭제하고,
Cloneable 인터페이스를 진정한 mixin 인터페이스로 만들거나 완벽하게 제거하고,
Math.log() 메소드를
Math.ln() 메소드로 이름을 변경하고, 협정에 의한 정확한 설계를 위한 지원을 추가하고, 검사된(checked) 예외(Bruce Eckel가 옹호해 왔던)를 제거하고, 객체를 단일 스레드로 제한하는 등... 더 많은 것들이 있다.
우리는 정확히 어떤 변화가 필요한지, 어떤 변화가 좋은 것 보다 해로운 것의 원인이 되는지에 관해 논의 할 수 있다. 그러나 만약 자바가 변화에 실패한다면... 만약 잘 알려진 문제들의 수정을 거부한다면... 자바의 문제점으로부터 배운, 몇몇 명석한 프로그래머가 만든 이전의 자바가 결함이 있는 언어(language)를 대체했던 것처럼 같은 방법으로 자바를 대체하기 위해 기회를 엿보는 다른 언어들이 있다는 것을 확실히 해두어야 할 것이다. 7년 전 Java 1.0에서 저지른 실수로 생긴 장애가 계속해서 남아있어서는 안된다. 호환성 문제에 대한 고리를 던져 버릴 필요성이 있는 곳에서 미래에도 강하게 밀어 붙일수 있는 핵심이 나온다.
엘리어트 러스티 해롤드(Elliotte Rusty Harold)는 『XML in a Nutshell』(O"Reilly)과 『Procesing XML with Java』(Addison-Wesley)의 공동 저자이다.