base
개발을 하다 보면 한 번쯤은 코드가 점점 복잡해지고 한 클래스나 함수가 너무 많은 일을 하고 있는 것 같다는 느낌을 받을 때가 있습니다. 이런 상황이 지속되면 결국 유지보수가 어려워지고, 코드가 엉망이 되기 십상입니다. 만약 여러분의 클래스가 이메일 전송, 데이터 처리, 사용자 인증, 로그 기록까지 모두 처리하고 있다면 어떻게 될까요? 하나의 클래스에서 여러 가지 기능을 다루다 보면 나중에 코드의 어느 한 부분을 수정할 때 다른 부분까지 영향을 미치게 되어 버그가 발생할 가능성이 커집니다.
이때 유용한 개념이 바로 단일 책임 원칙(Single Responsibility Principle, SRP)입니다. 이번 글에서는 소프트웨어 설계의 핵심 원칙 중 하나인 SRP의 개념을 명확하게 이해하고, 실제 개발에 어떻게 적용할 수 있는지에 대해 살펴보겠습니다.
단일 책임 원칙(SRP)이란, 하나의 클래스나 함수가 "단 하나의 책임만 가져야 한다"는 원칙입니다. 이 원칙은 로버트 C. 마틴(Robert C. Martin, Uncle Bob)의 SOLID 원칙 중 첫 번째 원칙으로 잘 알려져 있습니다. 여기서 말하는 "책임"이란 단순히 클래스나 함수가 하는 일 이상의 의미를 갖습니다. 실제로 SRP는 다음과 같은 개념으로 정의됩니다. "한 클래스는 변경되어야 하는 오직 하나의 이유만을 가져야 한다." 즉, 클래스가 수정되어야 하는 이유가 한 가지여야 한다는 것이 핵심입니다. 이메일 전송과 사용자 인증을 함께 처리하는 클래스가 있다면, 이메일 전송 방식이 바뀔 때도, 사용자 인증 로직이 바뀔 때도 동일한 클래스를 수정하게 됩니다. 이런 경우가 SRP를 위반한 사례가 됩니다.
SRP를 잘 지키게 되면, 다음과 같은 장점들이 생깁니다.
이제 간단한 예시를 통해 SRP를 적용하기 전과 후를 비교해 보겠습니다.
class UserService {
registerUser(user) {
this.saveToDatabase(user)
this.sendWelcomeEmail(user)
}
saveToDatabase(user) {
// 데이터베이스 저장 로직
}
sendWelcomeEmail(user) {
// 이메일 전송 로직
}
}
위 코드에서
class UserService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService,
) {}
registerUser(user) {
this.userRepository.save(user)
this.emailService.sendWelcomeEmail(user)
}
}
class UserRepository {
save(user) {
// 데이터베이스 저장 로직
}
}
class EmailService {
sendWelcomeEmail(user) {
// 이메일 전송 로직
}
}
이제 각 클래스는 명확한 책임을 가지고 있습니다.
이렇게 SRP를 적용하면 기능 변경 시 영향받는 부분이 명확해지고, 유지보수가 용이해집니다.
단일 책임 원칙을 적용할 때 지나치게 책임을 세분화하면 오히려 복잡성이 증가할 수도 있습니다. 항상 적절한 추상화와 분리 수준을 유지하는 것이 중요합니다.
적절한 균형점을 찾는 것이 중요합니다. 이는 회사 내부의 convention과 상황에 따라 고려하면 좋을 것 같습니다.
이번 글에서 다룬 내용을 다시 한 번 정리하면 다음과 같습니다.
단일 책임 원칙을 잘 지키면 더 깔끔하고 효율적인 코드를 작성할 수 있으며, 장기적으로 개발 생산성을 높일 수 있습니다.