Written by
Poogle
on
on
객체지향 생활 체조 원칙 - 규칙2, 3, 8, 9
이 글은 NEXTSTEP의 TDD, Clean Code with Java 수업을 수강하며 내용을 정리한 포스트 입니다.
객체지향 생활 체조 원칙
객체지향 생활 체조 원칙은 소트웍스 앤솔러지 책에서 다루고 있는 내용으로 객체지향 프로그래밍을 잘 하기 위한 9가지 원칙을 제시하고 있다. 이 책에서 주장하는 9가지 원칙은 다음과 같다.
- 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.
- 규칙 2: else 예약어를 쓰지 않는다.
- 규칙 3: 모든 원시값과 문자열을 포장한다.
- 규칙 4: 한 줄에 점을 하나만 찍는다.
- 규칙 5: 줄여쓰지 않는다(축약 금지).
- 규칙 6: 모든 엔티티를 작게 유지한다.
- 규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
- 규칙 8: 일급 콜렉션을 쓴다.
- 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.
규칙 2: else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
public int add(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
return Integer.parseInt(text);
}
- if문 내부에서 조기 반환(early return) -> 간결해짐
- if문 대신 Enum을 활용 -> 객체지향적
규칙 3: 모든 원시값과 문자열을 포장한다.
Ex. 인스턴스 변수 position을 포장하기
int position;
\\\
public class Position {
public static final int DEFAULT = 0;
private final int position;
public Position() {
this(DEFAULT);
}
public Position(int position) {
if (position < DEFAULT) {
throw new IllegalArgumentException(POSITION_VALUE_ERROR);
}
this.position = position;
}
public int position() {
return position;
}
public Position increase() {
return new Position(this.position + 1);
}
public Position max(Position other) {
if (other.position > position) {
return other;
}
return this;
}
\\\
}
- 포장한 객체를 만들다보면 값에 대한 유효범위도 객체가 책임을 지니게 되니까 버그가 발생할 확률 감소
- 로직이 객체로 이동, 상태를 객체 스스로 관리
- 불변 객체: 한 번 만들어지고 나면 외부에 의해서 값이 변경이 될 수 없음 -> 안전해짐
- 단점: 새로운 인스턴스가 많이 발생해서 Garbage Collection이 할 일이 많아짐. 그러나 이런 성능 이슈는 문제가 될 때 해결하는 편이 더 낫다.
규칙 8: 일급 콜렉션을 쓴다.
콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 각 콜렉션은 그 자체로 포장돼 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된셈이다. 필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 객체가 될 수 있다. 또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다. 콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다.
- Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태
Ex. 로또 게임
- 6개 숫자로만 이루어지고
- 중복되지 않는 자료구조
public class LottoBalls { private static final int LOTTO_SIZE = 6; private final Set<LottoBall> lottoBalls; public LottoBalls(List<LottoBall> lottoBalls) { this.lottoBalls = new HashSet<>(lottoBalls); } public static LottoBalls createLottoBalls(Set<Integer> lottoBallList) { validate(lottoBallList); return new LottoBalls(lottoBallList.stream() .map(LottoBall::valueOf) .collect(Collectors.toList())); } private static void validate(Set<Integer> lottoBallList) { if (lottoBallList.size() != LOTTO_SIZE) { throw new IllegalArgumentException(SIZE_MESSAGE); } }
- 비즈니스에 종속적인 자료구조
- 컬렉션의 불변 보장 (참고: https://woowacourse.github.io/tecoble/post/2020-05-08-First-Class-Collection/)
- 자바에서는 final로도 불변이 보장되지 않음
- 컬렉션 특성상 Getter를 그대로 사용하면 레퍼런스 관계가 되어 불변이 보장되지 않음(-> 그렇지 않으려면? 필요한 값만 반환하는 별도의 메소드를 만들어서 멤버변수에 저장되는 주소값을 재할당하거나
unmodifiableList()
를 사용, 외부에서 컬렉션 내부 필드에 단독 접근을 못하도록)
- 상태와 행위를 한 곳에서 관리
- 이름이 있는 컬렉션
규칙 9: getter/setter/property를 쓰지 않는다.
- 값을 꺼내서 비교하려고 하지 말고 객체에 메세지를 보내기
- ex. position get -> 값 바꾸고 -> set으로 저장하지말고 position 객체가 직접 move하도록
엘레강트 오브젝트 - 3.5 절대 getter와 setter를 사용하지 마세요
printf("Cash value is %d", cash.dollars) //자료구조
printf("Cash value is %d", cash.print()) //객체
- 객체와 자료구조의 차이점(자료구조가 OOP에서 해로운 이유)
- 자료구조: 멤버인 dollars에 직접 접근한 후 해당 값을 정수로 취급. 자료구조와는 아무런 의사소통도 하지 않고 멤버에 직접 접근
- 객체: 어떤 식으로든 멤버에게 접근하는 것을 허용하지 않음. 자신의 멤버 노출하지 않음. 클래스 안에 dollars라는 멤버가 있는지조차 알 수 없음. 객체에게 자기 자신을
print()
하라고 요청할 뿐. -> 캡슐화
- 유지보수 -> 모든 프로그래밍 스타일의 핵심 목표는 가시성의 범위를 축소해서 사물을 단순화시키는 것, 특정 시점에 이해해야 하는 범위가 작을수록, 소프트웨어의 유지보수성이 향상되고 이해하고 수정하기도 쉬워짐
- getter/setter 안티 패턴에서 유해한 부분: 접두사 get, set ```java ///이름을 get으로 짓지 말기 class Cash { private final int value; public int getDollars() { return this.value; } }
//이렇게 짓기 class Cash { private final int value; public int dollars() { return this.value; } } ```
getDollars()
: 데이터 중에 dollars를 찾은 후 반환하세요 (X)dollars()
: 얼마나 많은 달러가 필요한가요? (O) -> 데이터를 노출하지 않고, 객체를 데이터의 저장소로 취급하지 않고 존중함참고
Q. 테스트에서 검증하기 위한 용도로 getter를 정의하는 것이 맞는지?
- -> 테스트를 검증하기 위해서 getter를 추가하는 것은, 테스트 코드를 위해서 비즈니스 로직에 메소드를 추가하는 것과 마찬가지
- Getter 없이 테스트하는 방법
- 리플렉션을 사용한다. (객체지향적인지는 의문)
- 객체와 객체간의 동등성, 논리적 동치성 비교를 한다. (equals와 hashcode 재정의 필요)
- 객체의 메소드가 결과값은 반환하도록 한다.