@RequiredArgsConstructor, @AllArgsConstructor 사용은 지양하자!

joona95·2024년 2월 26일

📌 문제점

@RestController
@RequiredArgsConstructor
@RequestMapping("/fridges")
public class FridgeController {

    private final FridgeService fridgeService;
    private final FridgeBasketService fridgeBasketService;
    
    ...
    
}
    @Schema(description = "레시피 목록 응답 DTO")
    @Getter
    @AllArgsConstructor
    public class RecipesResponse {
        @Schema(description = "레시피 전체 갯수")
        private long totalCnt;
        @Schema(description = "레시피 목록")
        private List<RecipeResponse> recipes;
    }

기존에 Controller, Service 등에서 @RequiredArgsConstructor 어노테이션을 활용하여 생성자 선언을 간결하게 하고 DI가 될 수 있도록 했다.

그리고 DTO에서는 @AllArgsConstructor 어노테이션을 활용하여 전체 파라미터를 가지는 생성자 선언을 하도록 해두었다.

간단한 생성자 선언이고 단순 작업을 줄이고 코드도 간결하게 해주어서 편리하다고 생각하고 사용했지만 이러한 어노테이션을 사용할 때는 주의하지 않으면 문제가 발생할 수 있다.


@Getter
@AllArgsConstructor
public class User {
   private String id;
   private String nickname;
}
@Getter
@AllArgsConstructor
public class User {
   private String nickname;
   private String id;
}

예를 들어, 위의 예시처럼 어떠한 이유로 같은 타입의 다른 정보 선언 순서를 변경했다고 하자.

이 경우 @AllArgsConstructor 과 같은 자동으로 생성자 생성해주는 어노테이션은 선언 순서에 따라 생성자를 변경하게 된다.

User user = new User("userId", "nickname");

이 경우 선언 순서가 바뀌어도 같은 타입이기 때문에 다른 곳에서 이미 위와 같이 생성자를 사용하고 있었어도 문제를 인식하기 쉽지 않다.

생성자 파라미터 순서가 변한 것도 모른 채로 잘못된 값이 들어간 객체를 생성할 수 있다.

사실 이건 @AllArgsConstructor, @RequiredArgsConstructor 을 사용하지 않고 직접 생성자를 선언한다고 해도 발생할 수 있는 문제이고 주의해야 할 필요가 있다.


📌 해결 방안

@Getter
public class User {
   private String id;
   private String nickname;
   
   @Builder
   public User(String id, String nickname) {
   		this.id = id;
        this.nickname = nickname;
   }
}
User user = User.builder()
				.id("userId")
                .nickname("nickname")
                .build();

Builder 사용에 대해서도 여러 의견이 있긴 하지만 생성자 사용 시 발생하는 문제를 해결하기 위해서는 위와 같이 새로운 객체 선언 시 Builder를 사용하는 편을 추천한다.

어떠한 파라미터에 어떠한 값을 넣었는지 더 명확하게 확인할 수 있고 파라미터 선언 순서 변경에 영향을 받지 않을 수 있다.

그러나 Builder를 사용하는 경우에는 잊어먹고 어떠한 값 선언을 빼먹게 되는 경우 체크하기 어려우므로 선언 시 주의해야 할 필요가 있다.

Builder를 사용하는 DTO 등 클래스의 경우에는 확실히 @AllArgsConstructor 등을 사용하여 생성자 선언하는 것을 지양하는 것이 좋을 것 같다.


@RestController
@RequiredArgsConstructor
@RequestMapping("/fridges")
public class FridgeController {

    private final FridgeService fridgeService;
    private final FridgeBasketService fridgeBasketService;
    
    ...
    
}

하지만 Controller, Service 와 같은 DI를 생성자 주입으로 선언하는 케이스의 경우에는 @RequiredArgsConstuctor를 사용해도 괜찮지 않을까 싶다.

같은 타입의 다른 변수를 가지는 경우가 있다면 같은 문제가 발생하겠지만 사실상 그런 경우는 많지 않을 거고 따로 Builder를 사용하여 직접 주입하는 것이 아니기 때문이다.

그러나 Controller, Service에서는 어노테이션을 사용하고 DTO에서는 Builder 선언을 위한 생성자 선언을 하는 것이 코드의 일관성을 떨어뜨릴 수 있다는 의견도 있었다.

어떤 곳에선 어노테이션 사용하고 다른 곳에서는 생성자 선언을 사용한다면 코드를 작성한 사람은 바로 알 수 있지만 처음 보는 사람 입장에서는 한 번 생각을 하게 만든다.

다른 사람이 코드를 봤을 때 생각을 한 번 더 하게 만드는 코드는 좋은 코드가 아닐 수 있다.

개인 취향에 따라 결정하면 될 것 같지만 코드의 일관성 부분에 대한 의견이 내게는 좀 더 와닿았던 게 있어서 @AllArgsConstructor, @RequiredArgsConstuctor 사용을 지양하려고 한다.

0개의 댓글