← Posts

SOLID · SRP · OOP

Single Responsibility Principle

클래스가 바뀌는 이유는 하나여야 한다. '일'이 아니라 '이유'로 나누는 첫 번째 SOLID 원칙.

목차
  1. 한 클래스가 너무 많은 일을 한다
  2. 단일 책임 = 바뀌는 이유가 하나다
  3. ”일”이 아니라 “이유”다
  4. 고치다 옆이 깨진다
  5. 테스트가 딸려 온다
  6. 책임으로 쪼갠다
  7. 누가 바꾸라 하는가 - 액터
  8. 같은 이유는 모은다 - 응집
  9. 너무 쪼개면 파편화된다
  10. 실무: 비대한 서비스가 신호다
  11. 정리

한 클래스가 너무 많은 일을 한다

주문을 나타내는 Order 클래스가 이렇게 생겼다고 하자.

java
public class Order {
    private List<Item> items;

    public int total() {                    // ① 금액 계산 (할인·세금)
        // 비즈니스 규칙
    }

    public void save(Connection conn) {     // ② DB에 저장
        // SQL로 orders 테이블에 insert
    }

    public void sendConfirmationEmail() {   // ③ 확인 메일 발송
        // SMTP로 메일 전송
    }
}

한 클래스가 계산도 하고, 저장도 하고, 메일도 보낸다. 편해 보인다. “주문에 관한 것”은 다 여기 있으니까. 그런데 이게 문제의 씨앗이다.

단일 책임 = 바뀌는 이유가 하나다

단일 책임 원칙(SRP)은 흔히 “클래스는 하나의 일만 해야 한다”로 알려져 있다. 그런데 더 정확한 정의는 이것이다.

클래스가 바뀌어야 하는 이유는 하나여야 한다.

Order를 보자. 이 클래스는 세 가지 이유로 바뀐다.

  • 할인 규칙이 바뀌면 → total()을 고친다
  • DB 스키마가 바뀌면 → save()를 고친다
  • 메일 문구가 바뀌면 → sendConfirmationEmail()을 고친다

서로 아무 상관없는 세 가지 변경이 한 파일에 모여 있다.

”일”이 아니라 “이유”다

여기서 흔한 오해를 짚자. SRP를 “메서드를 하나만 두라”거나 “클래스를 무조건 잘게 쪼개라”로 읽으면 틀린다.

total()을 계산 단계별로 다섯 메서드로 나눠도 그건 SRP와 상관없다. 그 다섯은 다 같은 이유(비즈니스 규칙)로 바뀌니까. 반대로 메서드가 셋뿐이어도, 셋이 서로 다른 이유로 바뀌면 그게 위반이다.

기준은 메서드 개수가 아니라 **“바뀌는 이유”**다. 같은 이유로 바뀌는 것끼리는 한데 있어도 되고, 다른 이유로 바뀌는 것이 섞이면 그게 문제다.

고치다 옆이 깨진다

세 이유가 한 클래스에 있으면 무슨 일이 나나.

메일 문구를 바꾸려고 Order를 열었다가, 실수로 total()이나 save()를 건드릴 수 있다. 메일만 손보려던 사람이 계산 로직이 든 파일을 통째로 마주하는 것이다. 하나를 고치려 열었는데 관계없는 것들이 같이 노출되고, 같이 위험해진다.

diagramdiagram

테스트가 딸려 온다

계산이 맞는지만 확인하고 싶어도, total()save()·sendConfirmationEmail()과 한 클래스에 묶여 있으면 그 클래스를 세우는 순간 DB 연결과 메일 설정까지 따라온다. 순수하게 계산만 보려던 테스트가 인프라에 발이 묶이는 것이다.

바뀌는 이유가 하나인 클래스는 그 이유 하나만 세워 두고 테스트하면 된다. 여러 이유가 얽히면 테스트도 딱 그만큼 얽힌다.

책임으로 쪼갠다

고침은 단순하다. 이유별로 클래스를 나눈다.

java
public class Order {
    public int total() { /* 계산만 */ }
}

public class OrderRepository {
    public void save(Order order) { /* 저장만 */ }
}

public class OrderNotifier {
    public void sendConfirmation(Order order) { /* 메일만 */ }
}

이제 각 클래스는 바뀌는 이유가 하나다. 메일 문구가 바뀌면 OrderNotifier만 열고, OrderOrderRepository는 쳐다볼 일이 없다.

diagramdiagram

누가 바꾸라 하는가 - 액터

이 원칙을 더 날카롭게 보는 방법이 있다. “이유”를 사람으로 바꿔 생각하는 것이다. 변경을 요구하는 쪽을 액터라고 하면:

  • total()의 규칙은 회계·영업이 바꾼다
  • save()의 스키마는 인프라·DBA가 바꾼다
  • 메일 문구는 마케팅이 바꾼다

세 액터가 한 클래스를 공유하면, 한 액터의 요구가 다른 액터의 코드를 흔든다. 마케팅이 메일을 바꿨는데 회계 계산이 깨지는 식이다. 그래서 SRP는 **“한 클래스는 한 액터에게만 답한다”**로 읽으면 가장 정확하다.

같은 이유는 모은다 - 응집

SRP를 ‘쪼개는 원칙’으로만 읽으면 절반만 본 것이다. 나머지 절반은 모으는 것이다. 이 원칙은 다른 이유를 가르라고만 하지 않는다. 같은 이유로 바뀌는 것은 한데 모으라고도 한다.

할인 규칙과 관련된 로직이 여러 파일에 흩어져 있으면, 규칙 하나 바꾸는 데 이곳저곳을 돌아다녀야 한다. 같은 이유로 바뀌는 것을 한 곳에 모아 두는 성질을 응집이라 한다. 한 클래스 안의 것들이 다 같은 이유로 움직이면 응집이 높고, 제각각 다른 이유로 움직이면 응집이 낮다. SRP는 결국 응집을 높이는 원칙이다.

너무 쪼개면 파편화된다

그래서 반대 함정도 있다. SRP를 “무조건 작게”로 밀면 클래스가 파편화돼 오히려 나빠진다. 한 줄짜리 클래스가 수십 개가 되면, 관련된 걸 찾아 여기저기 헤매게 된다 - 응집이 도로 낮아지는 것이다.

기준은 늘 같다. 무작정 잘게 쪼개는 게 아니라, 변경의 축을 따라 나눈다. 다른 이유는 가르고, 같은 이유는 붙여 둔다.

실무: 비대한 서비스가 신호다

실무에서 SRP 위반은 대개 비대한 서비스 클래스로 나타난다. UserService 하나가 회원가입·인증·정산·통계·메일을 다 들고 500줄이 되는 식이다.

신호는 이렇다.

  • 클래스를 한 문장으로 설명하는데 “그리고”가 자꾸 들어간다 (사실은 여러 개다)
  • 한 메서드를 고쳤는데 관계없는 테스트가 깨진다
  • 서로 다른 팀이 같은 파일을 자주 만진다 (액터가 여럿이라는 증거)

이럴 때 “무엇이 이 클래스를 바꾸게 하는가”를 종이에 적어 보면, 자연스러운 분리선이 드러난다.

정리

SRP란클래스가 바뀌는 이유는 하나여야 한다
흔한 오해”일 하나”가 아니라 “이유 하나”. 메서드 개수 문제가 아니다
다른 이유가 섞이면, 하나를 고치다 관계없는 게 깨진다
액터로 보면한 클래스는 한 액터(변경을 요구하는 쪽)에게만 답한다
양면다른 이유는 가르고, 같은 이유는 모은다(응집). 무작정 쪼개기가 아니다
신호비대한 서비스, 이름의 “그리고”, 여러 팀이 만지는 파일

한 클래스에 여러 이유를 담지 않는 것. 그래야 하나를 바꿀 때 나머지가 안전하다. 그게 단일 책임이 지키는 것이다.

관련 글