7장. SRP, 단일 책임 원칙

2023. 5. 21. 09:04DDD/CleanArchitecture

소개

클린아키텍처: 소프트웨어 구조와 설계의 원칙 책을 읽고 정리하며 소감을 적는 포스트입니다.

SRP: 단일 책임 원칙

SOLID 원칙 중에서 그 의미가 가장 잘 전달되지 못한 원칙은 바로 단일 책임 원칙(SRP)이다.

프로그래머 입장에서는 모든 모듈이 단 하나의 일만 해야 한다는 의미로 받아들이기 쉽다.

헷갈리면 안된다. 함수가 단 하나의 일을 해야한다는 원칙을 가지고 있고 SRP의 의미는 아래와 같다

단일 모듈은 변경의 이유가 하나, 오직 하나뿐이어야 한다.

변경을 요청하는 한 명 이상의 사람들을 액터(Actor)라고 부른다면 아래와 같다.

하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다.

모듈이란 가장 단순한 정의는 바로 소스 파일이다. 모듈은 단순히 함수와 데이터 구조로 구성된 응집된 집합이다.

응집된(cohesive)이라는 단어가 SRP를 암시한다. 단일 액터를 책임지는 코드를 함께 묶어 주는 힘이 바로 응집성이다.

징후 1: 우발적 중복

Employee 클래스

위 클래스는 세 가지 메서드 calculatePay() reportHours(), save()를 가진다.

이 클래스는 SRP를 위반하는데, 이들 세 가지 메서드가 서로 매우 다른 세명의 액터를 책임지기 때문이다.

  • calculatePay() 메서드는 회계팀에서 기능을 정의하며, CFO 보고를 위해 사용한다.
  • reportHours() 메서드는 인사팀에서 기능을 정의하고 사용하며, COO 보고를 위해 사용한다.
  • save() 메서드는 데이터베이스 관리자(DBA)가 기능을 정의하고, CTO 보고를 위해 사용한다.

세 액터가 서로 결합되어 버려 CFO 팀에서 결정한 조치가 COO팀이 의존하는 무언가에 영향을 줄 수 있다.

예를 들어 calculatePay() 메서드와 reportHours() 메서드가 초과 근무를 제외한 업무 시간을 계산하는 알고리즘을 공유한다고 해보자.

그리고 개발자는 코드 중복을 피하기 위해 이 알고리즘을 regularHours()라는 메서드에 넣었다고 해보자

공유된 알고리즘

이제 CFO팀에서 초과 근무를 제외한 업무 시간을 계산하는 방식을 약간 수정하기로 결정했다고 하자. 반면 인사를 담당하는 COO팀에서는 초과 근무를 제외한 업무 시간을 CFO 팀과는 다른 목적으로 사용하기 때문에, 이 같은 변경을 원하지 않는다고 해보자.

이 변경을 적용하는 업무를 할당받은 개발자는 calculatePay() 메서드가 펴느이 메서드인 regularHours()를 호출한다는 사실을 발견한다. 하지만 안타깝게도 이 함수가 reportHours() 메서드에서도 호출된다는 사실을 눈치채지 못한다.

징후 2 : 병합

소스 파일에 다양하고 많은 메서드를 포함하면 병합이 자주 발생하고 이들 메서드가 서로 다른 액터를 책임진다면 병합이 발생할 가능성이 확실히 더 높다.

두 명의 서로 다른 개발자가, 그리고 아마도 서로 다른 팀에 속했을 두 개발자가 Employee 클래스를 체크아웃 받은 후 변경사항을 적용하기 시작하면 안타깝게도 이들 변경 사항이 서로 충돌한다. 결과적으로 병합이 발생한다.

병합에는 위험이 따른다고 굳이 말하지 않아도 될 것이다. 어떤 도구도 병합이 발생하는 모든 경우를 해결할 수는 없다.

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

해결책

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

즉, 아무런 메서드가 없는 간단한 데이터 구조인 EmployeeData 클래스를 만들어, 세 개의 클래스가 공유하도록 한다.

각 클래스는 자신의 메서드에 반드시 필요한 소스 코드만 포함한다. 세 클래스는 서로의 존재를 몰라야 한다. 따라서 우연한 중복을 피할 수 있다.

세 클래스는 서로의 존재를 알지 못한다.

반면 이 해결책은 개발자가 세 가지 클래스를 인스턴스화하고 추적해야 한다는 게 단점이다. 이러한 난관에서 빠져나올 때 흔히 쓰는 기법으로 퍼사드(Facade) 패턴이 있다.

퍼사드(Facade) 패턴

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

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

가장 중요한 메서드는 기존의 Employee 클래스에 그대로 유지하되, Employee 클래스를 덜 중요한 나머지 메서드들에 대한 퍼사드로 사용한다.

이 처럼 여러 메서드가 하나의 가족을 이루고, 메서드의 가족을 포함하는 각 클래스는 하나의 유효범위가 된다.

해당 유효범위 바깥에서는 이 가족에게 감춰진 식구(private 멤버)가 있는지를 전혀 알 수 없다.

결론

단일 책임 원칙은 메서드와 클래스 수준의 원칙이다. 하지만 이보다 상위 두 수준에서도 다른 형태로 다시 등장한다.

컴포넌트 수준에서는 공통 패쇄 원칙(Common Closure Principle)이 된다.

아키텍처 수준에서는 아키텍처 경계(Architectural Boundary)의 생성을 책임지는 변경의 축(Axis of Change)이 된다.

'DDD > CleanArchitecture' 카테고리의 다른 글

9장. LSP, 리스코프 치환 원칙  (1) 2023.05.22
8장. OCP, 개방-폐쇄 원칙  (0) 2023.05.21
6장. 함수형 프로그래밍  (0) 2023.05.20
5장. 객체 지향 프로그래밍  (0) 2023.05.20
4장. 구조적 프로그래밍  (1) 2023.05.19