ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] new 연산자가 아닌 @Builder를 선호하는 이유와 @Builder 사용 시 자주 마주칠 Exception
    Java 2023. 1. 15. 21:44
    반응형
    728x90

    @Builder를 더 선호하는 이유

    1. 가독성
    2. 유지보수
    3. (setter를 사용하지 않아) 변경 가능성 최소화

     

    참고: https://mangkyu.tistory.com/163

    * TestPost 객체를 만들기 위한 Class가 있다고 가정하자.

     

    package com.s2lc.lbssapi.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @Builder
    public class TestPost {
        private String postId;
        private String title;
        private String content;
    }

    1. 가독성

    - new 연산자를 통해 객체를 생성할 경우

     

    TestPost post = new TestPost();
    post.setPostId(1);
    post.setTitle("테스트제목");
    post.setContent("테스트내용");

     

    이런 식으로 객체를 생성할 수 있다.

    이 경우, setter를 사용하여야 하기 때문에 domain에 @Setter를 붙여주거나 setter 메소드를 생성해줘야한다.

    setter를 사용한다는 것은 객체를 생성한 후에 수정이 일어날 수 있다는 것임으로 꼭 필요할 때가 아니면 지양(이유는 아래 3번에서 설명하도록 하겠다.)하는 것이 좋다.

    만약 기본생성자 외에도 @AllArgsConstructor@RequiredArgsConstructor를 domain에 붙여줬거나 따로 생성자들을 선언해줬다고 가정했을 때 아래처럼 new 연산자로 객체를 생성할 수 있다.

     

    // @AllArgsConstructor를 사용했을 경우
    TestPost post = new TestPost(1, "123", "123");

     

    이해를 쉽게 하기 위해서 제목과 내용임을 유추하기 힘든 문자열을 넣어줬다.

    하지만 아무리 테스트코드라고 해도 이렇게 무의미한 변수명이나 문자열을 넣는 것은 지양하도록 하자.

     

    아무튼 이 경우 new 연산자를 쓰면 각각의 파라미터가 어떤 멤버필드를 나타내는 지 알기 어렵다.

    즉, 가독성이 떨어진다.

     

    - Builder Pattern을 통해 객체를 생성할 경우

     

    TestPost post = TestPost.builder()
                    .postId(1)
                    .title("123")
                    .content("123")
                    .build();

     

    반면에 Builder Pattern을 쓸 경우 각각이 어떤 멤버필드에 대한 것인지 한 눈에 알아볼 수 있다.

    즉, 가독성이 좋다.


    2. 유지보수

    1번 가독성에서 설명한 것과 이어진다.

    만약 멤버필드가 여러 개인 객체를 만든다고 가정했을 때, 그 중 필요한 멤버필드들만 사용해서 객체를 생성한다고 가정하자.

    이 경우 모든 경우에 대해 생성자를 만들어두어야 new 연산자를 쓸 수 있을 것이다.

    반대로 멤버필드가 추가 되었다고 가정해볼 수도 있다.

    이 경우 new 연산자를 사용하기 위해 Class 내에 생성자들을 다 작성해두었다면, 그에 맞게 추가된 멤버필드들을 또 직접 추가해줘야할 것이다.

    하지만 Builder Pattern을 쓴다면 이러한 수고를 덜 수 있다.

    대신 Builder Pattern도 그냥 멤버필드들을 선언한다고 해서 쓸 수 있는 것은 아니다.

    이건 아래 #2에서 다루도록 하겠다.


    3. 변경가능성 최소화

    많은 개발자들이 수정자 패턴(Setter)를 흔히 사용한다. 하지만 Setter를 구현한다는 것은 불필요하게 변경 가능성을 열어두는 것이다. 이는 유지보수 시에 값이 할당된 지점을 찾기 힘들게 만들며 불필요한 코드 리딩 등을 유발한다. 만약 값을 할당하는 시점이 객체의 생성뿐이라면 객체에 잘못된 값이 들어왔을 때 그 지점을 찾기 쉬우므로 유지보수성이 훨씬 높아질 것이다. 그렇기 때문에 클래스 변수는 변경 가능성을 최소화하는 것이 좋다.

    인용 출처: https://mangkyu.tistory.com/163


    @Builder를 썼을 때 마주할 수 있는 Exception

    : java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2

     

     

    Mybatis가 미리 해당 인스턴스를 생성하게 된다. 하지만 위의 엔티티는 모든 인자가 포함된 생성자 (Builder) 만이 존재하기 때문에 인스턴스를 생성할 수 없어 문제가 발생하게 되는 것이다.

    인용 출처: https://lob-dev.tistory.com/35


    해결책

    : resultType인 domain에 @RequiredArgsConstructor와 @AllArgsConstructor를 붙여준다.

    cf) 수정자(setter)를 지양해야하지만, 가끔 분기에 따라서 멤버필드가 바뀔 필요가 있을 때에는 해당 멤버필드에만 @Setter를 주어 수정자를 쓸 수 있게 해주어 처리한다.

     

    package com.s2lc.lbssapi.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @Builder
    @RequiredArgsConstructor
    @AllArgsConstructor
    public class TestPost {
        private String postId;
        private String title;
        private String content;
    }
    728x90
Designed by Tistory.