롬복을 알아보자!

maketheworldwise·2022년 1월 25일
2


이 글의 목적?

롬복은 내가 정말 많이 사용하는 라이브러리중 하나다. 하지만 그저 편리하다는 이유만으로 사용해서는 안된다는 점을 듣게 되었고 이 내용을 정리해보고자 한다.

롬복이란?

롬복은 코드 다이어트 라이브러리로 반복되는 메서드의 작성을 간략하게 해결해준다. 롬복을 이용하면 코드 자동 생성으로 인한 생산성 향상과 가독성 및 유지보수성이 향상되는 특징을 가지고 있다. 코딩 과정에서는 어노테이션이 보이지만, 컴파일된 결과물에서는 코드가 생성되어있는 것을 확인할 수 있다.

롬복 라이브러리로 사용할 수 있는 어노테이션을 살펴보기 전에 어노테이션을 생성할 때 필요한 메타 어노테이션을 살펴보자. 😤

  • @Target: 어노테이션을 어떤 것에 적용할지 선언
  • @Retention: 얼마나 오래 어노테이션의 정보가 유지되는지 선언
  • @Inherited: 모든 자식 클래스에서 부모 클래스의 어노테이션을 사용 가능하다는 것을 선언
  • @Documented: 정보가 JavaDocs(API) 문서에 포함된다는 것을 선언
  • @interface: 어노테이션 선언
  • default: 어노테이션의 기본값 설정

롬복 라이브러리에 있는 어노테이션의 정보들을 확인해보면 다음과 같다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface AllArgsConstructor {}

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {}

@Target({ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface Cleanup {}

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface CustomLog {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Data {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface EqualsAndHashCode {}

@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Generated {}

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface NoArgsConstructor {}

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface RequiredArgsConstructor {}

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Setter {}

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.SOURCE)
public @interface Singular {}

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.SOURCE)
public @interface SneakyThrows {}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Synchronized {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Value {}

@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface With {}

롬복의 단점?

롬복에 대한 의견은 반반 갈린다고 한다. 이렇게 편리한 것을 왜 반대하는 사람들이 왜 있을까? 일단 결론부터 말을 한다면 롬복은 어떻게 만들어주는지 모르면 운영상에서 장애가 발생할 수 있기 때문이다.

@AllArgsConstructor

먼저 생성자를 만들어주는 어노테이션인 @AllArgsConstructor의 경우 모든 필드 값을 파라미터로 받는 생성자를 만들게 되는데, 여기서 중요한 점은 선언된 필드의 순서대로 파라미터를 받게 된다. 왜 문제가 될까? 이유는 필드의 순서를 변경할 경우에 발생한다.

분명 필드의 순서에 맞게 생성자를 이용해왔는데 누군가 필드의 순서를 변경함으로서 필드에 올바르지 않은 값이 들어가게 된다면, 해당 생성자를 이용한 모든 코드들을 수정해야하는 안타까운 일이 벌어진다.

코드로 살펴보면 다음과 같다.

@AllArgsConstructor
public class SampleClass {
	private String title;
    private String description;
}
SampleClass sample = new SampleClass("title", "description");

이 구조에서 클래스의 필드의 순서만 변경하면 어떻게 될까?

@AllArgsConstructor
public class SampleClass {
    private String description;
    private String title;
}
SampleClass sample = new SampleClass("title", "description");

결과는 description 필드에 "title"이, title 필드에는 "description"이 들어가는 불상사가 일어난다.

(이 부분은 @Builder 빌더 패턴으로 충분히 해결이 가능해 보인다. 🤔)

@equalsHashCode

@equalsHashCode 어노테이션도 마찬가지다. 이 어노테이션도 가지고 있는 모든 필드로 생성해주는데, 해당 클래스가 Mutable 클래스이고 내용이 변경되는 순간 해시 코드가 변경되는 문제가 있다. (exclude 어노테이션 속성으로 해결이 가능하다!)

@toString

@toString 어노테이션은 JPA와 함께 사용하면 문제가 발생할 수 있다. JPA에 대해서는 나중에 다시 살펴보겠지만 A와 B가 서로 참조하는 형태를 가진 구조에서 이 어노테이션을 사용하게 되면, A 클래스 출력 시 B의 toString()을 호출하고, B 클래스 출력 시 A의 toString()을 호출하게 되는 순환 참조 또는 무한재귀호출StackOverflowError가 발생할 수 있다. (이 문제도 exclude 어노테이션 속성으로 해결이 가능하다! 😗)

정리해보자

이렇게 잠재적인 위험이 존재하는 문제를 어떻게 해결해야 할까? 적용한 어노테이션에 대해서는 어디에 사용되었는지 팀 모두가 공유해야한다. 따라서 내부적으로 코딩 컨벤션을 정하거나 온보딩, 문서화, PR 리뷰, 사용하지 말아야할 어노테이션과 사용할 어노테이션에 대한 제약 조건을 걸어두어 해결할 수 있다.

"코드상에서 롬복은 위험하다 vs 안전장치를 잘만 구성하면 좋다" 이 두 의견에 대한 입장은 모두 타당하다. 따라서 롬복을 사용할지 말지에 대해서는 프로젝트와 상황에 맞춰서 구성하는것이 가장 바람직하다.

모르는 사람이 편의성만을 위해 사용하면 위험할 수 있다는 문제를 이해하고 운영 관점에서 혹은 내부적으로 어떻게 어노테이션이 코드를 만들어내는지를 확인하는 습관을 들이는 것은 중요하니 잊지말자. 🤨

이 글의 레퍼런스

profile
세상을 현명하게 이끌어갈 나의 성장 일기 📓

0개의 댓글