[tdd] 객체지향 스타일

우리는 작성하기 쉬운 코드보다는 유지보수하기 쉬운 코드를 높이 평가한다
가장 직접적인 방식으로 기능을 구현하면 시스템의 유지보수성이 떨어질 수 있고, 그러면 코드를 이해하기 어려워지고 컴포넌트간에 보이지 않는 의존성이 생기게 된다
하지만 알다시피 당면한 관심사와 장기적인 관심사간의 균형을 유지하는 일은 까다로운 문제이다

유지 보수성을 고려한 설계

  • 관심사의 분리

    • 관련된 변경사항이 전부 코드의 어느 한군데에 들어있다면, 뭔가 변경할 때 코드의 여러곳을 변경하지 않아도 된다
      • 하나의 클래스가 여러 관심사를 가지고 있다면, 다른 클래스에서도 해당 관심사들을 가지고 있을 확률이 매우 높다
      • 이는 각각 특정 관심사로 분리되어야 하고, 분리된 관심사들이 여러곳에서 재사용 되어야 한다
      • 그러면 특정 변경사항을 적용하기 위해 코드의 여러곳을 수정하는 상황을 막을 수 있다
    • 우리는 변경사항(요구사항)을 예측할 수 없으므로, 같은 관심사들을 하나의 클래스로 모으는 연습을 계속해서 해야한다
    • e.g. 인터넷 표준 프로토콜로 전달된 메시지를 푸는 코드는 그 메시지를 해석하는 코드와 똑같은 이유로 변경되어서는 안되므로, 두 개념을 각기 다른 패키지로 나워야 한다
  • 더 높은 수준의 추상화

    • 흐름을 직접 제어하기보다는 클래스들을 조합하는 식으로 프로그램을 작성하면 더 많은 일을 해낼 수 있다
    • 이는 사람들이 식당에서 음식을 주문할 때 세세한 조리법을 설명하는 것이 아니라 메뉴를 보고 음식을 주문하는 것과 같다
  • ports & adapter pattern
    http://getoutsidedoor.com/2018/09/03/ports-adapters-architecture/
    https://dzone.com/articles/hexagonal-architecture-for-java
    기술적인 부분을 인터페이스로 분리해서 변경을 용이하게 한다 정도로만 이해했고, 실제 예제를 보지 못해서 정확히 이해하지 못하겠다

캡슐화와 정보은닉

캡슐화와 정보은닉은 비슷해보이지만 설계의 품질을 나타낼때는 별개의 개념이다

  • 캡술화

    • https://javacan.tistory.com/entry/EncapsulationExcerprtFromJavaBook
    • 해당 객체의 API를 통해서만 객체의 행위를 좌우할 수 있다
    • 외부에서 객체의 상태를 직접 접근해 변경하거나, 무언가 행위를 하는것이 불가능하다는 의미이다
    • 예상치 못한 의존성이 없음을 보장해줌으로써, 한 객체에 대한 변경이 시스템의 다른 부분에 영향을 덜 주게끔 통제할 수 있다
    • 잘못 캡슐화된 코드를 활용할 경우 해당 코드를 어디서 참조하는지 찾아보고, 잠재적 영향력을 추적하는데 굉장히 많은 시간을 보내게 된다
  • 정보 은닉

    • 해당 객체의 기능을 구현하는 방법을 추상화된 API 너머로 감춘다
    • e.g. 정렬이라는 메서드가 있으면, 내부에서 어떻게 정렬을 수행하는지 외부에서는 알 필요가 없게끔 한다

하지만 대부분의 객체지향 언어에서 제공하는 별칭(aliasing)을 통해 캡슐화를 위반할 수 있다
관련이 없는 두 클래스를 묶거나, 특정 클래스를 wraping한 클래스를 만들고, 그 클래스를 통해 자식클래스에 행위들을 수행해버릴 수 있다

1
2
3
4
5
6
7
class A {
private B b;

public void doManything(B b) { // aliasing
b.doSomething(); // 캡슐화 위반
}
}

A를 통해서 B를 수정할 수 있게 되므로, 이는 캡술화를 위반한 것이다
위와 같이 doManything를 호출 한 후 b의 값이 변경되어 버리는 현상이 일어나서는 안된다

이러한 상황들을 방지하기 위해 아래와 같은 관례를 따라야 한다고 말한다

  • 변경 불가능한 값 타입을 정의하고
    • 값 타입은 내부 값이 수정 불가능한 final이고, 값에 특정한 행위를 수행해도 새로운 객체로 반환하기 때문에 위와 같은 상황에서 안전하다
  • 전역변수와 싱글턴은 자제하며
  • 컬렉션과 변경 가능한 값을 객체간에 전달할때는 그것을 복사해야한다
    • 객체를 복사한다면 위와 같은 상황에 안전할 것이다
    • 저런식으로 협력객체의 특정

이 같은 결정이 중요한 이유는 어떤 객체를 얼마나 쉽게 쓸수 있는지에 영향을 주고, 시스템 내부 품질에 기여하기 때문이다

단일 책임 원칙

  • 모든 객체는 반드시 단 한가지 명확히 규정된 책임을 지녀야 한다
    • 그리고 그러한 객체들이 재사용되어야 한다
  • 한 객체의 역할을 설명할때는 접속사(와,나 등)를 사용하지 않고도 해당 객체의 역할을 설명할 수 있어야 한다
    • 접속사로 구분되는 애들이 각각 객체로 나뉠 수 있을것이다?

객체 이웃의 유형

객체가 단일 책임을 지녔고, 명료한 API를 통해 이웃 객체와 통신한다면, 서로 무슨 얘끼를 할까?
한 객체가 지닐 수 있는 관계는 다음과 같다