Joda-Time에서 java.time으로 마이그레이션하기
코드(특히 레거시 코드) 마이그레이션은 결코 즐거운 작업이 아닙니다. 이를 성공적으로 완수하려면 엄청난 계획과 노력이 필요합니다. 개발자에게 가장 흥미롭거나 동기 부여가 되는 작업은 아닐지라도, 레거시 코드를 새로운 라이브러리 버전으로 마이그레이션하려면 결단력과 적절한 경험이 필수적입니다.Joda-Time에서 java.time으로의 전환 역시 세심한 계획과 실행이 요구되는 작업 중 하나입니다.
Java SE 8 이전에 시작된 Java 프로젝트에서 날짜/시간 처리를 사용하고 있다면, 아마도 SE 8 이전의 사실상 표준이었던 훌륭한 라이브러리인 Joda-Time을 사용하고 있을 것입니다. 만약 프로젝트에서 여전히 Joda-Time을 사용 중이며 java.time으로 마이그레이션하고 싶다면 이 글을 계속 읽어보세요.
Java SE 8이 출시되면서 java.time(JSR-310)이라 불리는 새롭고 향상된 표준 날짜 및 시간 API가 포함되었습니다. 현재 Joda-Time 프로젝트는 java.time(JSR-310)으로의 마이그레이션을 권장하고 있습니다.
java.time(JSR-310)은 Joda-Time에서 많은 영감을 받았지만, 하위 호환성은 지원하지 않으며 개념과 용어도 변경되었습니다. 그렇기 때문에 Joda-Time에서 java.time으로 마이그레이션할 때는 변경하는 코드 한 줄 한 줄에 세심한 주의를 기울여야 합니다. 이 과정은 시간이 많이 소요되며, 더 쉽고 자동화된 마이그레이션 방법이 있었으면 하는 바람을 갖게 합니다.
더 나은 마이그레이션 방법이 있습니다. 저희는 사용자가 정의한 레시피(규칙)에 따라 자동으로 코드를 변환해 주는 IntelliJ 플러그인인 Sensei를 만들었습니다. 반복적인 마이그레이션 작업 대신 재사용 가능한 레시피를 정의하는 데 시간을 투자하세요. 이 자동화 도구는 레거시 Joda-Time 코드를 변환할 뿐만 아니라, 팀원들이 새로운 코드를 작성할 때 IDE 내에서 바로 가이드라인을 따를 수 있도록 도와줍니다.
마이그레이션을 빠르게 시작할 수 있도록 저희는 Standardization on java.time (JSR-310)이라는 공개 Sensei 쿡북을 만들었습니다. 여기에는 Joda-Time에서 java.time으로 덜 고통스럽게 마이그레이션할 수 있는 레시피가 포함되어 있습니다. 이 레시피 세트는 계속해서 확장될 예정이며, 더 많은 커버리지를 제공하기 위해 새로운 레시피가 추가될 것입니다.
Sensei를 통해 레거시 코드 마이그레이션을 얼마나 간편하게 수행할 수 있는지 보여주는 마이그레이션 예시를 소개합니다.

반복적인 수동 마이그레이션에서 자동화된 코드 변환으로
Joda-Time에서 java.time으로 코드 한 줄을 마이그레이션할 때 발생하는 몇 가지 숨겨진 함정을 보여주는 새로운 DateTime 생성 예시를 살펴보겠습니다. 그런 다음, Standardization on java.time (JSR-310) 쿡북에 포함된 Sensei 레시피 중 하나를 통해 이 모든 정보가 어떻게 캡처되는지, 그리고 동일한 마이그레이션을 모든 개발자가 반복해서 재사용할 수 있는 방법을 보여드리겠습니다.
이 예시에서는 DateTime 필드 값을 나타내는 7개의 int 인수를 사용하여 Joda-Time DateTime을 구성합니다.
이를 java.time에 해당하는 코드로 어떻게 마이그레이션할까요?
이 생성자에 대한 Joda-Time의 javadoc은 다음과 같이 설명합니다:
기본 시간대의 ISOChronology를 사용하여 날짜/시간 필드 값으로부터 인스턴스를 생성합니다.
처음에는 java.time에도 DateTime 클래스가 있을 것이라 생각할 수 있지만, 실제로는 없습니다. 구글에서 'migrate from joda time to java time'을 검색하면 Stephen Colebourne의 블로그 게시물인 Converting from Joda-Time to java.time을 가장 먼저 찾게 될 것입니다.
이 게시물은 좋은 출발점이 되며, java.time.ZonedDateTime 또는 java.time.OffsetDateTime을 사용하도록 안내합니다. 여기서 첫 번째 의문이 생깁니다. 무엇을 사용해야 할까요? Stephen의 의견을 고려하면 아마도 ZonedDateTime일 것입니다.
ZonedDateTime의 javadoc을 찾아보면 생성자가 전혀 보이지 않습니다. Stephen의 블로그 게시물을 더 읽어보면 다음과 같은 내용을 확인할 수 있습니다:
생성. Joda-Time에는 객체를 받아 타입 변환을 수행하는 생성자가 있습니다. 반면 java.time은 팩토리 메서드만 제공하므로 변환은 사용자의 몫입니다. 단, 문자열을 위한 parse() 메서드는 제공됩니다.
따라서 정적 팩토리 메서드가 있어야 합니다. 정적 메서드들을 검색해 보면 꽤 비슷해 보이는 메서드를 찾을 수 있지만, 완전히 동일하지는 않습니다.
기존 Joda-Time의 DateTime 생성자와 마찬가지로 7개의 int 매개변수를 가지고 있지만, 주의 깊게 보지 않으면 중요한 세부 사항을 놓치게 됩니다. 7번째 매개변수는 더 이상 밀리초가 아니라 나노초를 기대합니다. 이는 java.time이 Joda-Time보다 정밀도가 높아져 나노초 단위로 시간을 측정하기 때문입니다. 쉽게 간과할 수 있는 중요한 부분이죠. 게다가 이 메서드는 ZoneId를 요구하는데, 왜 이전에는 필요 없던 것이 지금은 필요한지 의문이 들 수 있습니다.
기본 시간대를 사용한다고 명시된 기존 생성자의 javadoc을 떠올려 보면, 기본 ZoneId를 가져오는 방법이 있지 않을까요?
The javadoc for ZoneId does not tell us about any constructors listed, but looking at the static methods we see we can use systemDefault()
이제 ZoneId 문제는 해결되었습니다. 그렇다면 밀리초를 나노초로 변환하는 문제는 어떻게 해야 할까요? java.util.concurrent.TimeUnit을 사용하여 변환을 수행할 수 있을지도 모릅니다.
이 메서드는 long 타입을 반환하는데, 우리 메서드는 int 타입을 기대하므로 또 다른 변환 문제를 해결해야 합니다. 간단하게 곱셈을 시도해 보는 건 어떨까요?
작동은 하겠지만 다소 어색해 보입니다. 이미 눈치채셨겠지만, 코드 한 줄을 마이그레이션하기 위해 상당한 시간과 노력을 들였습니다. 짐작하시겠지만, 이런 작업을 수동으로 반복해야 한다면 상황은 더 나아지지 않을 것입니다.
하지만 java.time API를 조금 더 자세히 살펴보면, 훨씬 더 유연한 해결책을 발견할 수 있습니다.
ZonedDateTime에는 밀리초를 설정하는 명확한 방법이 없지만, with(TemporalField field, long newValue) 메서드를 사용하고 ChronoField.MILLI_OF_SECOND를 TemporalField로 지정하면 가능합니다.
javadoc을 보면 나노초로의 변환을 자동으로 처리해 준다고 언급되어 있습니다:
이 필드를 사용하여 값을 설정할 때, NANO_OF_SECOND를 설정하고 값을 1,000,000배 하는 것과 동일하게 동작해야 합니다.
따라서 팩토리 메서드에서 나노초를 0으로 지정한 다음, with 메서드를 사용하여 원래의 모든 값과 밀리초가 포함된 ZonedDateTime을 생성할 수 있습니다.
최종 결과를 보면 코드 한 줄만 바뀐 것처럼 보이지만, 이 마이그레이션 하나를 연구하기 위해 들인 노력은 겉으로 드러나지 않습니다!
더 빠르고 쉽게 마이그레이션할 수 있는 레시피 만들기
Sensei는 우리가 어렵게 얻은 정보를 다른 개발자들과 공유할 수 있는 방법을 제공합니다. 이러한 모든 요구 사항을 담은 레시피를 만들면, Sensei 사용자는 클릭 한 번으로 마이그레이션을 수행할 수 있습니다.
Sensei 레시피는 3가지 주요 섹션으로 구성됩니다:
- 메타데이터
- 검색
- 사용 가능한 수정 사항
이 호출을 java.time에 해당하는 코드로 마이그레이션하는 데 도움이 될 Sensei 레시피(YAML 레시피로도 볼 수 있음)를 살펴보겠습니다.
DateTime foo = new DateTime(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);
메타데이터 섹션
메타데이터 섹션에는 레시피에 대한 정보와 사용 방법이 포함되어 있습니다.
검색 섹션
Sensei 레시피의 검색 섹션은 이 레시피를 적용할 코드 요소를 지정합니다.
search:
instanceCreation:
args:
1:
type: int
2:
type: int
3:
type: int
4:
type: int
5:
type: int
6:
type: int
7:
type: int
argCount: 7
type: org.joda.time.DateTime
이 검색 섹션에서 확인되는 내용은 다음과 같습니다.
- 검색 대상은 instanceCreation, 즉 생성자 사용입니다. 참고: 사용 가능한 다른 검색 대상이 많이 있습니다.
- 생성자는 7개의 인수를 가져야 하며, 이는 argCount 속성으로 지정됩니다.
- args 1~7은 int 타입이어야 합니다.
- 검색 대상 생성자의 type은 org.joda.time.DateTime입니다.
사용 가능한 수정 사항 섹션
availableFixes 섹션에서는 일치하는 코드 요소에 적용할 수 있는 하나 이상의 수정 사항을 지정할 수 있습니다. 각 수정 사항은 여러 작업을 포함할 수 있으며, 여기서는 2가지 작업을 수행하는 단일 수정 사항을 사용합니다.
- 수정 사항의 name은 'Quickfixes' 메뉴에 사용자에게 표시되며, 이 퀵픽스를 적용할 때 어떤 작업이 수행되는지 설명합니다.
- actions 목록은 이 퀵픽스에 의해 수행될 작업을 보여줍니다.
- rewrite 작업은 머스태시(mustache) 템플릿을 사용하여 코드 요소를 다시 작성합니다. 변수와 문자열 대체 함수를 사용할 수 있습니다.
- modifyAssignedVariable 작업은 이 생성자가 변수에 값을 할당하는 데 사용되는지 확인합니다. 그렇다면 이 작업은 변수 선언 타입을 type
레시피를 사용한 코드 변환
레시피를 작성하고 활성화하면 코드를 스캔하여 적용 가능한 세그먼트를 강조 표시합니다.
아래 스크린샷에서 Sensei가 대상 생성자를 표시한 것을 볼 수 있습니다. 표시된 생성자 위에 마우스를 올리면 레시피의 shortDescription과 Migrate to java.time.ZonedDateTime

Migrate to java.time.ZonedDateTime 퀵픽스를 선택하면 레시피에 지정된 작업에 따라 코드가 변환됩니다.

Sensei와 함께하는 일회성 마이그레이션 및 팀 간 균일한 코딩 관행
위 예시에서 볼 수 있듯이, 코드 한 줄을 마이그레이션하는 데에도 많은 노하우가 필요할 수 있습니다. Sensei는 이러한 지식을 팀 내에서 공유할 수 있는 실행 가능한 레시피나 쿡북으로 변환합니다. 일회성 마이그레이션 스프린트를 계획하거나, Joda-Time 코드를 발견할 때마다 점진적으로 java.time으로 즉시 변환하는 방식을 취할 수 있습니다. 레시피를 활성화/비활성화하여 단계별로 마이그레이션을 진행하거나 Sensei가 스캔하는 파일 범위를 조정할 수 있어 코드 마이그레이션의 부담을 덜어줍니다.
라이브러리 마이그레이션은 Sensei를 사용하여 프로젝트를 표준화하는 여러 방법 중 하나일 뿐입니다. 풀 리퀘스트나 직접 코딩을 할 때 자주 접하는 안티 패턴이나 수동 코드 변환을 항상 주의 깊게 살펴볼 수 있습니다. 개발자들이 자주 놓치는 코딩 가이드라인이 있다면 이를 레시피로 변환하여 개발자들이 승인된 코드 변환을 자신 있게 적용하도록 도울 수 있습니다.
궁금한 점이 있으시면 언제든 문의해 주세요! Slack 채널: sensei-scw.slack.com
배포 전 AI 기반 개발 거버넌스 확보
AI 기반 위험을 측정하고, 커밋 시점에 보안 코딩 정책을 적용하며, SDLC 전반에 걸쳐 안전한 소프트웨어 제공 속도를 높이세요.
더 많은 블로그 살펴보기
보안 코딩, AI 거버넌스, 소프트웨어 리스크 관리에 관한 전문가 콘텐츠를 확인해 보세요.
%252520%252520(3).avif)
Supercharged Security Awareness: How Tournaments are Inspiring Developers at Erste Group
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Security as culture: How Blue Prism cultivates world-class secure developers
Learn how Blue Prism, the global leader in intelligent automation for the enterprise, used Secure Code Warrior's agile learning platform to create a security-first culture with their developers, achieve their business goals, and ship secure code at speed
배포 전 AI 기반 개발 보안 확보
소프트웨어 개발 수명 주기 전반에서 개발자 리스크를 파악하고, 정책을 시행하며, 취약점을 예방하세요.



.avif)