Setter를 지양하자 근데 어떻게?

이기태·2023년 3월 20일
2

TIL

목록 보기
1/3
post-thumbnail

혼자 공부한 내용을 기록으로 남기고 있습니다. 만약에 내용이 틀렸을 경우에 댓글로 알려주시면 바로 수정하겠습니다! 도움이 되셨으면 댓글이나 좋아요 남겨주시면 감사하겠습니다:)

기존에 무지성으로 김영한 강사님 코드만 복붙했던 나 자신을 반성하며 헷갈리는 개념들을 확실하게 하려고 한다.

김영한 강사님은 강의에서 setter를 주로 쓰시지만 이는 편의상 쓴거고 실전에서는 setter를 지양하는게 좋다고 한다. 그래서 이에 대해 이유와 그럼 setter 대신 어떤걸 쓰는지 한번 정리해보자.

👊🏻 Setter 예시

@Getter @Setter
class User {
  private Long id;
  private Integer age;
}

User user = new User();
user.setId(1L)
user.setAge(25);

나는 평소에 이런식으로 써왔다. 이렇게 아무 생각없이 Setter를 썼을 때 문제점은 뭘까?

지금 User의 필드가 id와 age 2개밖에 없지만 만약 10개가 되면 set을 10번 써줘야한다. 그러면 객체 하나를 만들기 위해 set 함수를 여러번 호출해야하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓인다고 한다. 또 public으로 설정되어있기 때문에 어떤 곳에서든 변경이 가능하다. 개인 프로젝트라면 상관이 없겠지만 현업에서 고객의 중요한 데이터가 바뀌면 대참사가 나는 것이다. 이런 이유로(물론 이유가 더 있겠지만) 모두들 setter를 지양하라고 말하는 것 같다!

👊🏻 Setter 대체제 → 생성자

그러면 set 대신에 객체에 값을 지정해주는 방식은 무엇이 있을까? 바로 생성자이다. 위의 예시를 set대신에 생성자로 바꿔보자

@Getter
class User {
  private Long id;
  private Integer age;
}

// 생성자
public User(Long id, Integer age){
	this.id = id;
	this.age = age;
}

User user = new User(1L, 25);

이런식으로 해주면 setter를 사용하지 않고도 user 객체를 생성할 수 있다. 그럼 여기서 나같은 초보는 또 궁금해진다. ‘set을 안사용하면 값을 수정할 때는 어떡해?’ user의 나이 정보를 바꾼다고 가정해보자. 그럼

@Getter
class User {
  private Long id;
  private Integer age;
}

// 생성자
public User(Long id, Integer age){
	this.id = id;
	this.age = age;
}

// 유저 나이 변경
public void changeAge(Integer age){
	this.age = age;
}

이런식으로 비즈니스 로직을 Domain 파일 안에 넣어줌으로써 해결할 수 있다. 이는 DDD (Domain Driven Design)과도 연결이 되는 것 같은데 (아닐 수도 있음) 좋은 코드 방식인 것 같다.

👊🏻 생성자의 단점

나는 처음에 제목을 보고 띠용했다. 이런 완벽한 것 같은 방식도 좋지 않은 방식이라고 한다. 도대체 왜..?

우선 예시를 보자. 유저의 필드에 닉네임과 이메일 정보가 추가되었다고 가정해보자.

@Getter
class User {
  private Long id;
  private Integer age;
  private String nickname;
  private String email;
}

// 생성자
public User(Long id, Integer age, String nickname, String email){
	this.id = id;
	this.age = age;
	this.nickname = nickname;
	this.email = email;
}

// 유저 나이 변경
public void changeAge(Integer age){
	this.age = age;
}

닉네임, 이메일 정보를 추가하면 기존의 코드는 이렇게 변할 것이다. 하지만 여기서 닉네임과 이메일 정보는 필수 정보가 아니어서 없을 땐 null로 우선 DB에 저장해줘야한다고 가정해보자. 이 상황을 코드로 짠다면

@Getter
class User {
  private Long id;
  private Integer age;
  private String nickname;
  private String email;
}

// 생성자
public User(Long id, Integer age, String nickname, String email){
	this.id = id;
	this.age = age;
	this.nickname = nickname;
	this.email = email;
}

// 유저 나이 변경
public void changeAge(Integer age){
	this.age = age;
}

// 생성자 단점 예시
User user = new User(1L, 25, null, null);

이렇게 null이 2개 들어갈 것이다. 물론 나 혼자 코드를 짤 때는 ‘음, 저 뒤에 null 2개는 닉네임과 이메일 정보로군, 클라이언트로부터 넘어오지 않아서 null 처리를 한 것이야’ 라고 생각하겠지만 다른 개발자들이 볼 때는 무슨 정보인지 전혀 모르는 것이다. 또 지금은 필드가 4개이지만, 10개라고 가정해보자. 그럼 한줄에 너무 많은 정보가 들어가서 가독성이 형편없어 질 것이다. 개발자는 필연적으로 다른 개발자들과 협업할 수 밖에 없기에 이런 식의 코드는 좋지 않다. 그래서 나온게 빌더 패턴 (Builder Pattern) 이다.

👊🏻 생성자 → Builder

우선 코드부터 봐보자

@Getter
@Builder
@AllArgsConstructor
class User {
  private Long id;
  private Integer age;
  private String nickname;
  private String email;
}

public User(Long id, Integer age, String nickname, String email){
	this.id = id;
	this.age = age;
	this.nickname = nickname;
	this.email = email;
}

User user = User.builder()
			.id(1L)
			.age(25)
			.nickname(null)
			.email(null)
			.build();

이렇게 생성자와 보이는 건 같지만 위에 @Builder 어노테이션을 붙여주면 된다. Lombok에는 이미 builder 어노테이션이 있어서 편리하게 작성할 수 있다. 이게 가독성이 훨씬 좋지 않은가? 무조건 빌더 패턴을 사용해야하는 게 아니고 필드가 많을 때 사용하면 좋을 것 같다는 생각이 든다.

여기서 그럼 궁금한 점이 있다. ‘Builder 어노테이션까진 알겠는데, @AllArgsConstructor 이 친구는 도대체 뭐지?’ 이 친구에 관련해서는 NoArgsConstructor, Builder 와 함께 다음 글에서 정리해보자!

profile
사람들에게 도움이 되는 서비스를 만들기 위해 항상 노력하는 백엔드 개발자 이기태

0개의 댓글