지난 포스트에서 다루었 듯이 책임이란 "변경의 이유"이다. 하지만 단일 책임 원칙을 고려하면서 어떠한 애플리케이션을 구현하다 보면 책임에 대해 고민에 빠지게 된다. 변경의 이유는 절대적으로 정해져 있는 것이 아니기 때문이다.
책임 == 기능 ?
단일 책임 원칙, 혹은 SRP에 대해서 리서치를 하면 다양한 예시들을 접할 수 있다. 하지만 많은 예시들의 클래스에선 하나의 메서드만을 가지고 단일 책임 원칙을 설명하는 경우가 많다. 객체가 한 가지의 기능 만을 가졌을 때 단일 책임 원칙을 논하기는 매우 쉽기 때문이다. 그래서 그러한 예시들과 단일 책임 원칙에 대한 설명을 보다 보면 이해가 되는 듯 싶다.
하지만 실제로 단일 책임 원칙을 고려하며 설계를 하거나 구현을 하게 되면 무언가 헷갈리게 된다. 홀로 공부하며 흔히 접할 수 있는 예시들은 하나의 기능, 즉 하나의 메서드 만을 가지고 있었기 때문에 객체에 다른 기능을 추가하려니 책임의 경계를 더욱 고민하게 된다. 두 개 이상의 메서드가 있으니 마치 객체를 변경할 이유가 두 개가 되는 것 같은 느낌이 들었다.
감이 잘 잡히지 않아 홀로 구글링을 하며 단일 책임 원칙에 대해 심사숙고하다가 문득 들었던 생각이 있다.
"제일 바람직한 객체는 하나의 기능 만을 가진 객체가 아닐까?"
바보 같은 생각이라고 느껴질 수도 있었지만 이런 의문이 들었고 나 뿐만 아니라 많은 사람들이 실제로 헷갈려 하는 듯 싶다. 그럴 것이라 생각하지는 않았지만, 어쩌면 단일 책임 원칙을 제일 고수한 객체는 하나의 메서드 만을 가진 객체가 아닐까?
객체와 기능의 차이
많은 예시들이 하나의 메서드 만을 가지고 단일 책임 원칙을 설명하지만, 책임과 기능은 다르다. 기능은 행위이다. 그리고 객체는 기능을 행하는 주체이다.
개발 공부를 하다보면 도메인이란 말을 많이 접하게 된다. 도메인은 여러 상황에서 다른 의미로 다양하게 쓰이는 경우가 많다. 해결하고자 하는 문제의 범주를 도메인이라고 하기도 하고, 광범위하게 한 서비스가 다루고자 하는 영역을 도메인이라고 하기도 한다. 공통적으로 도메인은 범주, 혹은 범위를 나타낸다.
구글에 간단하게 도메인에 대한 사전적 정의를 찾아봤다. "area of territory", "specified sphere", "dicrete region", "distinct subset" 등등 주로 특정한 구역 혹은 범위를 나타낸다.
네이버 사전에선 아예 책임의 범위라고 나온다.
물론 사전적 정의가 절대적으로 모든 사람들에게 일맥상통한 것은 아니고, 쓰임새나 뉘앙스가 다르게 자리 잡은 단어일 것이다. 하지만, 객체를 일종의 도메인이라고 이해를 해도 괜찮지 않을까? 객체는 여러 기능을 행할 수 있지만 해당 기능들은 하나의 책임을 위해 존재한다. 즉, 객체라는 주체는 특정 책임의 범위를 가지고있다.
의사와 환자
환자가 아픈 곳이 있어 의사와 면담을 하는 상황을 가정해보자.
환자가 먼저 어디가 아파서 왔는지 말하고, 의사는 무엇이 문제일까 진찰하고 무엇이 문제인지 진단하고, 환자에게 맞는 약을 처방한다. 해당 상황을 프로그램한다 가정한다면 환자는 증상을 말하는 기능이 있고, 의사는 진찰, 진단, 그리고 처방이라는 세 가지 기능이 있다. 환자와 의사를 객체로 정의한다면 환자는 하나의 메서드만 있지만, 의사는 세 개의 메서드를 가지고 있다. 의사 도메인의 책임은 환자를 치료, 혹은 진료하는 것이고 의사 객체의 메서드들은 해당 책임의 범주에 포함된다.
책임의 변동
책임은 고정적인 값이 아니다. 책임의 범위는 넓어질 수도 있고, 축소될 수도 있다. 책임이 너무 많아지면 분리될 수도 있다. 그러나 이에 대한 절대적인 조건이 있는 것은 아니다.
의사 도메인의 변동
이전 상황과 같이 환자가 아픈 곳이 있어 의사를 찾아온 상황을 가정해보자. 하지만 이번엔 단순히 진단을 받고 약을 처방받는 것만으로는 부족하고, 주사를 맞아야한다.
의사의 할 일이 늘었다. 객체로 보자면 의사 객체에 메서드가 하나 추가된 것이다.
다음 환자는 진단 결과 입원까지 해야하는 환자라고 가정해보자. 그럼 의사의 할 일은 더욱 늘어나게 된다.
대략적으로 보기만 하더라도 의사의 할 일이 굉장히 늘게된다. 의사의 책임은 환자를 치료한다는 것은 같다. 하지만 책임의 범위는 더욱 넓어졌다. 의사는 간호사를 고용해 업무를 배정하기로 했다. 어디서부터 어디까지 배정해야할까? 그것을 결정하는 것은 의사일 것이다. 본인이 할 수 있는 일과 간호사가 할 수 있는 일을 구분할 것이고, 급여에 따라 할 일을 고려할 수도 있고, 환자의 수나 상황에 따라 업무량을 조절할 것이다. 의사가 해야할 일과 간호사가 해야할 일이 어느 정도 예상이 가고 일반적으로 구분을 하겠지만, 자세한 것에 대한 절대적인 기준은 없다.
의사는 간호사를 고용해 어렵지 않은 일을 처리하도록 업무를 분배했다. 간호사의 업무들을 하나로 대표하자면 환자 치료를 보조하는 것이다. 즉, 간호사의 책임은 환자 치료를 보조하는 것이고 간호사의 업무인 주사, 입원 절차 설명, 병실 배정, 후속 치료 등은 환자 치료를 보조한다는 책임의 범주에 들어가 있다. 의사의 책임은 환자를 치료한다는 것으로 같지만, 책임의 범주는 둘로 나눌 수 있었다.
만약 병원 상황이 어려워져서 하루에 환자가 10명 내외라고 극단적으로 가정을 해보자. 그렇다면 간호사를 그대로 고용하는 것은 불필요한 인건비를 지출하게 되는 것이니 다시 모든 것을 의사가 하는 것이 나을 것이다. 그러면 간호사의 업무는 의사의 책임의 범주에 그대로 포함될 것이다. 다양한 환자를 받게 되는 의사의 할 일은 더욱 많아질 수도 있지만 말이다! 책임의 범주란 이런 식으로 절대적인 기준이 있는 것이 아니라고 생각한다.
예상되는 변경
객체는 개발자가 정한 책임을 가지고 있다. 애플리케이션이 변경이 필요한 경우 해당 변경의 이유가 무엇인지를 따라가다 보면 어떤 객체를 변경해야 하는지 쉽게 나온다. 그렇기 때문에 단일 책임 원칙을 준수하며 애플리케이션을 설계하거나 구현하는 것이 권장된다.
하지만 객체의 책임의 범주는 주관적인 것이고 개발하다보면 어느새 객체의 할 일이 과한 것 같을 때도 있다. 즉, 변경의 이유가 한 개가 아니라 두 개가 되는 경우도 있을 것이다. 단일 책임 원칙을 준수하기 위해서 다른 클래스를 만들어 책임을 분리하는 것이 맞을 것이다. 하지만 책임의 정의는 "변경의 이유"이고, 만약 변경이 없을 것이라면 책임을 나눌 필요가 없지 않을까?
모뎀의 책임
public interface Modem {
void dial(String pno);
void hangUp();
void send(char ch);
char receive();
}
위의 모뎀이라는 인터페이스는 네 개의 기능을 한다. dial
은 전송을 걸기 위한 요청의 기능이고, hangUp
은 해당 요청을 받는 기능이며, send
는 데이터를 보내고, receive
는 데이터를 받는 기능이다. dial
과 hangUp
은 연결을 위한 기능이고, send
와 receive
는 데이터를 송수신 하는 기능이다. 여기까지 본다면 dial
과 hangUp
의 책임을 담당하는 객체를 두고, send
와 receive
의 기능을 담당하는 객체를 두는 것이 바람직해보인다.
만약 dial
과 hangUp
의 구현이 복잡하지 않아 변경이 없을 것이 확실하다면, dial
과 hangUp
의 변경의 이유는 없다고 할 수 있지 않을까? 그렇다면 모뎀 인터페이스는 분리할 이유가 없어진다. 왜냐하면 책임이란 변경의 이유인데, 모뎀은 사실상 하나의 변경의 이유만을 가지고 있기 때문이다. 오히려 책임을 분리하는 것이 복잡성을 증가시킬 수도 있는 상황이다.
마치며
객체 지향 프로그래밍은 서로 다른 책임을 가지고 있는 객체들 간의 협력으로 구성된 애플리케이션을 구현함으로써 변경이나 확장이 있을 경우 손쉽게 그를 성취하기 위해서이다. 그래서 절대적인 기준이 없고 그렇기에 새로운 애플리케이션을 만들 때마다, 혹은 새로운 객체에 대해 고민할 때면 책임에 대한 고민이 따르기 마련이다.
책임을 확실히 하는 것은 중요하지만, 불필요하게 고려하는 것은 오히려 복잡성을 증가시키기도 하고, 상황 별로 애매하게 남겨두는 것이 더 나을 때도 있을 것이다. 논란없는 정답이 없기 때문에 어려운 책임이지만, 나만의 기준으로 책임을 잘 구분하는 근거를 이루고자 한다.
Reference
R. C. Martin, Agile Software Development: Principles, Patterns, and Practices. Harlow, Essex: Pearson Education Limited, 2014.
'객체지향 프로그래밍' 카테고리의 다른 글
객체지향의 특성 - 추상화 (0) | 2024.05.22 |
---|---|
객체지향의 특성 - 상속 (0) | 2024.05.21 |
객체지향의 특성 - 캡슐화 (정보은닉) (0) | 2024.05.21 |
객체지향 프로그래밍(OOP) 이야기 (1) | 2024.05.20 |
단일 책임 원칙 이야기 (1) - 책임이란 (1) | 2023.11.30 |