Community

소프트웨어 디자인: 단일 책임 원칙🧑🏻‍💻

프로그래밍하다 보면 ‘단일 책임 원칙’에 대해서 많이 고민합니다. iOS 프로그래밍에 대부분 사용되는 일반적인 코드 예제와 시나리오를 통해 ‘단일 책임 원칙’의 책임을 잘 정하는 것이 얼마나 중요한지 알아보려고 합니다. 📄예제 코드 ``` private static var OK_200: Int { return 200 } internal static func map(_ data: Data, from response: HTTPURLResponse) -> Result { guard response.statusCode == OK_200, let root = try? JSONDecoder().decode(Root.self, from: data) else { return .failure(.invalidData) } return .success(root.item) } ``` 💬 시나리오 한 엔지니어가 map 메서드를 ‘책임’ 관점에서 ‘단일 책임 원칙’을 지키고 있는지 의문이 들어 말을 합니다. “map 메서드는 두 가지 책임을 감당하고 있습니다. 첫 번째, status code(응답 상태 코드)를 확인합니다. 두 번째, 데이터를 모델로 매핑합니다. 이는 단일 책임 원칙을 위반합니다. 그 이유는 하나의 메서드에서 서로 관련이 없는 두 가지 책임을 하고 있기 때문입니다. 두 가지 책임 중 하나만 변경되더라도 이 메서드를 변경해야 합니다. 또한, status code가 OK_200이 아닌 401(재인증 프로세스)이 나올 때 관련 로직을 추가해야 합니다.” 코드 작성자 역시 ‘책임’의 관점에서 답변합니다. “이 말은 충분히 일리가 있지만, 단일 책임 원칙을 위반한 것은 아닙니다. map 메서드의 책임은 status code가 포함된 백엔드 API 계약에 따라 ‘성공 응답을 매핑’하는 것입니다! StatusCode 규칙이 변경되면 백엔드 API 계약이 변경되는 것이므로 변경 사항을 반영하기 위해 로직을 업데이트해야 합니다. 또한 map 메서드 내부에서 재인증 로직을 수행할 필요가 없습니다. map 메서드는 401 응답을 거부할 책임이 있습니다. 다른 객체가 재인증 처리를 담당할 것입니다. map 메서드는 부작용이 없는 동작입니다. 단순히 데이터를 변환한 결과(성공 또는 실패)를 반환하기만 하면 됩니다. status code는 map 메서드 외부에서도 사용할 수 있으므로 다른 메서드가 map 메서드에 상관없이 재인증 프로세스를 처리할 수 있습니다. 실제로 재인증 프로세스는 데이터를 map 메서드로 전송하기 전에도 수행할 수 있습니다. 따라서 status code가 401이면 map 메서드를 건너뛰고 인증 플로를 시작할 수 있습니다.” 💡 ‘단일 책임 원칙’을 잘 지키기 위해 적절한 책임을 정하는 것이 중요합니다. 코드만 보기엔 두 가지 책임을 하는 것 같았지만 ‘성공 응답을 매핑’하는 것이 책임이었습니다. 때로는 질문을 한 동료 엔지니어처럼 ‘status code 확인’, ‘데이터를 모델로 매핑’을 각각의 책임으로 보는 것이 좋을 수 있지만, 각각의 책임을 테스트하는 것보다 ‘성공 응답을 매핑’ 한다는 책임이 유저 스토리 측면에서 봤을 때 훨씬 더 경제적이고 올바른 책임이라는 생각이 듭니다. ‘메서드가 아닌 행위를 테스트하라!’ 이 원칙을 기억한다면 ‘단일 책임 원칙’을 좀 더 유연하게 지키는 설계를 할 수 있지 않을까 생각합니다👍👍👍

알림

알림이 없습니다