SOLID 원칙이란? 클린 코드를 만들어보자

SOLID 원칙이란? 클린 코드를 만들어보자

현대 소프트웨어 개발에서는 코드의 가독성과 유지보수성이 매우 중요한 가치로 자리 잡고 있습니다. 변경 요구사항이 빈번히 발생하는 환경에서, 단순히 기능이 동작하는 것을 넘어 읽기 쉽고 이해하기 쉬운 클린 코드를 작성하는 것은 팀의 생산성과 품질을 높이는 핵심 요소이죠.

SOLID 원칙은 객체지향 설계(Object-Oriented Design)에서 코드 품질과 유지보수성을 한층 더 향상시켜 주는 대표적인 가이드라인으로, 다섯 가지 원칙을 의미합니다. 본 글에서는 각 원칙이 무엇을 뜻하는지 깊이 살펴보고, 실제 사례를 통해 효과적으로 적용하는 방법을 소개해드리겠습니다

S - 단일 책임 원칙 (Single Responsibility Principle)

단일 책임 원칙은 클래스나 모듈이 한 가지 책임만 가져야 한다는 개념입니다. 여러 책임을 한곳에 모으면 변경 시 불필요한 영향을 주고, 테스트와 유지보수가 복잡해집니다.
  • 각 클래스는 하나의 비즈니스 도메인 로직만 다룹니다.
  • 변경 사유가 여러 개인 경우, 클래스를 분리하여 역할을 명확히 나눕니다.
  • 테스트 커버리지와 재사용성을 높일 수 있습니다.

사용자 인증을 처리하는 AuthService 클래스가 사용자 정보 저장, 이메일 알림, 토큰 생성까지 모두 담당했다면, 이를 UserRepository, EmailService, TokenProvider와 같이 역할별로 분리합니다. 이렇게 하면 이메일 정책이 변경될 때 EmailService만 수정하면 되므로 유지보수가 간편해집니다.

O - 개방-폐쇄 원칙 (Open-Closed Principle)

개방-폐쇄 원칙은 소프트웨어 요소가 “확장에 열려 있고, 수정에는 닫혀 있어야” 한다는 철학입니다. 기존 코드를 손대지 않고 기능을 추가할 수 있어야 안정적인 시스템 유지가 가능합니다.
  • 인터페이스와 추상화를 활용해 확장 지점을 만듭니다.
  • 새로운 요구사항이 생겨도 기존 코드를 수정하는 일이 없어야 합니다.
  • 의도치 않은 버그 발생 위험을 크게 줄입니다.

PaymentProcessor 인터페이스를 정의하고, 신용카드, 페이팔, 카카오페이 등 다양한 결제 수단을 구현체로 추가하도록 설계합니다. 새로운 결제 수단이 등장해도 인터페이스만 구현하면 되어 기존 코드를 변경할 필요가 없습니다.

L - 리스코프 치환 원칙 (Liskov Substitution Principle)

리스코프 치환 원칙은 “자식 클래스는 부모 클래스가 할 수 있는 일을 동일하게 수행해야” 한다는 원칙입니다. 서브클래스가 부모 클래스의 기대를 어기면 다형성(Polymorphism)의 장점을 활용하기 어렵습니다.
  • 상속 관계에서 사전에 정의한 계약(Contract)을 자식 클래스가 준수해야 합니다.
  • 서브클래스가 부모 클래스의 기능을 제한하거나 변경하지 않습니다.
  • 예외 처리 로직도 부모 클래스와 호환성을 유지해야 합니다.

Rectangle 클래스를 상속한 Square 클래스에서 가로와 세로를 별도로 설정하도록 구현하면, 부모 클래스 메서드를 호출하는 클라이언트 코드가 의도치 않은 결과를 얻게 됩니다. 이럴 때는 Shape 인터페이스를 통해 공통 기능만 정의하거나, 별도의 클래스로 분리하여 설계하는 것이 바람직합니다.

I - 인터페이스 분리 원칙 (Interface Segregation Principle)

인터페이스 분리 원칙은 “clients should not be forced to depend on methods they do not use”라는 개념으로, 큰 인터페이스를 여러 개의 작은 인터페이스로 분리해야 합니다.
  • 클라이언트별로 필요한 기능만 포함된 인터페이스를 정의합니다.
  • 공통 기능은 별도의 인터페이스로 추출합니다.
  • 불필요한 메서드 구현을 강제하지 않도록 설계합니다.

문서 관리 시스템에서 DocumentHandler 인터페이스에 print(), scan(), fax()가 모두 포함되어 있다면 스캐너 전용 디바이스도 fax()를 구현해야 합니다. 이를 Printable, Scannable, Faxable 등 기능별로 인터페이스를 나누면 각 디바이스가 필요한 기능만 구현하게 되어 깔끔해집니다.

D - 의존 역전 원칙 (Dependency Inversion Principle)

의존 역전 원칙은 “고수준 모듈이 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화된 인터페이스에 의존해야 한다”는 원칙입니다. 이를 통해 코드의 결합도를 낮추고 유연한 구조를 구성할 수 있습니다.
  • 구체 클래스가 아닌 인터페이스나 추상 클래스에 의존합니다.
  • 의존성 주입(Dependency Injection)을 활용하여 런타임에 구현체를 변경할 수 있습니다.
  • 단위 테스트 시 모의 객체(Mock)를 주입하여 테스트 커버리지를 높일 수 있습니다.

로그 기능을 ConsoleLoggerFileLogger에 직접 의존하지 않고, Logger 인터페이스를 통해 처리하면 환경에 따라 로깅 구현체를 쉽게 교체할 수 있습니다. 테스트 환경에서는 MockLogger를 주입하여 빠르게 단위 테스트를 수행할 수 있습니다.

마무리하며

SOLID 원칙은 클린 코드와 유지보수성 높은 소프트웨어를 구현하기 위한 핵심 가이드라인입니다.

첫 적용 시 설계가 복잡하게 느껴질 수 있지만, 점진적인 리팩토링과 코드 리뷰를 통해 꾸준히 실천하면 장기적으로 코드 품질과 생산성이 크게 향상됩니다.

여러분의 프로젝트에도 SOLID 원칙을 적용하여 보다 견고하고 확장성 있는 아키텍처를 경험해 보시기 바랍니다~