본문 바로가기
Java

자바에서 setter를 지양해야 하는 이유

by 코더 제이콥 2023. 3. 4.

1. setter-pattern을 지양해야 하는 이유

setter 패턴은 프로그래밍 언어에서 가장 유명한 패턴입니다. 심지어 많은 개발툴에서는 getter와 setter를 손쉽게 만들 수 있는 스니펫을 제공합니다. 컴퓨터 학원을 다녔던 저도, 처음 배웠던 패턴이 getter와 setter란 것을 생각하면 말 다했죠😂

 

그러나 setter의 사용에 대해서는 자바 개발자 뿐만 아니라, 다른 언어의 개발자 분들도 이렇게 말씀하십니다.

setter, 사용하지마!

궁금했습니다. 왜 setter는 사용하면 안 되는 것일까요?

 

1. 캡슐화 원칙 위반

setter를 사용하면 객체 내부의 상태에 직접 접근이 가능합니다. 이는 캡슐화 원칙(https://ko.wikipedia.org/wiki/%EC%BA%A1%EC%8A%90%ED%99%94)에 위배됩니다.

 

2. 사용한 의도를 쉽게 파악하기 어려움

Member member = new Member();
member.setName("제이콥");
member.setAge(25);

위와 같은 코드가 있다고 봅시다. 이 코드는 Member 객체를 생성한 다음 저장하기 위해 짠 것일까요, 코드를 수정하기 위해 짠 것일까요? 정답은 이 코드를 작성한 저, 제이콥만 알 수 있습니다.

 

 

3. 일관성 유지의 어려움

public class Member {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void updateMember(String name, int age){
        this.name = name;
        this.age = age;
    }
}

 

위와 같은 코드가 있습니다. 여러분은 이 코드에서 Member를 수정하셔야 합니다. 그럼 어떻게 수정하실 껀가요? setter를 통해서? updateMember를 통해서?

 

예를 들어봅시다. 새해가 지나서 다음 년이 된겁니다. 그럼 모든 Member의 age는 한 살이 더해져야 합니다. age만 변경하고 싶은데 이런 경우에는 어떻게 수정해야 할까요? 보니까 updateMember 메소드는 name과 age를 파라미터로 받고, age만 수정할 수 있는 메소드는 setter를 제외하고 보이지 않습니다. 그럼 여러분은 setter를 사용하실 건가요, updateMember 메소드를 이용하실 건가요? 분명 setter는 좋지 않다고 들었는데 말이죠.

 

코드 작성자인 저는 일부로 updateMember 메소드를 통해서만 Member 객체의 수정을 열어두었습니다. 일부로 name이나 age의 단독 수정을 막은 것이죠. (이유는 없어요,,)

 

하지만 위 코드를 보신 여러분은 setter를 보신 순간, 그냥 setter를 사용해서 age 수정해버리지란 생각을 하셨을 겁니다. 코드를 작성한 제 의도는 필드의 단독 수정을 금지하는 것이었는데 그것이 전달되지 않았던 겁니다. 그리고 이것은 나중에 큰 장애로 이뤄질 가능성이 큽니다.

 

2. setter의 대안책은 무엇인가?

그럼 setter의 대안책으로 무엇이 있을까요? 몇 가지 알아봤습니다.

 

생성자를 통한 초기화

객체를 생성할 때 생성자를 통해 초기화 하는 방법입니다.

public class Member {
    private String name;
    private int age;

    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

Member 객체를 리펙토링 해봤습니다. 객체를 처음 생성할 때 parameter로 name과 age를 넣어 초기화합니다.

 

Member member = new Member("제이콥", 25);

 

또 기본 생성자는 일부로 생성하지 않았습니다. 객체를 생성할 때 무조건 필드의 값을 수정하도록 한 것이죠. 단, 생성자 초기화는 필드가 추가되거나 생성자의 파라미터로 넘어온 인자들의 순서가 변경될 때 리펙토링하는 과정이 상당히 빡샙니다.

 

builder 패턴

빌더패턴은 생성자 생성과 달리, 생성자에 들어가는 인자의 순서가 변경되어도 상관 없습니다. 위에서는 첫 번째 인자로 무조건 name이 들어가야 했지만, 빌더 패턴에서는 age를 먼저 초기화해도 되는 것입니다. 또 유지보수와 가독성이 향상됩니다.

@Builder
@AllArgsConstructor
public class Member {
    private String name;
    private int age;
}
Member member = Member.builder()
        .name("제이콥")
        .age(25)
        .build();

그런데, 가독성에 대해서는 인스턴스의 필드 수많큼 차이나는 거 같습니다. 제 경우, 많은 필드들이 있으면 세로로 길게 늘어져 보기 썩 좋지 않았던 경험이 있었습니다.

 

생성 메소드 사용

 

객체 상태를 변경하는 대신, 메소드를 활용해 객체를 처리하는 방식입니다. 이 방식은 객체를 변경하는 것이 아니라, 메소드를 호출해서 새로운 객체를 반환하는 방식입니다.

 

public class Member {
    private String name;
    private int age;

    private Member(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static Member createMember(String name, int age){
        Member member = new Member(name, age);
        return member;
    }
}

 

저의 경우, 이 방식은 JPA에서 연관 관계 설정 때문에 자주 사용했습니다.

 

3. 결론


setter 사용은 좋은 습관이 아니다!

 

생성 메소드를 사용하든지, 수정(update) 메소드를 사용하든지 중요한 것은 변경 시점을 알 수 있어야 한다는 것입니다. setter는 이게 생성한 이후에 초기화하는건지, 수정하는 건지 명확하지 않지만, updateMember 메소드는 생성은 이미 이루어졌고, 수정하는 메소드란 것이 명확하게 들어납니다.

 

setter 말고, updateMember처럼 이 메소드가 생성하는 메소드인지 변경하는 메소드인지, 생성과 수정이 어떤 한 부분에서 명확하게 들어나는 메소드를 사용하는 것이 좋습니다.