Getter와 Setter는 지양하는게 좋다

청포도봉봉이·2024년 3월 7일
2

java

목록 보기
17/20
post-thumbnail

엔티티나 DTO를 사용할때 GetterSetter를 지양하라는 이야기를 들어봤을 것입니다. 얘기만 들어봤지 이거에 대한 내용을 자세히 몰라 정리해보기로 하였습니다.

Getter와 Setter를 사용하는 이유

객체 지향의 원칙 중 하나는 정보 은닉(Infomation Hiding)입니다. 즉 캡슐화입니다. 객체의 구체적인 정보를 외부에 노출하지 말라는 것입니다. 이러한 이유 때문에 자바에서는 클래스를 작성할 때 모든 필드를 private으로 숨기고 public 메서드를 통해 간접적으로 필드를 다루게 됩니다.

Getter와 Setter가 그래서 뭐?!

따라서 private으로 필드를 숨겨 놓고 Getter와 Setter를 모두 public으로 열어서 사용하는 것은 정보 은닉을 없애는 행위라고 할 수 있습니다.

필드 자체는 숨겨놓고 그 값을 Getter로 조회할 수 있고 Setter를 통해 수정할 수 있으면 이 정보가 정말 숨겨졌다고 할 수 없는 것이다!

Setter를 지양해야 하는 이유

Setter를 사용하면 필요할 때마다 객체의 상태를 바꿀 수 있기 때문에 굉장히 편리해보인다. 하지만 그 이면에는 큰 단점이 존재한다.

Setter는 값을 바꾸는 이유를 드러내지 않는다.

public class Apple {
    private int count;

    public Apple(int count) {
        this.count = count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

Apple freshApple = new Apple(100);
freshApple.setCount(50);

위 코드에서

  • freshApple를 처음 가게에 가져올때 100개를 가져온 것을 알 수 있다.
  • 하지만 freshApple.setCount(50);만 봤을때는 처음에 50개를 가져온 것인지 사과가 팔려서 50개가 된 것인지 알 수 없다.

이렇게 Setter를 사용하면 객체의 속성이 갖는 값을 바꾼 이유를 명확하게 알 수 없다.

다른 객체들로 책임이 분산된다.

Setter를 사용하면 해당 객체가 해야할 일(책임)을 다른 객체가 해야할 수도 있다.

public class AppleService {
    public void sellApple(int sellCount) {
        Apple apple = new Apple(1000);
        int nowCount = apple.getCount() - sellCount;
        
        if (nowCount < 0) {
            throw new IllegalArgumentException("남아있는 사과가 부족합니다...");
        }
        
        apple.setCount(nowCount);
    }
}

원래라면 Apple 객체에서 남은 사과의 갯수를 관리해야 하지만 이 코드에서는 사과를 팔때 AppleService에서 사과 갯수가 0개보다 작은지를 확인해주고 있는 것이다. 이는 Apple 객체가 책임을 다하지 않았기 때문에 AppleService가 대신 일을 하고 있는 것이다.

Getter를 지양해야 하는 이유

Getter는 단순히 값만 조회하는 건데 뭐가 문제일까? 그 이유를 알아보자.

Getter는 조회로 끝나지 않는 경우가 많다.

단순히 객체의 상태값이 뭔지 알고 싶어서 Getter를 사용하는 경우도 많지만, 많은 경우에는 Getter로 상태값을 조회하면 그 값이 조건에 맞는지 확인하는 비즈니스 로직을 수행하게 된다.

아까 봤던 코드를 다시 봐보자!

public class AppleService {
    public void sellApple(int sellCount) {
        Apple apple = new Apple(1000);
        int nowCount = apple.getCount() - sellCount;
        
        if (nowCount < 0) {
            throw new IllegalArgumentException("남아있는 사과가 부족합니다...");
        }
        
        apple.setCount(nowCount);
    }
}

이 코드에서는 현재 사과의 갯수를 Apple 객체에서 조회해 판매 갯수를 차감해보고 그 갯수가 0보다 작은지 확인하고 있다. 다시 말해

  1. 사과 몇 개 남았니?
  2. 남은 갯수랑 판매할 갯수를 한번 빼보자
  3. 그 갯수가 0보다 작은지 보자

위와 같은 불필요한 과정을 거치고 있는 것이다. 그냥 판매할 갯수만 전달하여 갯수가 충분한지만 물어보면 되는걸 그걸 직접 확인하고 있다.

Getter를 통해 조건을 검사하면 변경에 취약하다

아래는 학생 성적에 따른 등급을 출력하는 예제이다.

public class Student {
    private int score;

    public Student(int score) {
        this.score = score;
    }

    public int getScore() {
        return score;
    }
}

public void studentGrade(Student student) {
        if (student.getScore() > 90) {
            System.out.println("A");
        }
        else if (student.getScore() > 60) {
            System.out.println("B");
        }
        else {
            System.out.println("C");
        }
    }

그런데 요구사항이 변경되어 학생이 단순 성적이 아니라 발표 성적과 숙제 성적으로 세분화된다면 어떻게 될까?

public class Student {
    private int presentationScore;
    private int homeworkScore;
    
    ...
}

더 이상 존재하지 않는 기존의 성적에 조회를 하려고 할테니까 당연히 컴파일 에러가 발생한다.

이 뿐만 아니라 기존에 getScore를 통해 값을 조회하고 있던 코드를 모두 수정해야 한다. 매우 끔찍한 일이다.

그래서 어떻게 하라는거야?!

지금까지 SetterGetter를 사용할 때의 취약점에 대해 알아보았다. 하지만 프로젝트를 하다보면 객체의 상태를 바꾸거나 조회해야 할때가 분명히 존재한다.

Setter 대신 명확한 의도를 가진 메서드를 사용하자

public class AppleService {
    public void sellApple(int sellCount) {
        Apple apple = new Apple(1000);
        int nowCount = apple.getCount() - sellCount;
        
        if (nowCount < 0) {
            throw new IllegalArgumentException("남아있는 사과가 부족합니다...");
        }
        
        apple.setCount(nowCount);
    }
}

이 코드를

public class Apple {
    ...

    public void sellApple(int sellCount) {
        if (count < sellCount) {
            throw new IllegalArgumentException("남아있는 사과가 부족합니다...");
        }
        count -= sellCount;
    }
}

위와 같이 변경한다면 판매할 갯수만 전달하여 Apple 객체에서 확인하고 판매할 갯수를 빼라고 전달하는게 된다. 코드가 간결해졌으며 의도가 명확해졌다. 혹시나 로직이 변경되어도 sellApple()만 수정하면 된다.

Getter로 조건을 검사하지 말고 결과를 반환하게 하자

아까 학생 등급을 매기는 예제에서는 학생 성적이 세분화되면서 문제가 발생했다. 이를 방지하기 위해 학생 객체 자체에서 등급을 반환하라고 하면 아래와 같은 코드가 된다.

public class Student {
    private int presentationScore;
    private int homeworkScore;

    public void studentGrade() {
        int totalScore = presentationScore + homeworkScore;
        
        if (totalScore > 90) {
            System.out.println("A");
        }
        else if (totalScore > 60) {
            System.out.println("B");
        }
        else {
            System.out.println("C");
        }
    }
}

이제 더 이상 학생 객체말고는 등급을 출력하기 위해 학생의 상태를 알 필요가 없다. 그저 학생에게 등급이 뭐냐고 물어보면 되는 것이다.

마치며

이상으로 Getter와 Setter를 지양해야 하는 이유와 예시에 대해서 살펴보았다.

무분별한 Getter와 Setter의 사용은 객체 지향의 핵심인 정보 은닉을 해치게 되고 외부로 부터 객체의 상태를 알려주고 변경할 수 있게 되므로 의도하지 않은 동작을 수행할 수 있어 문제가 발생할 수 있다.

따라서 꼭 필요한 경우가 아니라면, 책임이 있는 객체에게 직접 요청할 수 있는 메서드를 작성하여 사용하는 것이 좋다.

참고한 글
https://colabear754.tistory.com/173

profile
서버 백엔드 개발자

0개의 댓글