[Clean code] getter setter에 대한 고찰

박상혁·2023년 5월 11일
0

Refactoring

목록 보기
1/1

Domain을 설계할 때 getter setter를 남발하지 말라는 말은 들었지만, 그 이유는 알지 못했습니다.
이번 포스팅을 통해 domain 객체의 캡슐화 및 정보은닉을 위해 getter setter 메서드를 어떤 식으로 사용해야 하는지 알아보겠습니다.

❗ getter, setter 메서드를 앞으로 엑세스 함수라는 명칭을 사용하여 표현하겠습니다.


목차
1. 엑세스 함수를 사용하면 정보은닉? 캡슐화?
2. 필드 값을 변경하려는 목적을 표현해라
3. 필요한 메서드만 구현해라


엑세스 함수를 사용하면 정보은닉? 캡슐화?

// Board 객체
private String title;
private String content;
    
getter 메서드
    
setter 메서드

// 다른 객체
board.getContent();

객체의 필드값을 직접 참조하여 변경하는 것은 해당 객체 안의 정보를 직접 볼 수 있고 수정할 수 있어 정보은닉을 할 수 없습니다.

이에 필드 값은 다른 객체에서 참조할 수 없게 private로 구현하며, getter setter 메서드를 public으로 만들어 사용합니다.

액세스 함수를 사용하게 되면 필드값을 직접 참조할 필요없이 값을 수정할 수 있습니다.

하지만 정보은닉이 되진 않습니다.

결국 해당 메서드를 사용하여 값을 수정하는 것이므로, 이를 다른 곳에서 남발하게 되면 결국 필드 값을 직접 참조하여 수정하는 방식과 다를 게 없으므로 똑같이 정보은닉을 망칩니다.

때문에, 이를 최소한으로 사용해야 합니다.

최소한으로 사용해야할 뿐만 아니라 몇가지 더 고려할 사항이 있습니다.
진짜 캡슐화를 만족시킬 수 있는 방법을 소개합니다.


필드 값을 변경하려는 목적을 표현해라

👉 도메인 규칙(검증)은 도메인 영역에 포함시키자

공개 set 엑세스 함수를 사용하게 되면 한 곳에 응집되지 않기 때문에 유지보수에 더 많은 시간이 걸립니다.
도메인 영역에 검증하는 코드를 포함시켰습니다.

setState(String state) {
	if(this.content.contain("욕")) {
        throw new IllegalArgumentException("욕설이 담긴 게시물은 게시물이 될 수 없습니다.");
    }
    if(this.state.equals("곧 삭제")) {
    	throw new IllegalArgumentException("곧 삭제되는 게시물은 인기 게시물이 될 수 없습니다.");
    }
    this.title = title;
}

이에 비즈니스 로직에 중복을 제거하여 코드가 간결해졌습니다.

public void updateStateToPopular(Board board) {
    board.setTitle("popular");
}

👉 setTitle()에 규칙(검증) 코드를 다 포함시키려 하지 말고, 메서드를 아예 나누어라

비즈니스 로직에 있던 메서드를 가져온다고 생각하면 됩니다.
목적을 분명히 하여 메서드 네이밍에 반영하면 좋습니다.

// Board domain 객체
public void updateStateToPopular() {
    if(this.content.contain("욕")) {
        throw new IllegalArgumentException("욕설이 담긴 게시물은 게시물이 될 수 없습니다.");
    }
    if(this.state.equals("곧 삭제")) {
    	throw new IllegalArgumentException("곧 삭제되는 게시물은 인기 게시물이 될 수 없습니다.");
    }
    this.title = "popular;
}

public void updateTitleToHello() {
	if(this.title.equals("공략")) {
    	throw new IllegalArgumentException("공략은 제목을 수정할 수 없습니다");
    }
    
    this.title = "안녕"
}

도메인 객체 내부에서만 구현함으로써 더이상 도메인 영역 외의 영역에서는 신경 쓸 필요가 없게 되었습니다.

공개 set 엑세스 함수에 있던 Board 타입 인자를 사용하지 않음으로 더이상 도메인 외의 영역에서 Board 객체의 값을 수정하는 로직이 없어지게 되었습니다.

👉 공통 규칙(검증) 로직이 존재한다면 set을 사용하되, 도메인 객체내에서만 활용하도록 private로 만들어라

만일 위의 검증 로직이 다른 메서드에서도 사용된다면, 코드의 중복을 방지하기 위해 리펙토링을 진행할 수 있습니다.

private setState(String state) {
	containAbuse();
    this.state = state;
}

private void containAbuse() {
	if(this.content.contain("욕")) {
        throw new IllegalArgumentException("욕설이 담긴 게시물은 게시물이 될 수 없습니다.");
    }
}
public void updateTitleToHello() {
	if(this.title.equals("공략")) {
    	throw new IllegalArgumentException("공략은 제목을 수정할 수 없습니다");
    }
    
    setState("popular");
}

👌👌👌 이를 통해 도메인 객체의 정보은닉캡슐화를 온전히 지킬 수 있게 되었습니다.


필요한 메서드만 구현해라

👉 Dto? 그냥 set 메서드를 필요한 만큼만 구현하자

public class Board {
	private Long id;
    private String title;
    Private String content;
    
    public void setId(Long id) {
    	this.id = id;
    }
}

lombok의 @Setter를 사용하게 되면 쉽게 공개 set 엑세스 함수를 만들 수 있습니다.
하지만 이는 모든 필드값들에 대한 함수를 만들게 되므로 구현하면 안되는 setId()까지 만들어지게 됩니다.
id는 DB안에 있는 Board 테이블의 중요한 키 정보이므로 이를 수정하면 절대 안됩니다.

그냥 구현 안하면 됩니다.
getter setter 어노테이션을 냅다 적기 전에 우선 필드값을 먼저 보고, 비즈니스 로직에 필요한 메서드만을 구현하면 됩니다.

각 객체의 역할과 책임을 확실하게 정의해라

예를들어,
domain 객체같은 경우 검증 로직과 필드를 수정하는 역할이며,
dto 객체같은 경우는 Transfer 역할에 맞게 데이터를 옮기는 역할, 원하는 데이터만 입력 및 출력할 수 있도록 하는 역할을 담당합니다.

이처럼 각자 역할에 맞게 메서드를 구현하여 책임을 확실히 정의하고 분리하는 것이 좋습니다.


요약

각 상황별로 각각의 공개 메서드를 만들어 그 안에 도메인 규칙을 구현하고, 메서드를 외부에 제공한다.
냅다 getter setter 어노테이션 적기 전에 필요한 메서드만을 구현하자.

주로 값이 정해져 있을 때(회원 등급, 인기 게시물 같은 state를 저장할 때)


참조

setter 쓰지 말라고만 하고 가버리면 어떡해요
엑세스 함수(getter/setter)를 올바로 사용하기 :: 개똥이야기

profile
개발 노트

0개의 댓글