본문 바로가기
학습/Java

public API에서 AllArgsConstructor 사용하지 말 것!!!

코동이 2021. 2. 21.

서론

class를 변경하려고 할 때 public API가 적합한지에 대해 생각해 본 적이 있습니까? LomBok을 사용할 때, 특히 몇가지 놓치기 쉬운 것들이 있습니다. AllArgsConstructor 어노테이션이 많은 문제를 일으킬 것입니다.

 

문제가 무엇인가?

AllArgsConstructor를 사용한 간단한 클래스에 대해 보겠습니다.

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}

 

이제, 테스트에서 만들어진 생성자를 이용할 수 있습니다.

def 'use generated allArgsConstructor'() {
    when:
        Person p = new Person('John', 'Smith', 30)
    then:
        with(p) {
            firstName == 'John'
            lastName == 'Smith'
            age == 30
        }
}

테스트는 통과했습니다!

 

Person class에 새로운 필드를 추가해보도록 하겠습니다. - email

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
    private String email;
}

 

새로운 필드를 추가하는 것은 적합한 변경으로 허용할 수 있습니다. 그러나 테스트는 실패합니다.

groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.github.alien11689.allargsconstructor.Person(java.lang.String, java.lang.String, java.lang.Integer)

 

어떻게 이 문제를 해결 할 수 있을까요?

 

1. 필드를 추가한 이후에 이전의 생성자를 추가할 것

 

아직도 AllArgsConstructor를 사용하고 있다면, 이전에 작성한 생성자에 내용을 추가함으로써 적합성을 확인해야만 합니다. 즉, null이라는 부분을 따로 추가해야 합니다.

@Data
@AllArgsConstructor
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
    private String email;

    public Person(String firstName, String lastName, Integer age) {
        this(firstName, lastName, age, null);
    }
}

이제, 우리 코드는 다시 테스트를 통과합니다.

 

2. lombok.Data 어노테이션만 사용

 

단지 Data 어노테이션만 사용한다면, final 필드를 가진 생성자만이 생성 될 것입니다. 왜냐하면, Data는 

RequiredArgsConstructor를 포함하기 때문입니다.

@Data
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}

class PersonTest extends Specification {
    def 'use generated allArgsConstructor'() {
        when:
            Person p = new Person('John', 'Smith')
            p.age = 30
        then:
            with(p) {
                firstName == 'John'
                lastName == 'Smith'
                age == 30
            }
    }
}

email이라는 새로운 필드를 추가하더라도 이제 테스트는 통과합니다.

 

3. Builder 어노테이션을 사용

 

Builder 어노테이션은 PersonBuilder 클래스를 만드는데, 새로운 Person 객체를 만들도록 돕습니다.

@Data
@Builder
public class Person {
    private final String firstName;
    private final String lastName;
    private Integer age;
}

class PersonTest extends Specification {
    def 'use generated allArgsConstructor'() {
        when:
            Person p = Person.builder()
                    .firstName('John')
                    .lastName('Smith')
                    .age(30).build()
        then:
            with(p) {
                firstName == 'John'
                lastName == 'Smith'
                age == 30
            }
    }
}

email 필드를 추가하고나서도 테스트는 통과합니다.

 

 

결론적으로, 3번을 추천합니다. 또한, 가급적이면 @Builder를 이용하여 직접 만든 생성자를 사용하는 것이 좋습니다.

Builder() 생성자는 변수의 이름으로 매개변수를 설정할 수 있기 때문에 순서에 구애받지 않습니다. 

 

특히, 변수가 선언된 순서대로 lombok은 생성자를 만들기 때문에,

만약 개발자 입장에서 임의적으로 변수의 순서를 바꾼다면 모든 생성자의 매개변수 위치가 꼬일 수 있습니다.

 

 

 

 

참조

dzone.com/articles/do-not-use-allargsconstructor-in-your-public-api

반응형