Java

@AllArgsConstructor를 지양해야 하는 이유

수수한개발자 2023. 8. 2.
728x90

현재 넥스트 스텝에서 ATDD, 클린 코드 with Spring 강의를 수강하고 있습니다.

 

1주 차 미션 구현중  예외 코드를 정의한 부분에 대해서 리뷰어님께서 @AllArgsConstructor를 지양하신다고 코멘트를 주셨습니다.

 

@AllArgsConstructor를 지양해야 하는 이유

그래서 이번 글은 @AllArgsConstructor 에 대해서 알아보고 사용법, 장점, 단점을 설명하고자 글을 작성합니다.

 

 

@AllArgsConstructor 

@AllArgsConstructor는 롬복에 포함되어 있는 어노테이션으로 모든 멤버 변수에 대한 생성자를 자동으로 생성해 주는 어노테이션입니다.

 

사용법

build.gradle

// lomnok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// test lombok
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

 

Member

@Getter
@AllArgsConstructor
private static class Member {
    private String firstName;
    private String lastName;
}

위와 같이 작성시 컴파일 시 순서대로 파라미터로 들어간 생성자가 생성되게 됩니다.

 

장점

장점은 위에서 말했듯이 멤버변수에 대한 생성자를 어노테이션 하나로 만들어 줍니다. 필드가 많아진다면 엄청 편할 것이라고 생각됩니다.

 

단점

지금은 필드가 두 개뿐이지만 만약 주소, 폰번호 등등 같은 타입의 필드가 여러 가지가 된다면 버그를 초례하는 코드를 작성한다고 생각합니다.

그 이유는 다음과 같습니다.

1. 자동으로 생성해 준다.

2. 타입만 맞다면 에러를 컴파일 시점에 에러가 발생하지 않는다.

 

위의 2가지가 왜 문제일까요?

좋은 코드를 짠다고 생각하는 것 중의 하나가 외부 라이브러리에 의존하지 않고 또 제어할 수 없는 것에 의존하지 않아야 된다고 생각합니다.

그렇기 때문에 자동으로 생성되는 것에 대해 추후에 문제가 발생했을 때 내가 제어할 수 없기 때문에 버그를 잡기 어렵고 

타입만 맞다면 컴파일 에러를 발생하지 않기 때문에 협업할 때 문제가 생길 수 있습니다.

 

예를 들어 보겠습니다.

내가 작성하고 내가 생각한 코드

@Getter
@AllArgsConstructor
private class Member {
    private String firstName;
    private String lastName;
}

@Test
void createMember() {
    Member member = new Member("성", "이름");
    assertThat(member.getFirstName()).isEqualTo("성");
    assertThat(member.getLastName()).isEqualTo("이름");
}

firstName은 성이고 lastName은 이름입니다. 

 

다른 사람이 작성한 코드

@Test
void createMember2() {
    Member member = new Member("이름", "성");
    assertThat(member.getFirstName()).isEqualTo("성");
    assertThat(member.getLastName()).isEqualTo("이름");
}

위와 같이 객체를 생성하면 테스트는 깨지게 됩니다.

하지만 생성자를 찾아봐도 프로덕션 코드에는 @AllArgsConstructor 어노테이션만 있을 뿐 생성자를 찾을 수 없어 한 번 더 확인해야 되게 됩니다.

 

그래서 애초에 버그를 발생할 가능성을 줄이자는 게 제 생각인데요.

 

개선점

 

1. 생성자를 직접 만든다.

private class Member {
    private String firstName;
    private String lastName;

    public Member(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

위와 같이 생성자를 직접 만들어 객체를 생성하게 하는 방법입니다.

 

 

2. 빌더 패턴 사용

생성자를 직접 만든다면 만약 필드가 엄청 많게 되면 그 순서에 맞게 전달하여 객체를 생성해야 되어서 또 다른 버그를 만들 수 있습니다.

그래서 나온 게 빌더 패턴입니다.

 

@Test
void createMemberWithBuilder() {
    Member member = Member.builder()
            .firstName("성")
            .lastName("이름")
            .build();

    assertThat(member.getFirstName()).isEqualTo("성");
    assertThat(member.getLastName()).isEqualTo("이름");
}

    
private class Member {
    private String firstName;
    private String lastName;

    public Member(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public static MemberBuilder builder() {
        return new MemberBuilder();
    }

    public static class MemberBuilder {
        private String firstName;
        private String lastName;

        MemberBuilder() {
        }

        public MemberBuilder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public MemberBuilder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Member build() {
            return new Member(this.firstName, this.lastName);
        }
    }

 

빌더 패턴은 필드가 많아도 객체를 생성할 때 값만 가지고 객체를 생성할 수 있어 객체를 더 안전하고 유연하게 생성할 수 있습니다.

 

이번 리뷰를 통해 최대한 제어할 수 없는 것에 의존하지 않고 더 좋은 코드란 무엇인가 생각할 수 있는 시간이었던 것 같습니다.

 

728x90

댓글