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이 아직 나오지 않은 지금으로서는 거의 유일한 대안이라고 보여집니다.

댓글 없음:

댓글 쓰기