태그 : java

스칼라의 Option을 자바 버전으로 만들어 봤습니다.

어떤 값이 있을 수도, 없을 수도 있는 상황에 쓸 수 있는 간단한 형태의 컨테이너 클래스가 스칼라에 있습니다. Option이란 클래스인데요. 이 클래스는 추상 클래스라서 인스턴스를 만들 수 없고 코드에서는 이 클래스의 하위 클래스인 Some이나 None을 사용합니다. Some은 값이 있음을 나타내고 그 안에 해당 값을 가지고 있습니다. 반면에 None은 값이 없음을 의미하며 아무런 값도 가지지 않습니다.

Option 클래스는 Map, List 같은 집합형으로 자료를 관리하려는데 특정 값이 있을 수도 있고 없을 수도 있다면 null을 넣는 대신 사용할 수 있습니다. 보통처럼 null을 넣게 되면 값을 집합형에서 꺼내면서 null인지 확인해야 하는데 이 사실을 문서화 해 놓더라도 자주 잊어버려 NullPointerException이 생기곤 합니다. 잊지 않고 기억한다 하더라도 매번 꺼낼 때마다 null인지 확인해야 하는 번거로움도 있습니다.

Map<String, Integer> repository = new HashMap<String, Integer>();
repository.put("key1", 1);
repository.put("key2", null);

if(repository.get("key2") == 1) { ... } // 예외 발생

이때 Option을 사용하면 값이 없을 때 기본값으로 대신 처리하도록 하거나 강제로 값이 없는지 판단하도록 할 수 있습니다.

Map<String, Option<Integer>> repository = new HashMap<String, Option<Integer>>();
repository.put("key1", Option.some(1));
repository.put("key2", Option.none());

if(repository.get("key1").getOrElse(0) > 0) { ... } // 기본값 사용

if(repository.get("key2").isDefined()) { // 값 유무 여부를 판단
   int value = repository.get("key2").get();
}

Option 유용하게 사용할만한 또 다른 곳은 반환 값입니다. 어떤 질의 메서드를 호출하고 결과로 값을 받는데 값이 없을 때는 null이 오거나 예외가 발생합니다. 값이 없지만 굳이 확인해야 할 필요가 없을 때는 메서드가 Null 객체를 반환하기도 합니다.

값이 없다는 사실을 null을 반환함으로써 표현하는 경우, 집합형을 쓸 때처럼 받는 쪽에서 null인지 확인하는 절차를 잊을 수도 있고 매번 null을 확인하는 불편을 감수해야 합니다. 이 때 메서드의 반환값이 없을 수도 있음을 명시적으로 나타내면서 간단히 처리하도록 하는데 Option이 좋습니다.

깃헙 기스트에 테스트 코드와 함께 올려 놨습니다.

https://gist.github.com/1263813

by 박성철 | 2011/10/05 16:18 | 프로그래밍 이야기 | 트랙백 | 덧글(0)

자바가 다중 상속을 지원하지 않는 이유?

이 글은 LangDev의 토론을 뒤늦게 읽고는 너무 늦게 토론에 참여하기 뭐해 극적거리기 시작해서 미완성인 채로 남겨 두었다가, KSUG의 토론을 보고 마무리 지어야겠다는 생각이 들었고, 오늘에야 시간을 나서(내서?) 결론 내려 한다.

자바 개발자라면 누구나 알겠지만 자바는 클래스를 하나만 상속해서 확장할 수 있다.

class GundamMk2 extends Gundam {
...
}

클래스를 둘 이상 상속 받을려고 하면 컴파일이 안 된다.

class SpaceGundamV extends Gundam, Valkyrie {
...
}

자바를 첫 프로그래밍 언어로 배웠거나 자바로 객체지향 프로그래밍에 입문한 개발자라면 별다른 의문 없이 이런 제약을 받아들였겠지만 C++나 다른 다중 상속을 지원하는 언어를 먼저 익힌 사람이라면 궁금해하거나 이상하게 여길지 모르겠다.

위 그룹스의 글에서도 그렇고 다른 다중 상속 관련 토론을 보면 늘 다중 상속에는 "다이아몬드 문제"가 있다는 얘기가 나온다. 더 나아가 "다이아몬드 문제" 때문에 자바가 다중 상속을 지원하지 않는다고 주장하는 사람도 상당히 많다.

그럼 다이아몬드 문제가 뭘까? 한번 알아보자...니 귀찮다. -_-); 그래서 위키백과 링크 하나 달랑 던져 놓고 넘어가겠다.

http://en.wikipedia.org/wiki/Diamond_problem

위키백과의 글을 보면(이라고 쓰고 감상하면 이라고 읽는다. 영어따위!) 알겠지만, 설명이, 짧다(잉?).  별문제 아닌 것 같다. 더구나 밑에 다중 상속을 지원하는 여러 언어에서 이 문제를 어떻게 다루는지 설명하고 있(는 것 같)다. 결국, 다중 상속에는 다이아몬드 문제가 일어날 수 있지만 이 문제는 여러가지 방법으로 해결할 수 있는 사소한 문제일 뿐이(라고 추측할 수 있)다. 그러니 이 문제 때문에 자바가 다중 상속을 지원하지 않는다는 주장은 그리 설득력이 없어 보인다.

KSUG 메일링에서 몇몇 분은 상속 자체가 쉽게 문제로 발전할 수 있는 여지가 있으니 다중 상속은 더욱 취약하고, 그래서 애초에 다중 상속을 금지한 것 아니냐고 말한다.

혹시 상속보다 구성을 사용하자는 말을 들어봤는지 모르겠다. 객체지향 프로그래밍의 상징처럼 여겨지는 상속을 가능하면 쓰지 말고 객체 구성을 활용하자는 격언이다. 상속은 코드를 재사용하는 멋진 아이디어지만, (템플릿 메서드 패턴처럼) 상속해서 쓰도록 미리 고려된 객체가 아니라면 무척이나 불안전하고 위험한 부작용이 생길 수 있다. 캡슐화가 깨져 조상 객체와 피상속 객체 사이에 강한 결합이 생길 가능성이 있는 것이다. 그래서 이런 예상 밖의 부작용을 막으려고 구상 클래스의 public 메서드를 final로 만들어 오버라이드하지 못하게 막기도 한다. 아니면 최소한 상속했을 때 문제가 생기지 않도록 내부 정보를 문서화하라고도 한다.

상속이 생각보다 그리 유용하지 않고 위험하기까지 하니 다중 상속은 얼마나 안 좋겠냐는 생각은, 막연하지만, 어느 정도 타당성이 있어 보이고, 다중 상속을 제거했다는 자바 설계자의 선택이 현명해 보이기도 하다.

그런데 애석하게도 다중 상속은 아주 유용하다. 단일 상속만 허락한다면 객체가 너무 경직된다. 아무리 객체의 단일 책임의 원칙을 강조한다고 해도 객체에 복합적인 특성을 부여해야 할 일이 많다. 객체 모델링을 할 때를 예로 들면, 말은 동물로 분류되면서 동시에 탈 것으로 분류될 수 있다. 스마트 폰은 정보기기면서 전화기다.

또, 나는 컴포넌트의 경계(boundary) 객체를 내부적으로 문맥(context) 객체로 쓰는 습관이 있는데, 이때도 다중 상속은 유용하다. 한 객체가 컴포넌트 외부와의 인터페이스 역할을 하면서 내부적으로는 문맥을 제공하니 한 객체가 바라보는 관점에 따라 두 가지 역할을 하는 샘이다. GUI의 콤보 박스는 입력창과 목록의 특성을 모두 가지고 있다고 할 수 있다.

아무리 객체가 현실을 그대로 모델링할 수 없다고 말하지만 어느 정도 다면성을 부여할 수 없다면 설계가 복잡해진다. 특히 정적 타이핑 언어에서는......

그래서 자바는 다중 상속을 지.원.한.다. 두둥!!!!

눈치 빠른 사람은 무슨 소린지 알아챘겠지만, 확실히 자바는 다중 상속을 지원하는 언어다. 다중 상속을 지원할 뿐 아니라 이런저런 문제를 일으키지 않도록 특별히 고려하기까지 하면서 말이다. 사실 자바를 비롯해 다중 상속을 지원하는 언어들은 다이아몬드 문제 같은 다중 상속의 문제를 회피하기 위해 나름의 장치를 마련해 놨다. 자바도 마찬가지로 자신만의 독특한 방식으로 다중 상속을 구현했다. 자바의 방식은 다이아몬드 문제뿐 아니라 다중 상속의 복잡성도 해결할 수 있다.

자바는 부모 객체가 특별한 조건에 부합될 때에만 다중 상속을 할 수 있도록 언어 차원에서 규제를 걸어 놨다. 더 정확히 말하자면, 자바에서 다중 상속을 하려면 부모 객체가 추상 객체여야 한다. 구상 객체는 다중 상속을 받지 못한다. 그것도 그냥 추상 객체면 되는 게 아니라 순수한 추상 객체여야 한다. 순수 추상 객체라는 말은 어떤 로직도 가지고 있지 말아야 한다는 의미로, 자바에서는 이런 추상 객체를 부르는 용어가 따로 있다. 바로 인터페이스다.

class ZetaGundam extends Gundam implements Fighter, Transformable  {
}

이쯤에서 내 말에 동의하는 사람도 있겠지만 버럭할 사람도 있으리라. 당신 말이 맞다. 자바는 다중 상속을 지원하지 않는다. 사실 상속도 지원하지 않는다. 무슨 말이냐고? 다음 코드가 자바에서 컴파일될까?

class RichChild inherits RichFather {
}

자바는 부자 아빠의 재산을 그 자녀가 물려받도록 허락할 생각이 없는 모양이다.

말장난을 한 김에 조금 더 해보겠다. 자바는 '상속'을 허락하지 않는 대신 '확장'할 수 있도록 하기로 했다. 우리는 습관처럼 extends라는 구문을 '상속'이라는 의미로 아무 생각 없이 쓰지만, 분명히 상속이 아니라 확장이다.

그게 그거라고?

아니다.

상속은 내가 물려받는 재산에 초점이 있다. 하지만 확장은 내가 해야 할 일의 성격에 초점이 있다.

졸리니 그냥 내가 말하려는 바를 말하겠다.

모 출판사의 편집자도 읽었다는 명저 Effective C++ (3판)의 32번과 38번 항목은 이렇다.

항목 32: public 상속 모형은 반드시 "is-a(...는 ...의 일종이다)"를 따르도록 만들자
항목 38: "has-a(...는 ...를 가짐)" 혹은 "is-implemented-in-terms-of(...는 ...를 써서 구현됨)"를 모형화할 때는 객체 합성을 사용하자

재산을 물려받는 '상속'이란 비유는 has-a가 될 가능성이 많다. '확장'이란 비유는 명백히 is-a를 뜻한다.

is-a는 부모 객체 A를 자식 객체 B가 상속했을 때 "B는 A다"라고 할 수 있음을 뜻하는 말이다. 팩토리 객체에 Pool 기능을 추가해 성능을 높이는 경우를 예로 들 수 있겠다. has-a로 변질되는 때는 마치 인간의 몸에 외계 생물의 DNA가 들어와 유전자를 변형시켜 인간이 아닌 뭔가로 바꿔버리는 경우다. 예전에 본 한 레가시 코드가 기억나는데, DB 질의 결과로 반환되는 객체가 RecordSet 형태였다. 소스를 열어보니 HashMap을 확장, 아니, 상속해서 만들었지만 어느 곳에서도 이 객체를 Map으로 다루는 곳은 없었다.

상속의 이런 측면을 메서드 수준에서 보면 객체지향 원리 중 하나인 리스코프 치환 원칙을 따른다고 할 수 있고 조금 거시적인 관점에서 보면 변경에 대해 닫혀있고 확장에 대해서는 열린, 개방-폐쇄 원칙 준수한다고 할 수 있다. 상속 받은, 아니, 확장한 자식 객체가 여전히 외부에서 봤을 때 부모 객체와 같게 보인다면 결국 좋은 객체지향 원칙을 준수하게 되는 것이다.

내 결론은 이렇다. 우리가 '상속'이란 말을 is-a로 사용한다면, 자바는 분명히 다중 상속을 지원하는 언어다. 반대로, has-a로 변질될 위험성이 있는 의미로 사용한다면, 자바는 다중 상속을 지원하지 않는 언어다. C++에서는 이 부분을 개발자가 실천해야할 규범으로 열어 놓았다면 자바는 언어차원에서 조금 더 강제할 뿐이다. 자바의 다중 상속 지원 방식이 최선인지는 모르겠다. 하지만 분명히 좋은 방법 중 하나라고 생각한다.

최근에 스칼라라는 언어를 공부하면서 트레잇(trait)을 알게 됐는데 이 또한 특수한 형태의 추상 객체로서 나름이 방식으로 다중 상속을 지원한다. 스칼라 진영 측에서는 인터페이스의 장점을 살리면서 단점을 보완한, 개선된 방식이라고 말하지만 나는 아직 확신이 안 선다. 뭐 똑똑한 사람들이 더 낫다고 하니 그런가보다 싶을 뿐이다. 사실 언어가 제공하는 장치를 너무 비판하는 것도 별로 지혜롭다고 할 수 없다. Effective C++에 반복해서 나오는 말처럼 "심사숙고"해서 잘 쓰면 된다.

by 박성철 | 2011/07/15 02:44 | 프로그래밍 이야기 | 트랙백 | 덧글(15)

자바 7에서 관심 있는 부분과 아쉬운 점

오늘, 7월 7일, 드디어 자바 7이 발표된다. (프로그래머들에게 별 관심도 받지 못한) 자바 6가 발표된 지 5년 만이다. 일반 개발자들에게는 "뭐야! 또 새로 나온거야?"라는 소리를 듣기는 하지만 내겐 자바가 그리 빠르게 성장한다는 느낌이 들지는 않는다. 물론 꼭 자바 언어가 변해야 하느냐는 얘기도 있다. 그런데 그렇게 주장하는 사람들은 이제 자바의 역할을 끝났으니 그루비나 스칼라 같은 다른 언어로 갈아 타라고 결론을 내리는 편이라서 일단 배제하겠다. (아! 지금 아는 걸로 천년 만년 먹고 살고 싶다는 사람도 있다. 나도 그러고 싶지만 이 업계가 이런 나태함을 허락하지 않으니 아쉽다. 사실 그런 업계라면 내겐 별 매력이 없다).

그런데 나는 이번 7이 그다지 성에 차지 않는다. 사실 자바 6.5라고 해야하지 않나 싶은데, 내가 투덜 거린다고 바뀔 것도 아니고, 곧 8을 내 놓겠다고 하니 지구 폭파 버튼은 조금 더 두고 보고 누를 생각이다.

암튼 이번 발표에서 관심이 가는 부분과 아쉬운 부분을 발표 전에 정리해 보겠다.

관심 가는 개선 사항

직업이 프로그래머다 보니 우선 언어의 변화에 관심이 가장 크다. 그동안 코인이란 이름으로 진행되던 소규모 언어 개선(JSR 334) 프로젝트 결과물 중 몇 개가 이번 자바 7에 포함된다.
  • switch문에 문자열 사용 - 뭐 좋아하는 사람도 많겠지만, 난 워낙 switch문을 잘 안 쓰니 그냥 그런가보다 싶다. (혹시 toString()으로 스칼라의 패턴 매칭 흉내 내려는 사람 없겠지?)
  • 이진 정수 리터럴과 숫자 리터럴에 밑줄 기호 사용 - 난 이거보다 short나 byte 형 리터럴 표기 방법을 원한다! 상수에 타입 변환을 해야 하다니!
  • 다중 예외 포획(-_-)과 정밀 재투척(-_-) - 아주 반갑다. 평소에 많이 아쉬웠다.
  • 지네릭 객체 생성시 타입 추론 - 아~ 쪼잔해... 자바는 힌들리-밀러 타입 추론을 도입하라!
  • 자동 자원 반환 - 좋은 기능이고 편하긴 하겠지만 자원을 반환해야 한다는 사실을 모르는 개발자는 이 구문도 모를 게 분명하다. 그리고 비대해진 try(...) 문이 가분수처럼 느껴진다.
  • 다중 인자 메서드 호출 단순화 - 잘못한(?) 놈은 놔두고 엄한 사람 보고 뭐라 그러는 문제 해결.
그 다음은 동시성 패키지 개선! (더그 리 교수님! 사랑합니다! OpenJDK에서 잘 부탁해요).
  • ForkJoinPool - 단연 기대 된다. ForkJoinPool은 비동기 실행을 해주는 ExecutorService의 새로운 구현체인데 비동기로 작업을 실행하되, 작업이 클 경우 여러 하위 작업으로 나눠서 여러 쓰레드에서 동시에 실행하도록 하는 프레임워크를 제공한다. 큰 작업을 나눠서 정복하기 방식으로 처리하면서 멀티 코어 CPU를 충분히 활용할 수 있다.
  • Phaser - CyclicBarrier와 CountDownLatch의 역할을 더 유연하고 범용적으로 사용할 수 있는 다중 쓰레드 동기화 관리 장치다. 부정 출발하는 쓰레드는 실격! 먼저 들어가기 없기! 중간에서 우리 만나! 등등
  • TransferQueue - 소비자와 생산자를 직접 만나게 해주는 직거래 장터... 같은 메시지 큐... 랄까...
  • ThreadLocalRandom - 다중 쓰레드 환경에서 더 효율적으로 돌아가는 Random 클래스. ThreadLocal에 각기 다른 Random을 넣어도 비슷하지만 ThreadLocalRandom은 초기화를 공유한다.
스크립팅 지원!
자바 6에서는 아파치 BSF와 비슷한 기능이 JVM에 추가되는 수준의 스크립팅 지원이었을 뿐인데 자바 7에서는 JVM 기반 스크립트 언어(정확히는 동적 타이핑 언어)를 만드는 데 필요한 *엄청난* 기능이 VM에 반영된다.
지금의 자바에서는 절대 필요 없는 Continuation, Tail recursion 같은 기능이 JVM에 추가되고 동적 메서드 호출도 빨라진다. 이제 JVM은 (언어로서의) 자바만을 위한 VM이 아니라고 할만하다. (그동안 JVM에서 다른 언어 구현하느라 삽질한 분들에게 박수를...)

하지만!

이렇게 좋아진 자바 7이지만 내가 쌍수들어 반가워하지 않는 이유가 있다. 원래 자바 7에 포함되기로 했던 몇가지가 자바 8로 미뤄졌는데 그것들이 정작 내가 기대했던 부분이기 때문이다. 물론, 이 개선 사항들이 다음 버전으로 연기됐기 때문에 7이 지금 나올 수 있었던 것이고, 나머지를 빨리 마무리 해서 원래 계획에 없던 8로 내놓겠다고 하기는 했다. 그래도 그게 약속처럼 빨리 나올지도 모를 일이고... 아쉬움을 어쩔 수 없고... 그래서 이번 출시 판 번호는 7이 아니라 6.5라고 혼자 얘기해본다. (투덜투덜)

그럼 내가 기대했던 특징이 뭔지 나열해 보겠다. 
  • 자바 모듈화 지원 : 드디어 자바에 모듈 기능이 들어간다. 모듈화 지원 스펙인 JSR 294 때문에 언어, VM, API에 변화가 생기고 JDK 자체를 모듈화하는 Jigsaw 프로젝트의 결과가 반영된다. 자바 애플리케이션의 복잡도를 제어할 수 있는 모듈화 기능이 들어갈 뿐 아니라 컴포넌트 배포 환경에 변화가 생길 수도 있다.
  • 컬렉션 리터럴 : List, Set, Map을 리터럴로 표현할 수 있다. 빌더를 사용해서 회피하려고 해 보지만 컬렉션 초기화는 정말 귀찮다.
  • 람다(클로저) 지원 : 무슨 말이 필요할까. 다른 언어가 "우리는 클로저 된다!" 그러면 자바에서는 "엄마~ 우리는?" 했었는데 자바에 드디어 클로저가 들어온다. 스프링의 탬플릿-콜백 패턴은 이제 더 강력해진다.
-------
 
점심 시간에 잠깐 정리해서 올리려고 했는데 이런 저런 일로 마무리 못하다가 대충 정리해 올린다. 젠장!
 











 

by 박성철 | 2011/07/08 01:58 | 프로그래밍 이야기 | 트랙백 | 덧글(10)

◀ 이전 페이지다음 페이지 ▶