[클린 아키텍처] 7. SRP: 단일 책임 원칙

햄도·2021년 8월 24일
0

Clean Architecture

목록 보기
7/11

출처

클린 아키텍처를 읽으며 정리한 내용입니다.

3부 소개

SOLID 원칙은 함수와 데이터 구조를 클래스로 배치하고, 이들을 서로 결합하는 방법을 설명해준다. 단, 여기에서의 클래스는 함수와 데이터를 결합한 집합으로, SOLID가 객체 지향에만 적용되는 원칙은 아니다.
SOLID 원칙의 목적은 중간 수준의 소프트웨어 구조가 아래와 같도록 만드는 데에 있다.

  • 변경에 유연하다.
  • 이해하기 쉽다
  • 많은 소프트웨어 시스템에서 사용될 수 있는 컴포넌트의 기반이 된다.

여기에서 중간 수준이란 모듈 수준, 즉 코드 수준보다는 조금 상위를 의미한다. SOLID는 모듈과 컴포넌트 내부에서 사용되는 소프트웨어 구조를 정의하는 데에 도움을 준다.
SOLID 원칙은 아래와 같다.

  • SRP: 단일 책임 원칙
    • 각 소프트웨어 모듈은 변경의 이유가 단 하나여야만 한다.
  • OCP: 개방 폐쇄 원칙
    • 기존 코드를 수정하기보다는 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야 한다.
  • LSP: 리스코프 치환 원칙
    • 상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이 구성요소는 반드시 서로 치환 가능해야 한다.
  • ISP: 인터페이스 분리 원칙
    • 소프트웨어 설계자는 사용하지 않은 것에 의존하지 않아야 한다.
  • DIP: 의존성 역전 원칙
    • 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대 의존해서는 안된다.

7. SRP: 단일 책임 원칙

단일 책임 원칙의 의미는 모든 모듈이 단 하나의 일만 해야 한다는 것이 아니지만, 개발자들은 이 원칙을 '함수는 단 하나의 일만 해야 한다'는 원칙과 헷갈리곤 한다.

이 원칙의 진정한 의미는 '하나의 모듈은 하나의 액터에 대해서만 책임져야 한다'는 것이다.
여기에서 액터란 변경을 요청하는 집단을 의미하며, 모듈은 함수와 데이터 구조로 구성된 응집된 집합(보통은 소스 파일)을 의미한다.

이 원칙을 이해하기 위해 위반 징후를 먼저 살펴보자.

징후 1: 우발적 중복

SRP를 위반하는 클래스의 사례로 다음과 같이 서로 매우 다른 세 명의 액터를 책임지는 Employee 클래스를 정의해볼 수 있다.

  • calculatePay() 메서드 - 회계팀에서 CFO 보고를 위해 사용
  • reportHours() 메서드 - 인사팀에서 COO 보고를 위해 사용
  • save() 메서드 - DBA가 CTO 보고를 위해 사용

이 결합으로 인해 CFO 팀에서 결정한 조치는 COO 팀이 의존하는 무언가에 영향을 줄 수 있다.

만약 calculatePay()reportHours() 가 정규 업무 시간을 계산하는 알고리즘을 공유하며, 이 알고리즘을 regularHours()라는 메서드 하나로 공유한다면 어떻게 될까?

CFO에서 정규 업무 시간을 계산하는 방식을 수정 요청하고, 수정하는 개발자가 regularHours()가 양쪽에서 호출한다는 사실을 눈치채지 못한다면 CFO 팀에서의 수정이 COO 팀의 로직에 영향을 주게 된다.

불행히도 우리는 모두 이런 상황을 목격한 경험이 있다..
SRP는 이러한 상황을 방지하기 위해 서로 다른 액터가 의존하는 코드를 분리하라고 말한다.

징후 2: 병합

소스파일에 다양하고 많은 메서드를 포함할수록, 그리고 이 메서드가 서로 다른 액터를 책임질수록 병합이 발생할 가능성이 높다.

CTO 팀에서 데이터베이스의 Employee 테이블 스키마를 수정하는 동시에 COO 팀에서 reportHours() 메서드의 보고서 포맷을 변경하기로 결정한다면 어떻게 될까?

아마 서로 다른 팀에 속한 개발자가 각자 변경사항을 적용하고, 이 변경사항은 충돌할 것이다. 병합에는 당연히 위험이 뒤따른다.

이 문제에서 벗어나는 방법은, 앞서 말했듯이 서로 다른 액터를 뒷받침하는 코드를 분리하는 것이다.

해결책

이러한 문제들의 해결책은 다양한데, 모두가 메서드를 각기 다른 클래스로 이동시키는 방식이다.
아마도 가장 확실한 해결책은 데이터와 메서드를 분리하는 방식일 것이다.

예를 들어, 아무 메서드가 없는 EmployeeData 클래스를 만들어 세 클래스가 공유하도록 하고, 각 클래스는 자신의 메서드에 반드시 필요한 소스 코드만을 포함하며 서로의 존재를 모르도록 한다면 우연한 중복을 피할 수 있다. 하지만 이 방법은 개발자가 세가지 클래스를 인스턴스화하고 추적해야 한다.

대안으로는 파사드 패턴이 있다.
EmployeeFacade에 코드는 거의 없고, 이 클래스는 세 클래스의 객체를 생성하고 요청된 메서드를 가지는 객체로 위임하는 일을 책임진다.

가장 중요한 업무 규칙을 데이터와 가깝게 배치하는 방식을 선호한다면, 가장 중요한 메서드는 기존의 Employee 클래스에 그대로 유지하되, Employee 클래스를 덜 중요한 나머지 메서드들에 대한 파사드로 사용할 수도 있다.

결론

단일 책임 원칙은 기본적으로 메서드와 클래스 수준의 원칙이다.
하지만 같은 원칙이 컴포넌트 수준에서는 공통 폐쇄 원칙으로, 아키텍처 수준에서는 변경의 축으로 다시 등장할 것이니 기대하자.

profile
developer hamdoe

0개의 댓글