한 것
페어 프로그래밍
두명이 짝을 지어, 하나의 컴퓨터를 이용해 한명(드라이버)은 코드를 직접 작성하면서 자신의 의도를 설명하고, 다른 한명(네비게이터)은 드라이버의 코드와 의도에 의문을 제시하며 함께 프로그래밍을 하는 방법.
알게 된 것
getter에 대하여
객체 지향 프로그래밍에서는 객체의 공개된 행동(메서드)을 통한 객체 사이의 의사소통으로 프로그램을 전개한다. 따라서, 객체 내부의 상태를 그대로 공개하는 getter는 객체지향 프로그래밍에서 지양한다.
그런데, 정말 getter는 무조건 피해야하는 걸까?
이번 미션에서는 자동차가 이동했다면, 실제로 이동했는지 확인해야 할 필요가 있었다. 하지만, 자동차의 위치를 getter로 가져오지 않으면서 이를 확인하고 싶었다. 이를 위해 나는 equals() 를 재정의 하였고, 다음과 같은 테스트 코드를 작성했다.
@Test
@DisplayName("정지 시 이동 거리가 증가하지 않는지 확인")
void stop() {
Car test = new Car(0, "test");
test.stop();
Assertions.assertThat(test)
.isEqualTo(new Car(0, "test"));
}
Java
복사
그리고 이에 대해 다음과 같은 리뷰를 받았다.
여러 경로를 통해 얻은 getter를 지양해야 하는 이유는 크게 두가지로 정리할 수 있었다.
1.
객체의 내부 상태를 노출한다. ⇒ 다른 객체의 내부 상태에 의존하는 코드를 작성할 수 있다.
2.
객체를 이상한 상태로 만들 가능성이 있다. ⇒ 참조형 데이터의 특성상 객체의 내부 상태를 외부에서 변경하게 될 수 있다.
그렇다면 이 두 이유를 부정하거나 약하게 만들 수 있다면 사용할 수 있지 않을까?
그렇게 내린 getter를 안전하게 사용할 수 있는 조건을 정리해 2단계 미션에 반영했다.
그리고 이 과정에서 기존의 객체에 메세지를 던지는 방식으로 도메인 로직을 구현한 메서드 중 하나인 Car.hasSameDistance(Car other) 메서드를 getter를 사용한 코드로 변경했다. 그랬더니 다음과 같은 리뷰가 달렸다.
Getter는 근본적으로 다른 메서드와 다른가?
이번 자동차 미션에서도 일반적으로는 Car를 구현할 때 내부에 그 위치를 저장하는 position 과 같은 필드(상태)를 선언하고 움직일 때 마다 이를 증가하는 방식으로 구현했을 것이다. 하지만, 그게 유일한 방법은 아니다.
class Car {
private List<Boolean> moveLog = new ArrayList<>();
public void move(int power) {
if(power > 4) {
moveLog.add(true);
return;
}
moveLog.add(false);
}
}
Java
복사
이런 식으로 move 메서드를 호출할 때 마다 각 호출에서 실제로 어떤 개념(전진 혹은 그대로)이 수행되어야 하는지 기록해 둔 뒤
class Car {
private List<Boolean> moveLog = new ArrayList<>();
// 생략
public int getPosition() {
return (int)moveLog.stream()
.filter(value -> value == true)
.count();
}
}
Java
복사
이런 식으로 getPosition 메서드를 호출 할 때 마다 이를 통해 값을 계산해 반환할 수도 있다. 이러면 getPosition은 단순한 getter가 아니게 된다.
get~ 메서드여도 메세지를 던진 것이 될 수 있다. 그 객체가 알고 있을 것이라고 생각하는 어떤 것을 알려달라는 메세지인 것.
결론
getter가 문제가 아니라 객체에 적절한 메세지를 던져서 해결할 수 있는 것을 모두 “니가 알고 있는 걸 나한테 줘!” 라는 지나치게 포괄적인 메세지를 전달하는 것이 문제다.