[LOMBOK] 사용시 주의사항(PitfallCard)

윤재열·2022년 10월 1일
0

Java

목록 보기
58/71
post-custom-banner

Lombok 은 편리한 기능을 제공하지만 사용상 주의가 필요합니다.

@AllArgsConstrucor, @RequiredArgsConstructor

  • 이 두개는 매우 편리하게 생성자를 만들어주지만 치명적 버그가 되게 만들 수 있습니다.
@AllArgsContructor
public static class Order{

	private Long cancelPrice;
    private Long orderPrice;
}
// 취소금액 5,000원 , 주문금액 10,000원 
Order order = new Order(5000L,10000L);
  • 위 클래스에 대해 자동으로 canlePrice, orderPrice 순서로 인자를 받는 생성자가 만들어집니다.
  • 그런데 개발자가 보기에 Order 클래스 인데 canclePrice 가 orderPrice 보다 위에 있는것이 마음에 안들어서 순서를 바꾼다고 가정해봅니다.
@AllArgsContructor
public static class Order{

    private Long orderPrice;
    private Long cancelPrice;
}
  • 이 경우, IDE 가 제공해주는 리팩토링은 작동하지 않고, lombok 이 개발자도 인식하지 못하는 사이에 생성자의 파라미터 순서를 필드 선언 순서에 맞춰 바꿔버립니다.
  • 게다가 이 두 필드는 동일한 타입이라서 기존 생성자 호출코드에서는 인자 순서 변경이 없음에도 어떠한 오류도 발생하지 않습니다.
  • 이에 의해 , 위의 생성자를 호출하는 코드는 아무런 에러없이 잘 작동하는 듯 보이지만 실제로 입력된 값은 바뀌어 들어가게 됩니다.
//주문금액 : 5,000원, 취소금액 : 10,000원
Order order = new Order(5000L,10000L);
  • 이 문제는 @AllArgsConstructor,@RequiredArgsConstructor에 둘다 존재하며, 이에 따라 두 lombok 어노테이션은 사용을 금지하는 것이 좋습니다.
  • 대신 생성자를 직접 만들고 필요할 경우에는 직접 만든 생성자에 @Builder 어노테이션을 붙이는 것을 권장합니다.
@AllArgsContructor
public static class Order{

    private Long orderPrice;
    private Long cancelPrice;
    
    @Builder
    private Order(Long orderPrice,Long cancelPrice){
    this.orderPrice = orderPrice;
    this.cancelPrice = cancelPrice;
}
// 필드 순서를 변경해도 문제 없음
Order order = Order.builder().cancelPrice(5000L).orderPrice(10000L).build();

@EqualsAndHashCode

  • EqualsAndHashCode는 상당히 고품질의 equals,hashCode 메서드를 자동으로 만들어줍니다.
  • 다만 잘 사용하면 좋지만, 남용한다면 심각한 문제가 발생합니다.
  • 특히 문제가 발생하는 점은 Mutable(변경가능한) 객체에 아무런 파라미터 없이 그냥 사용하는 @EqualsAndHashCode 어노테이션입니다.
@EqualsAndHashCode
public static class Order {
    private Long orderId;
    private long orderPrice;
    private long cancelPrice;
 
    public Order(Long orderId, long orderPrice, long cancelPrice) {
        this.orderId = orderId;
        this.orderPrice = orderPrice;
        this.cancelPrice = cancelPrice;
    }
}
 
Order order = new Order(1000L, 19800L, 0L);
 
Set<Order> orders = new HashSet<>();
orders.add(order); // Set에 객체 추가
 
System.out.println("변경전 : " + orders.contains(order)); // true
 
order.setCancelPrice(5000L); // cancelPrice 값 변경
System.out.println("변경후 : " + orders.contains(order)); // false
  • 위와 같이 동일한 객체임에도 Set 에 저장한 뒤에 필드값을 변경하면 HashCode가 변경되면서 찾을 수가 없게 되어버립니다. 이는 @EqualsAndHashCode의 문제기 보다는 변경가능한 필드에 이를 남발하면서 생기는 문제입니다.
    • immutable(불변) 클래스를 제외하고는 아무 파라미터가 없는 @EqualsAndHashCode 사용은 금지합니다.
    • 일반적으로 비교해서 사용하지 않는 Data 성 객체는 equals,hashcode는 따로 구현하지 않는 것이 차라리 낫습니다.
    • 항상 @EqualsAndHashCode(of={"필드명시"})형태로 동등성 비교에 필요한 필드를 명시하는 형태로 사용합니다.
    • 실전에서는 누구나 이에 대해 실수하기 마련인지라 차라리 사용을 완전히 금지시키고 IDE 자동생성으로 꼭 필요한 필드를 지정하는것이 나을 수도 있습니다.
    • @EqualsAndHashCode.Include@EqualsAndHashCode.Exclude를 사용하여 명시적으로 사용할 수 있습니다.
      @ToString처럼 @EqualsAndHashCode(onlyExplicitlyIncluded = true)를 사용하는 것도 가능하다.
// CanclePrice 필드를 포함하고 OrderPrice 필드를 제외하는 방법
class Order {

    private Long canclePrice;
 
    @EqualsAndHashCode.Exclude
    private Long orderPrice;
 
}
 
// 또는
@EqualsAndHashCode(onlyExplicitlyIncluded = true)  
class Order {  

    @EqualsAndHashCode.Include  
    private Long canclePrice;  
 
    private Long orderPrice;  
}

@Data

  • @Data는 파라미터없는 @EqualsAndHashCode@RequiredArgsConstrctor를 포함하기때문에 사용을 아에 금지하고, 차라리 다음과 같이 명시하는 것이 좋습니다.
@Getter
@Setter
@ToString
public class Order {

    // 생성자와 필요한 경우에만 equals, hashCode 직접 작성
}

@Value

  • @Value는 immutable 클래스를 만들어주는 조합 어노테이션이지만, 이 또한 @EqualsAndHashCode,@AllArgsConstructor를 포함합니다.
  • @EqualsAndHashCode 는 불변 클래스라 큰 문제가 되지는 않지만, @AllArgsConstructor가 문제가 됩니다.
@Getter
@ToString
public class Order {
    // private final 로 여러 필드 생성
    // 생성자와 필요한 경우에만 equals, hashCode 직접 작성
}

@Builder

  • @Builder를 사용하면 객체 생성이 조금 더 명확해지고 쉬워지는데, 이는 기본적으로 @AllArgsConstructor를 내포하고 있습니다.
  • 이 자체는 평상시에는 큰 문제가 되지는 않지만, 생성자를 protected 로 만들기 때문에 외부에서 생성자를 호출하는 일은 쉽게 생기지 않습니다.
  • 하지만 해당 클래스의 다른 메서드에서 이렇게 자동으로 생성된 생성자를 사용하거나 할 때 문제의 소지가 될 수 있습니다.
  • 따라서 @Builder는 가급적 클래스보다는 직접 만든 생성자에 붙이는 것을 권장합니다.

@Log

  • @Log를 통해 각종 Logger 를 자동생성할 수 있습니다.
  • 이때 기본적으로 private static final 로 생성하는데, static 이 아닌 필드로 만들고자 하거나 Logger 객체 이름을 변경하고자 한다면 lombok.config를 사용하면 됩니다.
lombok.log.fieldName=logger # 로거 객체 이름을 logger로 변경. 원래는 log
lombok.log.fieldIsStatic=false # 로거를 static이 아닌 필드로 생성
  • 또한 가급적이면 @Sl4j만 사용하고 나머지는 사용할 수 없게 금지시키는 것도 가능합니다.
  • 저는 일반 서비스에서는 Logger를 field 변수로 만들 필요없이 static final로 하는 것을 선호합니. 그래야 별다른 처리없이 static method에서도 호출이 가능하기 때문입니다.

@NonNull

  • @NonNull은 개인적 취향에 가까운데, 불필요하게 branch converage를 증가시킵니다.(즉, 프로젝트 코드 커버리지를 유지하고 싶다면 Null인 상황에서 오류발생과 그렇지 않은 상황에 대한 테스트를 모든 사용처에서 만들어야합니다.)
  • 그렇다고 해서 이 코드를 일일이 테스트를 만들자니 이미 검증된 라이브러리의 기능을 사용하는 곳에서 모두 테스트를 만드는 것도 굉장히 소모적인 일입니다.
  • 그래서 @NonNull을 사용하지 않고 Guava 의 Preconditions로 null을 검증하고 오류 처리하는 것을 선호합니다.

@ToString, @EqualsAndHashCode 필드명 지정시 오타문제

  • @ToString,@EqualsAndHashCode는 Include,Exclude 지정이 가능해졌습니다.
  • 따라서 아래의 문제가 모두 해소됩니다.
    • 이 어노테이션들은 파라미터로 특정 필드를 지정해서 처리대상에 포함시키거나 제외시킬수 있습니다.
      문제는 이게 필드 이름을 String으로 지정한다는 점입니다. 이로 인해 IDE 에서는 필드명을 리팩토링할 때 올바르게 반영이 안되거나 아주 단순한 오타가 나도 눈치채지 못할 수 있습니다.
      보통 오타등으로 인해 잘못된 필드가 지정되면 컴파일 싲머에 warning이 출력됩니다.
profile
블로그 이전합니다! https://jyyoun1022.tistory.com/
post-custom-banner

0개의 댓글