현재 넥스트 스텝에서 ATDD, 클린 코드 with Spring 강의를 수강하고 있습니다.
1주 차 미션 구현중 예외 코드를 정의한 부분에 대해서 리뷰어님께서 @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);
}
}
빌더 패턴은 필드가 많아도 객체를 생성할 때 값만 가지고 객체를 생성할 수 있어 객체를 더 안전하고 유연하게 생성할 수 있습니다.
이번 리뷰를 통해 최대한 제어할 수 없는 것에 의존하지 않고 더 좋은 코드란 무엇인가 생각할 수 있는 시간이었던 것 같습니다.
'Java' 카테고리의 다른 글
[Java] OCP - 개방 폐쇄 원칙을 잘지키는 코드 (0) | 2023.12.13 |
---|---|
[ThreadLocal] 쓰레드 로컬 사용해보기 (0) | 2023.04.22 |
[Java] Mac에서 여러개의 Java 버전 확인하기 (0) | 2022.12.13 |
[java] stream (0) | 2022.07.27 |
[Java] equals()와 hascode() 메서드 (0) | 2022.07.09 |
댓글