Programming/Clean Architecture

7장. SRP: 단일 책임 원칙

JKROH 2023. 11. 13. 15:56
반응형
  • SRP에 대한 가장 흔한 오해는 하나의 모듈은 하나의 일만 해야한다는 것이다.
  • 그러나 SRP의 정확한 의미는 '단일 모듈은 변경의 이유가 오직 하나뿐이어야 한다'는 것이다.
  • 여기서 말하는 변경의 이유는 사용자와 이해관계자를 가리킨다. 다시 사용자와 이해관계자는 해당 변경을 요청하는 집단을 의미한다.
  • 결국 SRP의 의미는 '하나의 모듈은 오직 하나의 액터에 대해서만 책임져야 한다'는 것이다.
  • 이 때 모듈은 단순히 함수와 데이터 구조로 구성된 응집된 집합이다. 이는 클래스가 될 수도, 패키지가 될 수도, 정말 하나의 모듈이 될 수도 있다.
  • '응집된' 이라는 표현이 SRP를 암시한다. 단일 액터를 책임지는 코드를 하나로 묶어주는 힘이 바로 응집성이다.

SRP를 위반하는 징후 1 : 우발적 중복

  • 영화의 스태프 클래스를 아래와 같이 만든다고 생각해보자.
public class Staff {

    public void setLight() {

    }

    public void setSoundEquipment() {

    }

    public void castActor() {

    }
}
  • 스태프는 조명도 설치해야하고, 음향 장비도 설치해야하며, 배우 섭외도 해야한다. 대충 보면 추상화가 잘 됐다고 생각하고 넘길 수도 있다.(물론 절대 그럴리 없지만)
  • 그러나 위의 세 작업은 서로 다른 세 액터를 책임지는 전혀 다른 기능들이다.
    • setLight()은 조명팀에서 기능을 정의하고 사용한다.
    • setSoundEquipment()는 음향팀에서 기능을 정의하고 사용한다.
    • castActor()는 캐스팅팀에서 기능을 정의하고 사용한다.
  • 이 세 메서드를 하나의 Staff라는 클래스에 배치함으로써 세 액터가 서로 결합되었다. 이러한 결합으로 인해 한 팀에서 결정한 조치가 다른 팀에 영향을 미칠 수 있다.
    • 예를 들어, 기존에는 음향팀과 조명팀에서 한 장비 대여 업체를 이용해서 해당 업체에 연락하는 과정을 contactRentalCompany(); 라는 메서드로 뽑았다고 생각해보자.
    • 그런데 촬영 과정에서 조명팀은 대여 업체를 변경해서 contactRentalCompany() 메서드를 수정해달라고 개발자에게 요청한다.
    • 개발자는 요청 사항을 받아들여 기능을 수정하고 테스트하여 배포한다. 이제 조명팀은 원하는 결과를 얻게 되었다. 그럼 음향팀은?
  • 이러한 문제는 서로 다른 액터가 의존하는 코드를 너무 가까이 배치했기 때문에 발생한다. SRP는 서로 다른 액터가 의존하는 코드를 서로 분리하라고 말한다.

SRP를 위반하는 징후 2 : 병합

  • 소스 파일에 여러 기능이 담겨있으면 병합이 자주 발생할 수 있다. 특히 이들 메서드가 서로 다른 액터를 책임진다면 병합의 가능성은 더욱 높아진다.
    • 여기서 말하는 병합은 branch merge가 아니라 하나의 모듈 단위 내에서의 병합을 의미한다.
    • 한 클래스를 여러 브랜치에서 건드리고 merge할 때를 생각해보면 이해가 쉽다.
  • 우리는 병합 과정에서 발생할 수 있는 위험을 여러 도구들로 해결할 수 있지만, 모든 경우를 해결할 순 없다. 결국 병합에는 항상 위험이 뒤따르게 된다.
  • 다른 징후들도 있겠지만 이들은 모두 많은 사람이 서로 다른 목적으로 동일한 소스 파일을 변경하는 경우에 해당한다.

해결책

  • 그럼 이러한 문제를 해결하기 위해서는 어떻게 해야할까? 쉽고 단순한다. 서로 다른 액터를 뒷받침하는 코드를 서로 분리해야 한다.
  • 가장 확실한 해결책은 데이터와 메서드를 분리하는 방식일 것이다.
    • StaffData클래스를 만들어 테이블 정보만 담아 사용한다.
    • 모든 기능은 각 기능을 담당하는 객체를 만들어 자신이 담당하는 기능에 필요한 소스코드만을 포함한다.
      • LightSetter#setLight()
      • SoundEquipmentSetter#setSoundEquipment()
      • Caster#castingActor()
    • 이 방법은 개발자가 위의 세 가지 클래스를 인스턴스화 해 추적해야 한다는 단점이 있다.
  • 퍼사드 패턴을 활용할 수도 있다.
    • StaffFacade는 위의 세 클래스의 객체를 생성하고, 요청된 메서드를 가지는 객체로 위임하기만 한다.
  • 가장 중요한 메서드는 기존의 Staff클래스에 그대로 유지하되, Staff클래스를 덜 중요한 나머지 메서드들에 대한 퍼사드로 사용할 수도 있다.
반응형