2012년 7월 28일 토요일

[java] Date classes의 사용을 자제하자.

JDK에는 기본적으로 여러 편리한 library들이 포함되어있습니다.

마치 웹 표준인 것 마냥 널리 사용되는 Java지만, 그중에는 잘 설계된 것이 있는 반면, 잘못 설계된것도 많습니다.

날짜 계열 Class들은 잘못설계되서 욕을 바가지로 먹고있는 대표적인 class들입니다.
구체적으로는 다음 class들이 해당됩니다.

java.util.Date
java.sql.Date
java.sql.Timestamp
java.util.Calendarjava.util.GregorianCalendar
java.util.TimeZone
java.util.SimpleTimeZone
java.text.DateFormatjava.text.SimpleDateFormat
java.text.DateFormatSymbols

이 class들이 욕먹는 이유는 대체적으로 다음과 같습니다.

1. Mutable하다
객체는 기본적으로 Immutable한것이 좋습니다. mutable하지 않은 경우 의도치 않게 객체의 정보가 변경되어 버그의 온상이 됩니다. 특히 multi thread환경이라면 더욱 그렇습니다.
실제로 SimpleDateFormat의 경우 static으로 박아놓고 사용해, 문제가 되는 경우가 잦은 편입니다.
SimpleDateFormat의 문서에는 이 class가 thread-safe하지 않음을 명시해놓고 있지만, 만약 이 클래스가 Immutable하다면 이런 명시도 필요없었을 겁니다.

2. 불필요한 CheckedException을 던진다.
SimpleDateFormat.parse를 사용하면 다음과 같이 ParseException을 던지고 있습니다만, 실제로 Checked Exception은 꼭 필요한 경우가 아니면 사용을 지양하는 것이 좋습니다.

try {
    Date date = new SimpleDateFormat("yyyyMMdd").parse("20120508");
    System.out.println(date);
} catch (ParseException e) {
    e.printStackTrace();
}

게다가 기껏 ParseExceptin을 던지면서도 실상 Exception을 제대로 던지고 있지도 못합니다.
날짜에 20120508이 아니라 20129931나,  222220120508과 같이 실제로 ParseException을 던져야하는 문자열을 입력해도... Exception을 뱉지않고 넙죽넙죽 Parsing해버리고 맙니다.
정말 필요없는 Checked Exception을 던지고 있는겁니다. 참고로 잘못된 입력의 경우 다음과 같은 결과를 내놓고 있습니다.

Date date = new SimpleDateFormat("yyyyMMdd").pare("20109931");
    -> Sat Mar 31 00:00:00 KST 2018

Date date = new SimpleDateFormat("yyyyMMdd").pare("222220120508");
    -> Sun Jul 08 00:00:00 KST 2553 

3. 날짜 계산이 복잡하다.
add정도의 method는 지원하고 있습니다만, 이는 가장 단순한 형태만 지원하는 것으로.. 실제 날짜 계산에 들어가면 적절한 메소드가 없어서 생으로 계산하게 되는 경우가 많습니다. Date나 Calendar를 이용해 날짜 계산을 해보신 분들이라면 느끼셨을 불편함으로.. 여기서는 딱히 예를 들지 않겠습니다.

4. 월이 0부터 시작한다.
new Date().getMonth() 메소드로 월을 구하면 꼭 +1을 해줘야하는 불편함이 있습니다.
1월인 경우 0이나오고 2월인 경우 1이 나오기 때문에, 숙련된 프로그래머라도 종종 실수하게 되는 부분입니다.
처음에는 외국에서는 Jan, Feb와 같이 영문 표기를 즐겨 사용하니 그렇게 했나보다 헀는데.. 외국 포럼에도 Month가 0부터 시작하는 사실을 모르고 버그냐고 물어보고.. 그런 게시물들이 있더군요.

5. TimeZone 변경에 따른 잘못된 시간표기
특정한 날짜를 다음과 같은 코드로 표기하면 다음과 같이 오차가 나오는 경우가 발생합니다.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
cal.setTime(sdf.parse("1988-05-08 00:00:00"));

Expected                => Actual
1904-12-01 00:00:00 => 1904-12-01 00:30:00
1932-01-01 00:00:00 => 1932-01-01 00:30:00
1960-05-15 00:00:00 => 1960-05-15 01:00:00
1961-08-10 00:00:00 => 1961-08-10 00:30:00
1968-10-01 00:00:00 => 1968-10-01 00:30:00
1987-05-10 00:00:00 => 1987-05-10 01:00:00
1988-05-08 00:00:00 => 1988-05-08 01:00:00

말하자면 1904년 12월 1일 00시 00분 00초를 파싱했더니 1904년 12월 1일 00시 30분이 나오는 겁니다. 이는 우리나라가 과거 몇번 우리나라 고유의 TimeZone을 사용했다가 일본것으로 바꾸고 하는 과정이 있었는데.. 이때 생긴 오차 때문인 것으로 알고있습니다.

그렇다고 해서, 이렇게 제멋대로 시간을 바꿔버리면... 사용하는 입장에서는 곤혹스럽기 마련입니다. 이런 경우 차라리 명확하게 Exception을 던지거나 하는 것이 낫죠.

6. 그외..
그외에도 외국에서는 서머타임이나 이런 부분 때문에 혼동스러운 부분이 많은 모양입니다.
Deplicated된 메소드도 유난히 많은데... 처음 설계자체가 좋지않은 것을 알 수 있습니다.
만약 추가적인 문제를 알고있으시다면 댓글로 달아주시면 감사하겠습니다. 본문에 추가하도록 하겠습니다.


대략.. 위와 같은 이유로 Java의 Date Classes들은 사용을 지양하는 것이 좋습니다. 대체품을 사용하는 것이 좋은데, 가장 추천할만한 것으로는 JodaTime(http://joda-time.sourceforge.net/)이 있습니다.
쪼다타임으로 읽어야할지 요다타임으로 읽어야할지 약간 망설여지는 이름입니다만...

날짜 표기 방식으로 국제 표준인 ISO8601을 사용하고 있고 JSR310에도 영향을 끼친 것으로 알고있습니다. JSR310이 아직 나오지 않은 지금으로서는 거의 유일한 대안이라고 보여집니다.

[eclipse] eclipse juno에서 sts 사용하기

오랜만에서 이것저것 개발관련 글들을 보다보니, eclipse juno가 나와있었네요.
6월 27일에 나온 모양인데, 한달이나 지나 알게됐습니다. -_-

새로 설치하고 나서 STS를 설치하려고 보니, 아직 juno용 STS가 나오지 않은 모양입니다.

아직 개발중인 모양으로, 다음 페이지를 참고하시면 미리 사용해볼 수 있습니다.

[java] interface의 필요성

Interface는 Java 초심자 입장에서는 참으로 이해하기 힘든 존재입니다.

이미 Class가 있는데 굳이 Interface라는 문법이 필요한가도 그렇고, 이 Interface가 실질적으로 로직은 기술할 수 없는 반쪽짜리 class라는 점이 더욱 그렇습니다.

이 Interface를 이해하기 위해서 USB를 예로 들어보겠습니다.

모든 컴퓨터 마다 몇개씩 달려있는 USB단자는 매우 편리합니다. 어떠한 주변기기든 USB에 꼽기만 하면 그걸로 O.K. 입니다.

우리는 아무 생각없이 사용하고 있지만, USB라는 표준이 없었을때 얼마나 불편한지를 생각해보면 이 USB라는 표준하나로 얼마나 많은 사람들이 편리함을 얻고있는지 알수있을 겁니다.

마우스, 키보드, 그외 각종 주변기기가 종류별로, 제조사 별로 모두 제각각의 단자를 가지고 있다면 기껏산 마우스가 자신의 컴퓨터에 맞지 않거나 하는 문제가 빈번히 발생할 겁니다. 또 이 문제를 해결하기 위해서 우리는 수많은 종류의 아답터를 구비해 놓고있어야할겁니다.

Interface는 USB표준과 같은 역할을 하는 것으로, 만약 여러분이 작성한 class가 파라미터를 Interface로 받도록 구현해놓았다면, 어떤 클래스든지 여러분이 만든 class의 기능을 이용하기 위해서 그 Interface만 구현하면 될겁니다.

이는 모든 마우스, 키보드 그외 컴퓨터 주변기기 제조사들이 USB표준대로만 물건을 만들어 팔면 별 고민없이 여러분의 컴퓨터에서 사용할수있는 상황과 같습니다.

실제 코드상의 예를 들자면 Java의 Comparable이 좋은 예가 될겁니다.
http://docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html

정렬을 원하는 class라면 그 class가 무엇이든지 Comparable을 구현한 후, Collections.sort에 집어넣으면 정렬이됩니다.

만약 Java의 정렬을 구현한 개발자가 이와같은 설계대신 Integer, String, Date와 같은 기본적인 class의 정렬만 따로 구현해놨다면, 우리는 정렬이 필요할 때마다 직접 정렬 로직을 구현하거나, 정렬을 도와주는 helper class등을 따로 이용해야했을 겁니다.

게다가 Class의 정렬규칙이 변경되기라도 한다면 그 정렬 로직자체의 수정이 불가피했을 겁니다.

하지만 Java에서는 정렬이라는 행위를 추상화하고 Interface를 이용함으로서 이러한 불편함을 최소화했습니다.


우리가 Class를 설계할때도 항상 이러한 부분을 고려해야합니다. 자신이 만들고자 하는 기능을 최대한 작은 부분 부분으로 나누고 추상화해서 범용적으로 사용될 수 있는지 생각해 봐야합니다.

범용적으로 사용될만한 기능이 있다면, '이 기능을 사용하고 싶으면 이 Interface만 구현해.'
라고 설계합니다.

추후 로직의 재사용뿐만 아니라 유지보수시에도 상당히 편리해집니다.