getter 사용은 과연 금기인가
우아한테크코스에 합류하고 객체지향과 클린코드라는 개념을 차차 알아가는 중이다.
좋은 코드를 만들기 위해서는 getter / setter 사용을 자제하라고 한다.
따라서 나는 코드를 짜면서 getter / setter 사용을 모든 상황에서 제거하려고 노력했다.
하지만 얼마 전 체스 미션을 진행하면서 이에 관한 내 생각이 흔들린 것 같다.
View를 위해서가 아니더라도 getter를 사용해도 되는 상황이 있다는 것을 깨달았기 때문이다.
과연 getter 사용은 금기일까?
나는 아니라고 생각하게 되었고, 그 이유를 아래에서 풀어보고자 한다.
주관적인 내용이 많이 담겨있습니다.
"getter를 사용하지 마라"
왜 위와 같은 말이 나왔을까?
시스템은 객체들이 메세지를 보냄으로써 구성된다.
쉽게 말해, 시스템은 객체들의 협력으로 구성된다.
객체들은 스스로가 처리할 수 없는 부분을 다른 객체에게 위임한다.
"이건 내가 할 수 없는 일이니, 너가 처리해줘야겠어"
라고 말이다.
앞서 작성한 '왜 상태와 행위를 한 곳에서 관리해야 할까?' 라는 아티클에서
나는 상태란 행위를 구현하기 위한 수단이기 때문에 한 곳이 있는것이 맞다. 라는 결론을 내렸었다.
여기서 getter가 무슨 책임을 가지는지 한번 생각해보자.
getter는 인스턴스 변수를 반환하는 책임을 가진다.
이는 곧 객체의 내부 상태를 반환한다는 것과 같다.
하지만 상태는 행위를 구현하기 위한 수단이라고 했다.
즉, getter를 통해 상태를 드러내는 것은 행위를 구현하기 위한 수단을 외부에 드러내는 것과 같다.
블랙잭에서 Player 객체와 Deck 객체가 있다고 해보자.
Player 객체는 아래와 같이 Deck 객체를 상태로 가진다.
public class Player {
private final Deck deck;
public Player(final Deck deck) {
this.deck = deck;
}
}
만약 플레이어가 자신의 덱을 통해 '가진 카드의 총 개수'를 반환해야 한다고 해보자.
getter를 사용해 구현해보자.
public class Player {
private final Deck deck;
public Player(final Deck deck) {
this.deck = deck;
}
public Deck getDeck() {
return deck;
}
}
그리고 '가진 카드의 총 개수'를 구하기 위해 클라이언트는 다음과 같은 코드를 작성해야 한다.
Deck deck = player.getDeck();
int totalCount = deck.count();
...
getter를 사용했을 때 문제가 무엇인가?
위 코드의 단점은 무엇일까?
'가진 카드의 총 개수를 구하는 책임'을 Player가 아닌 다른 객체가 가져가게 된다.
해당 정보를 제공하기 위한 상태는 Player가 갖고 있는데도 말이다.
즉, getter를 사용하면 책임이 엉뚱한 곳에 수행될 수 있다는 것이 문제다.
내부 구현을 외부로 드러내니, 이는 당연한 수순이다.
이런 맥락에서 무분별한 getter 사용은 캡슐화를 저해시키고, 객체스럽지 않은 설계를 낳게 된다.
따라서 'getter 사용 대신 메세지를 보내라' 등과 같은 말이 나온 것이다.
사실 우리는 은연 중 getter를 사용하고 있다
"getter를 사용하지 마라" 라는 말은 개인적으로 오해의 소지가 있다고 생각한다.
앞서 설명한 내용을 싹 자르고 단순히 '사용하지 말라'고 하는 건 올바르지 않다고 생각한다.
사실 우리는 은연 중 getter를 사용하고 있는데도 말이다.
나는 체스 미션을 진행하면서 보드의 좌표를 저장하는 'Coordinate' 라는 객체를 만들었었다.
하지만 구현 과정에서 X축 좌표나 Y축 좌표가 필요할 때가 있었다.
좌표 값을 객체로 만들었기 때문에, 이는 어찌보면 당연한 절차였다고 생각된다.
처음에 나는 이를 악물고 getter를 안쓰려고 했고, 다음과 같은 코드가 나왔다.
public boolean hasLowerRowValueThan(final int target) {
return row < target;
}
public boolean hasBiggerRowValueThan(final int target) {
return row > target;
}
상태 검사를 통해 직접적으로 getter를 사용하지 않도록 했다.
위 방식이 getter 보다 나은 점은 무엇일까?
앞서 getter를 사용하면 안 되는 이유로 '내부 구현이 외부에 결합될 수 있기 때문'이라고 했다.
그럼 위 코드는 내부 구현을 드러내지 않는가?
위 코드를 사용하는 클라이언트는 세개의 메소드를 사용해서 상태를 유추할 수 있다.
따라서 내부 구현을 드러내고 있다. 본질은 같다.
즉, getter를 사용하나 위처럼 코드를 작성하나 같다는 말이다.
다만 위처럼 작성하는 경우는
- 메소드 네이밍을 통해 도메인적인 표현이 가능하고,
- 연산을 클라이언트에게 맡기지 않을 수 있다.
는 장점이 존재한다.
반면 getter를 사용하는 경우는 연산을 클라이언트가 맡아야 되지만, 메소드 중복을 줄일 수 있게 된다.
무엇을 선택할지는 자유이지만, 여기서 중요한 것은 두 방식이 근본적으로 차이가 없다는 점이다.
getter 사용에 대한 나만의 기준 확립하기
getter와 같은 성질을 가진 메소드들을 알아보았고,
그에 따라 getter는 먼 개념이 아닌 친숙한 개념이라는 것을 알았다.
어찌보면 getter의 존재는 당연하다고 생각된다.
객체는 속성을 갖고 있는 경우가 있다.
'옷' 객체의 색깔이라든가, '사람' 객체의 나이라든가..
어떤 상황에서는 이런 속성 자체만 필요한 경우가 있다.
즉, 메세지를 보내 무언가를 시키도록 요청하는 것이 아니라, 데이터 그 자체가 필요한 경우가 있다는 것이다.
이런 경우는 getter를 사용해도 된다고 생각한다.
하지만, getter의 대상이 엄연히 자율적인 객체라면 사용해서는 안된다고 생각한다.
앞서 말했듯이 객체의 자율성을 해치고, 캡슐화를 저해시키며, 최종적으로는 유지보수에 어려움을 가져다주기 때문이다.
아무튼, getter 사용 허용에 대한 나만의 기준은 다음으로 요약할 수 있을 듯 하다.
getter를 통해 반환된 값을 단순히 '값 그 자체'로써만 다룰 것인가?
그렇다면 사용해도 된다.
-> ex. 자료구조, 값 객체 등
getter를 통해 본인이 아닌 다른 객체의 책임을 수행하는가?
그렇다면 사용해서는 안된다.
-> ex. 핵심 도메인 객체 등
결론
getter 사용은 금기가 아니다.
때에 따라서는 객체의 속성이 필요한 경우가 있다.
이런 경우는 사용해도 괜찮다.
반면 getter를 통해 대상 객체의 책임을 빼앗아 수행하는 경우는 금기가 맞다.
앞으로 getter 사용이 고민될 때, 다음과 같은 질문을 스스로에게 던져보려고 한다.
getter를 통해 하려는 행위가 무엇인가?
결국 getter를 사용하니 마니 결정하는 것은 책임에 관한 문제라고 생각한다.
따라서 이러한 장단점을 명확하게 알고 있다면, 아무리 사용해도 문제가 되지 않을 듯 하다.
학습을 위한 아티클이기에, 혹시 개선해야 되는 내용이 있거나, 이야기 나눠보고 싶은 부분이 있다면 댓글로 언제든지 알려주세요!
'우테코 5기' 카테고리의 다른 글
[레벨 1 미션] 체스 미션 회고 (1) | 2023.03.29 |
---|---|
설계 관점에서 바라본 불변 객체 (1) | 2023.03.29 |
[레벨 1 미션] 블랙잭 게임 미션 회고 (7) | 2023.03.22 |
[레벨 1 미션] 사다리 타기 미션 회고 & 학습기록 (1) | 2023.03.12 |
[레벨 1 강의] 좋은 코드 (0) | 2023.03.01 |