/ CS

[클린 소프트웨어] 단일책임원칙(SRP)

[클린 소프트웨어] 단일책임원칙(SRP)

클린 소프트웨어 책의 단일책임원칙 파트를 읽고 정리한 내용입니다.

이번 장에서는 모듈이나 클래스의 변경을 야기하는 응집력에 대해서 언급하려 한다.

단일 책임 원칙

한 클래스는 단 한 가지의 변경 이유만을 가져야 한다.

보통 단일 책임 원칙에 대해서 검색해보면 하나의 클래스는 하나의 책임만을 가져야 한다고 설명하고 있습니다. 하지만, 이 책에서는 다르게 설명하고 있습니다. 한 클래스는 단 한 가지의 변경 이유만을 가져야 한다는 것입니다.

이는 SOLID 원칙이 소프트웨어 설계 단에서 발생하는 문제를 해결하는 데 있습니다. SOLID 원칙을 지키는 것에만 집중하면서 생기는 변화를 잊지 말아야 합니다. 앞서 배운 대로 애자일 설계는 문제를 해결하는 과정을 의미합니다. 단일 책임 원칙 역시 소프트웨어 변경에 있어서 발생하는 문제를 해결하는 데 사용하는 원칙입니다. 클래스는 하나의 책임을 가져야 한다는 원칙보다는 한 가지의 변경 이유만을 가져야 한다는 목적을 명확하게 이해해야 합니다.

예를 들어, 게임을 만들 때 현재 프레임을 기억하는 것과 스코어를 계산하는 두 가지 책임이 있을 때, 소프트웨어를 설계할 때 2개의 클래스로 분리할 것입니다. 이는 하나의 클래스가 하나의 책임만 가져야 하기 때문이 아니라 변경점이 생겼을 때 하나의 클래스를 변경할 때 그 클래스가 가진 하나의 책임에 의해서만 변경되어야 하기 때문에 미리 유지보수를 고려하여 2개의 클래스로 분리한 것입니다.

책임이란 무엇인가?

그렇다면 책임이란 무엇일까요? 만약 한 클래스를 변경하기 위해 한 가지 이상의 이유가 있다면, 그 클래스는 한 가지 이상의 책임을 맡고 있다는 것입니다. 그런데 사람들은 여러 책임을 묶어서 하나의 기능으로 생각하기 때문에 클래스를 분리하기 어렵습니다.

만약 클래스를 과도하게 쪼개면 불필요한 복잡성을 가지게 되고, 단순히 기능 단위로 묶어두면 변경이 힘들어지는 경직성의 악취를 풍기게 됩니다.

대부분의 경우 테스트 주도 개발 방식으로 개발하면 설계 단계에서 악취가 나기 한참 전에 테스트 코드를 작성하는 과정에서 책임이 분리되도록 만들 수 있습니다. 그러나 테스트 코드가 분리를 강제하지 않았고, 경직성과 취약성의 악취가 강해진 경우에는 퍼사드나 프록시 패턴을 사용해 두 책임이 분리되도록 리팩토링해야 합니다.

단일 책임 원칙은 가장 간단한 원칙 중 하나이면서도 제대로 적용하기 가장 어려운 원칙 중 하나입니다. 책임들을 구분해 분리하기는 익숙하지 않고, 우리가 실제로 소프트웨어를 설계할 때 이런 책임들을 하나 하나 찾고 분리하는 것이 대부분의 일입니다.

추가로 단일책임원칙과는 상관 없지만, 클린 소프트웨어에서 결합된 책임을 분리하기 위해서 퍼사드 패턴과 프록시 패턴을 사용 할 수 있다고 언급하고 있어 해당 패턴에 대해서 간단하게 정리하려 합니다.

퍼사드 패턴

퍼사드 패턴은 복잡하고 일반적인 인터페이스를 가진 객체 그룹에 간단하고 구체적인 인터페이스를 제공하고자 할 때 사용합니다. 예를 들어, 어떤 함수나 클래스가 어떤 동작인지 이해하기 어려운 매우 복잡한 동작이나 다양하고 많은 인터페이스를 가지고 있을 때, 새로운 인터페이스를 만들어 이해하기 쉽게 만들 수 있습니다.

소프트웨어가 점차 고도화되면서 인터페이스가 다양해지고 복잡해질 수 있습니다. 만약 하나의 클래스가 다수의 책임을 가지게 된다면, 해당 클래스를 한 가지 책임만을 가진 다수의 클래스로 리팩토링하고 해당 클래스를 모아서 사용하는 퍼사드를 만들어 인터페이스를 제공할 수 있습니다.

덕분에 복잡한 기능이라도 다수의 부담 없이 다수의 클래스로 분리하고 사용하는 입장에서 퍼사드를 통해 간단하게 다수의 클래스가 결합된 하나의 인터페이스를 사용할 수 있습니다.

프록시 패턴

프록시는 사전적인 의미로 대리인을 뜻한다. 클라이언트가 원본 객체에 접근하기 전 프록시를 통해 로직을 수행 한 후 원본 객체에 접근하는 형태를 취하고 있다.

다음과 같은 이유가 있을 때 일반적으로 프록시 패턴을 사용한다.

  • 보안: 클라이언트가 원본 객체에 접근할 권한이 있는 지 체크 후 권한이 있는 경우에만 전달한다.
  • 캐싱: 프록시가 내부 데이터를 캐싱하고 캐싱 데이터가 없는 경우에만 원본 데이터에 접근한다.
  • 데이터 유효성 검사: 원본 객체에 전달하는 데이터가 원본 객체가 다룰 수 있는 범위의 데이터인지 사전에 검증한다.
  • 지연 초기화: 만약, 리소스가 부족하다면, 여유가 있을 때 까지 대기할 수 있다.
  • 로깅: 원본 객체와의 인터렉션을 인터셉트해 기록한다.
  • 원격 객체: 원격에 있는 객체를 로컬에 있는 객체처럼 보이도록 할 수 있다.

클린 소프트웨어에서는 프록시는 사용하기 까다롭다고 언급하고 있다. 하지만 그 모든 까다로운 특성에도 불구하고 매우 강력한 이점이 한 가지 있는데, 바로 관심사의 분리이다. 프록시의 기능과 원본 객체의 기능을 분리해 생각 할 수 있다.

원본 객체는 자신의 책임만 집중하고 다른 책임을 프록시에 위임할 수 있다.

하지만, 클린 소프트웨어에서는 시간을 아끼기 위해 일단 파사드 패턴으로 시작하고 필요하면 리팩토링 할 것을 권하고 있다.

참고

퍼사드 패턴: https://inpa.tistory.com/entry/GOF-💠-퍼사드Facade-패턴-제대로-배워보자

프록시 패턴: https://inpa.tistory.com/entry/GOF-💠-프록시Proxy-패턴-제대로-배워보자