SOLID Principles
SOLID 원칙은 2000 년 Robert C. Martin 에 의해 체계화된 객체 지향 설계의 5 대 핵심 원칙이다. 단일 책임 (SRP), 개방/폐쇄 (OCP), 리스코프 치환 (LSP), 인터페이스 분리 (ISP), 의존성 역전 (DIP) 원칙으로 구성되어 있다. 이 원칙들은 코드의 결합도를 낮추고, 변경에 유연하며, 테스트와 유지보수를 쉽게 만들어준다. SOLID 는 현대 소프트웨어 개발에서 품질 높은 시스템 구축의 표준이자 필수 지침으로 널리 사용된다
핵심 개념
SOLID 원칙의 정의
- SRP (Single Responsibility Principle): 클래스는 하나의 책임만 가져야 하며, 변경되는 이유도 하나여야 함
- OCP (Open/Closed Principle): 소프트웨어 개체는 확장에는 열려있고 수정에는 닫혀있어야 함
- LSP (Liskov Substitution Principle): 하위 타입은 상위 타입으로 대체 가능해야 함
- ISP (Interface Segregation Principle): 클라이언트는 사용하지 않는 인터페이스에 의존하지 않아야 함
- DIP (Dependency Inversion Principle): 상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 함
핵심 목표
- 코드의 유지보수성 (Maintainability) 향상
- 시스템 확장성 (Extensibility) 보장
- 코드 재사용성 (Reusability) 증대
- 테스트 용이성 (Testability) 확보
- 결합도 (Coupling) 감소 및 응집도 (Cohesion) 증가
배경
SOLID 원칙은 2000 년 Robert C. Martin(Uncle Bob) 이 “Design Principles and Design Patterns” 논문에서 처음 제시했다. 이후 Michael Feathers 가 SOLID 라는 약어를 도입했다. 이 원칙들은 수십 년간의 객체 지향 프로그래밍 경험과 모범 사례를 바탕으로 체계화되었으며, 애자일 소프트웨어 개발과 클린 코드 철학의 기초가 되었다.
목적 및 필요성
- 코드 부패 (Code Rot) 방지: 시간이 지남에 따라 코드가 경직되고, 취약해지며, 이식성이 떨어지는 문제 해결
- 변경 용이성: 요구사항 변화에 신속하게 대응할 수 있는 유연한 구조 제공
- 협업 효율성: 여러 개발자가 동시에 작업할 수 있는 모듈화된 구조 구축
- 기술 부채 감소: 장기적인 유지보수 비용 절감
주요 기능 및 역할
- 설계 지침 제공: 클래스와 모듈 설계 시 따라야 할 구체적인 가이드라인 제시
- 품질 보증: 코드 리뷰와 아키텍처 검증의 기준점 역할
- 팀 표준화: 개발팀 내 공통된 설계 철학과 접근 방식 확립
- 리팩터링 기준: 기존 코드 개선 시 목표와 방향 제시
특징
- 상호 보완성: 5 개 원칙이 서로 연관되어 시너지 효과 창출
- 언어 독립성: 객체 지향 언어라면 Java, C#, Python, JavaScript 등에서 모두 적용 가능
- 확장성: 마이크로서비스, 함수형 프로그래밍 등 다른 패러다임에도 개념 적용 가능
- 점진적 적용: 프로젝트 규모와 상황에 맞춰 단계적으로 도입 가능
핵심 원칙
원칙명 | 정의 | 핵심 요소 | 적용/구현 방법 |
---|---|---|---|
SRP (단일 책임 원칙)Single Responsibility Principle | 클래스는 단 하나의 변경 이유만을 가져야 함 | 하나의 액터 (Actor) 에 대한 책임 | 책임의 성격과 변경 요인 기준으로 분리 |
OCP (개방/폐쇄 원칙)Open/Closed Principle | 확장에는 열려 있고, 수정에는 닫혀 있어야 함 | 기존 코드 변경 없이 확장 가능 | 추상화, 다형성, 상속 등의 활용 |
LSP (리스코프 치환 원칙)Liskov Substitution Principle | 하위 타입은 상위 타입으로 대체 가능해야 함 | 행동의 일관성 보장 | 사전 조건 강화 금지, 사후 조건 약화 금지 |
ISP (인터페이스 분리 원칙)Interface Segregation Principle | 클라이언트는 사용하지 않는 메서드에 의존하지 않아야 함 | 작고 응집력 높은 인터페이스 | 인터페이스 최소화, 역할 중심 인터페이스 분리 |
DIP (의존성 역전 원칙)Dependency Inversion Principle | 고수준 모듈과 저수준 모듈 모두 추상화에 의존해야 함 | 구체 구현보다 추상화에 의존 | 의존성 주입 (DI), 인터페이스 기반 설계 |
SRP (Single Responsibility Principle)–단일 책임 원칙
항목 | 내용 |
---|---|
개념 | 클래스는 하나의 변경 이유만 가져야 한다. |
핵심 원칙 | 하나의 클래스는 하나의 책임 (기능) 만을 가져야 하며, 하나의 행위만을 변경해야 한다. |
설명 | 여러 책임을 가지는 클래스는 기능이 늘어나며 결합도가 높아지고, 유지보수가 어려워진다. |
연관성 | OCP(확장성), DIP(구조 분리) 와 강하게 연계됨. |
잘된 예시
|
|
무엇이 잘된 것인지:
- 각 클래스가 단일 책임을 가짐
- Employee: 급여 계산 로직만 담당
- EmployeeRepository: 데이터 영속성만 담당
- EmployeeReportService: 보고서 생성만 담당
- EmailService: 알림 발송만 담당
- 각 클래스가 하나의 변경 이유만 가지므로 유지보수가 용이
잘못된 예시
|
|
무엇이 잘못되었는지:
- 하나의 클래스가 여러 책임을 가져 SRP 위반
- 비즈니스 로직, 데이터 접근, 보고서 생성, 통신 기능이 혼재
- 여러 변경 이유가 존재하여 한 부분의 변경이 다른 부분에 영향
- 테스트하기 어려운 구조
- 다양한 팀이 같은 클래스를 다른 이유로 수정할 수 있어 충돌 위험
OCP (Open/Closed Principle)–개방 - 폐쇄 원칙
항목 | 내용 |
---|---|
개념 | 확장에는 열려 있고, 변경에는 닫혀 있어야 한다. |
핵심 원칙 | 기존 코드를 수정하지 않고도 기능을 확장할 수 있어야 한다. |
설명 | 새로운 요구사항이 생겨도 기존 클래스는 그대로 유지되어야 한다. |
연관성 | LSP(상속), DIP(의존 역전) 와 함께 자주 적용됨. |
잘된 예시
|
|
무엇이 잘된 것인지:
- 새로운 도형 클래스 추가 시 기존 코드 수정 없이 확장 가능
- 추상 클래스를 통한 계약 정의
- 다형성을 활용한 동일한 인터페이스 제공
- AreaCalculator 는 새로운 Shape 구현체가 추가되어도 변경 불필요
잘못된 예시
|
|
무엇이 잘못되었는지:
- 새로운 도형 추가 시 기존 AreaCalculator 클래스 수정 필요
- if-else 또는 switch 문의 과다 사용은 OCP 위반의 일반적인 냄새
- 확장할 때마다 기존 코드가 변경되어 버그 위험 증가
- 단일 확장에 여러 수정이 필요한 구조
LSP (Liskov Substitution Principle)–리스코프 치환 원칙
항목 | 내용 |
---|---|
개념 | 서브 클래스는 부모 클래스를 대체할 수 있어야 한다. |
핵심 원칙 | 하위 클래스는 상위 클래스의 행위를 그대로 사용할 수 있어야 하며, 기대를 깨면 안 된다. |
설명 | 다형성을 사용할 수 있어야 하며, “is-a” 관계를 보장해야 한다. |
연관성 | OCP, DIP 의 구현을 위한 핵심 원칙 중 하나 |
잘된 예시
|
|
무엇이 잘된 것인지:
- 각 하위 클래스가 상위 클래스와 동일한 방식으로 동작
- 펭귄은 날 수 없으므로 FlyingBird 를 상속하지 않음
- 각 클래스가 자신의 고유한 행동을 올바르게 구현
- 상위 타입을 사용하는 코드가 하위 타입으로 정상 동작
잘못된 예시
|
|
무엇이 잘못되었는지:
- Square 클래스가 Rectangle 의 불변 조건을 위반
- 너비와 높이를 독립적으로 설정할 수 있다는 Rectangle 의 기대를 위반
- 상위 클래스와 하위 클래스가 예상과 다른 결과 생성
- 클라이언트 코드가 구체적인 타입을 확인해야 하는 상황 발생
ISP (Interface Segregation Principle)–인터페이스 분리 원칙
항목 | 내용 |
---|---|
개념 | 하나의 일반적인 인터페이스보다는 여러 개의 구체적인 인터페이스가 낫다. |
핵심 원칙 | 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다. |
설명 | 인터페이스가 너무 많아도 문제지만, 너무 커도 문제 → 역할 중심 분할 필요 |
연관성 | DIP, SRP 와 함께 인터페이스 설계 시 활용 |
잘된 예시
|
|
무엇이 잘된 것인지:
- 각 클래스가 필요한 기능만 구현
- SimplePrinter 는 스캔이나 팩스 기능을 구현할 필요 없음
- 작고 응집력 있는 인터페이스로 분리
- 클라이언트가 사용하지 않는 메서드에 의존하지 않음
잘못된 예시
|
|
무엇이 잘못되었는지:
- 클래스가 사용하지 않는 메서드들을 강제로 구현
- UnsupportedOperationException 발생은 ISP 위반의 지표
- 거대한 인터페이스로 인한 불필요한 의존성
- 클라이언트가 사용하지 않는 기능에 대해서도 알아야 함
DIP (Dependency Inversion Principle)–의존성 역전 원칙
항목 | 내용 |
---|---|
개념 | 고수준 모듈은 저수준 모듈에 의존하지 말고, 둘 다 추상화에 의존해야 한다. |
핵심 원칙 | 구현이 아닌 인터페이스 (추상) 에 의존해야 한다. |
설명 | 유연성과 테스트 용이성을 위해 설계 계층 간 결합도를 낮춤 |
연관성 | 모든 원칙과 결합되어 고수준 모듈 설계에 기초가 되는 원칙 |
잘된 예시
|
|
무엇이 잘된 것인지:
- 상위 수준 모듈 (OrderService) 이 하위 수준 모듈의 구체적 구현에 의존하지 않음
- 추상화 (인터페이스) 를 통한 의존성 관리
- 동일한 상위 수준 로직을 다른 하위 수준 구현과 재사용 가능
- 테스트 시 Mock 객체로 쉽게 대체 가능
잘못된 예시
|
|
무엇이 잘못되었는지:
- 상위 클래스가 하위 클래스의 세부사항에 직접 의존
- 새로운 결제 방식 추가 시 OrderService 수정 필요
- 테스트 시 실제 결제 처리와 이메일 발송이 실행될 위험
- 상위 수준 로직의 재사용성 제한
- 구체적 구현 변경 시 상위 모듈도 영향받음
전체 원칙들의 상호작용
SOLID 원칙들은 서로 밀접하게 연관되어 있다:
- SRP는 다른 모든 원칙의 기초
- OCP는 LSP 와 DIP 를 통해 달성됨
- LSP는 OCP 를 안전하게 만듦
- ISP는 SRP 의 인터페이스 버전
- DIP는 OCP 와 LSP 의 조합으로 간주됨
이러한 원칙들을 함께 적용하면 유지보수 가능하고, 확장 가능하며, 테스트하기 쉬운 소프트웨어를 만들 수 있다.
sequenceDiagram participant C as Client participant A as Abstraction participant I1 as Implementation1 participant I2 as Implementation2 Note over C,I2: 의존성 역전 원칙 적용 C->>A: 추상화를 통한 접근 A->>I1: 구체적 구현 호출 I1-->>A: 결과 반환 A-->>C: 결과 전달 Note over C,I2: 구현체 교체 (개방/폐쇄 원칙) C->>A: 동일한 인터페이스 사용 A->>I2: 새로운 구현체 호출 I2-->>A: 결과 반환 A-->>C: 결과 전달
구조조
classDiagram class Client { -service: IService +doSomething() } class IService { <<interface>> +process() } class ServiceA { +process() } class ServiceB { +process() } class DIContainer { +register(interface, implementation) +resolve(interface) } Client --> IService : depends on abstraction ServiceA ..|> IService : implements ServiceB ..|> IService : implements DIContainer --> ServiceA : creates DIContainer --> ServiceB : creates Client --> DIContainer : gets dependency
장점과 단점
구분 | 항목 | 설명 |
---|---|---|
✅ 장점 | 유지보수성 향상 | 코드 변경 시 영향 범위가 제한되어 안전한 수정 가능 |
확장성 보장 | 기존 코드 수정 없이 새로운 기능 추가 가능 | |
재사용성 증대 | 모듈화된 설계로 다른 프로젝트에서 재사용 용이 | |
테스트 용이성 | 의존성 분리로 단위 테스트 작성이 쉬워짐 | |
협업 효율성 | 명확한 인터페이스로 팀 작업 시 충돌 최소화 | |
코드 가독성 | 단일 책임으로 각 클래스의 역할이 명확함 | |
⚠ 단점 | 초기 복잡성 증가 | 설계 단계에서 더 많은 시간과 노력 필요 |
과도한 추상화 위험 | 불필요한 인터페이스와 클래스 생성으로 복잡도 증가 | |
성능 오버헤드 | 추상화 계층으로 인한 간접 호출 비용 | |
학습 곡선 | 개발팀의 이해와 숙련도 필요 | |
과잉 엔지니어링 | 간단한 기능에 과도한 설계 패턴 적용 위험 |
도전 과제
과제 | 설명 | 해결 방안 |
---|---|---|
과도한 추상화 / Over-engineering | 간단한 기능에도 불필요한 인터페이스 및 계층 도입으로 구조 복잡도 증가 | YAGNI 원칙 적용, 책임 단위 기준 설계, 점진적 리팩터링 |
LSP (리스코프 치환 원칙) 위반 탐지 어려움 | 위반 여부가 런타임까지 드러나지 않아 테스트 어려움 | 계약 기반 설계 (Contract-based Design), 단위 테스트 강화 |
DIP 구현의 복잡성 | DI 컨테이너가 없을 경우 수동 구현 부담 | Spring 등 DI 프레임워크 활용, 기본 생성자 주입 전략 적용 |
레거시 코드 적용의 어려움 | 기존 코드에 SOLID 원칙 적용 시 대규모 리팩터링 필요 | 스트랭글러 패턴 적용, 자동화 테스트 기반 점진적 리팩터링 |
성능과 설계 원칙 간 균형 | 원칙 적용으로 인한 호출 비용 증가, 추상화 계층의 성능 저하 가능성 | 성능 크리티컬 영역 식별 후 최적화, 프로파일링 기반 성능 측정 |
팀 간 일관성 부족 | 팀원 간 SOLID 원칙 이해도 및 적용 방식 차이 | 아키텍처 가이드 정립, 코드 리뷰/교육/페어 프로그래밍 통한 공유 |
실무 적용 예시
적용 분야 | 적용 원칙 | 구체적 사례 | 기대 효과 |
---|---|---|---|
웹 애플리케이션 | SRP + DIP + OCP | 컨트롤러, 서비스, 레포지토리 계층 분리 | 계층별 책임 명확화, 유연한 확장 |
결제 시스템 | OCP + DIP + ISP | 결제 방식별 인터페이스 설계 (신용카드, 간편결제 등) | 새로운 결제 수단 추가 용이 |
로깅 시스템 | ISP + DIP | 로그 수준 (INFO, WARN, ERROR) 별 인터페이스 분리 | 필요한 로깅 기능만 의존, 유연한 구성 |
ETL 데이터 처리 | SRP + OCP | 추출, 변환, 적재 각 단계별 클래스 분리 | 모듈별 독립적 수정 가능, 테스트 용이 |
게임 엔진 구조 | LSP + OCP | 다양한 게임 오브젝트가 동일 인터페이스 상속 및 교체 | 다양한 객체 유형 유연하게 지원 |
마이크로서비스/API | 전체 SOLID 원칙 적용 | API Gateway + 각 서비스의 독립적 책임 및 DI 적용 | 서비스 독립성 강화, 유지보수성과 확장성 확보 |
MVVM 패턴 (Android) | SRP + DIP | ViewModel → Repository 인터페이스 의존 구조 | 단일 책임과 의존성 역전을 통한 테스트 용이 |
알림 시스템 | DIP + DI 적용 | Email, SMS, Push Notification 분리 | 테스트 및 구현체 교체 유연성 |
주문 시스템 | SRP 적용 | 주문 처리 로직과 결제/배송 로직 분리 | 단일 책임 기반 확장 및 변경 용이 |
활용 사례
사례 1: 전자상거래 플랫폼에서 주문, 결제, 알림 시스템을 SOLID 원칙에 따라 설계
시스템 구성:
- 주문 서비스: SRP 적용, 주문 처리만 담당
- 결제 서비스: OCP/ISP 적용, 다양한 결제 방식 확장
- 알림 서비스: DIP 적용, 의존성 주입으로 구현체 교체 용이
시스템 구성 다이어그램
classDiagram class OrderService { +processOrder() } class PaymentService { > +pay() } class KakaoPayService { +pay() } class NaverPayService { +pay() } class NotificationService { > +notify() } class EmailNotification { +notify() } class SMSNotification { +notify() } OrderService --> PaymentService KakaoPayService ..|> PaymentService NaverPayService ..|> PaymentService OrderService --> NotificationService EmailNotification ..|> NotificationService SMSNotification ..|> NotificationService
Workflow:
- 주문 발생 → 주문 서비스 처리 (SRP)
- 결제 요청 → 결제 서비스 인터페이스 통해 다양한 결제 방식 확장 (OCP, ISP)
- 알림 발송 → 알림 서비스 인터페이스로 다양한 알림 방식 확장 (DIP)
역할:
- 각 서비스는 단일 책임만 담당
- 새로운 결제/알림 방식 추가 시 기존 코드 수정 없이 확장
사례 2: 전자결제 시스템 리팩토링
시나리오: OrderService 가 직접 여러 결제 방법 (Card, KakaoPay, NaverPay) 에 의존 → SRP, DIP, OCP 위반
SOLID 적용 후 구조:
IPaymentProcessor
인터페이스 정의- 각각의 결제 방식이 이를 구현
OrderService
는 이 인터페이스에만 의존 (DIP)- 새로운 결제 방식은 기존 코드 수정 없이 추가 (OCP)
구조:
classDiagram class OrderService { -IPaymentProcessor processor +processOrder() } class IPaymentProcessor { <<interface>> +processPayment() } class CardProcessor { +processPayment() } class KakaoPayProcessor { +processPayment() } OrderService --> IPaymentProcessor CardProcessor ..|> IPaymentProcessor KakaoPayProcessor ..|> IPaymentProcessor
워크플로우:
- 사용자가 결제 요청 → OrderService
- OrderService 는 processor 에 위임
- Processor 는 실제 결제 처리
- 결과 반환
사례 3: E-commerce 주문 처리 시스템
시나리오: 온라인 쇼핑몰에서 주문 처리, 결제, 배송, 알림 등을 통합 관리하는 시스템
시스템 구성:
graph TB subgraph "Presentation Layer" C[OrderController] end subgraph "Business Layer" OS[OrderService] PS[PaymentService] NS[NotificationService] IS[InventoryService] end subgraph "Data Layer" OR[OrderRepository] PR[PaymentRepository] end subgraph "External Services" PG[PaymentGateway] ES[EmailService] SS[SMSService] end C --> OS OS --> PS OS --> NS OS --> IS OS --> OR PS --> PR PS --> PG NS --> ES NS --> SS
SOLID 원칙 적용:
SRP 적용: 각 서비스가 단일 책임을 가짐
- OrderService: 주문 처리 로직만 담당
- PaymentService: 결제 처리만 담당
- NotificationService: 알림 발송만 담당
OCP 적용: 새로운 결제 방식이나 알림 채널 추가 시 기존 코드 수정 없이 확장
LSP 적용: 모든 PaymentProcessor 구현체는 동일한 방식으로 동작
ISP 적용: NotificationService 는 필요한 알림 방식만 의존
DIP 적용: 모든 서비스는 인터페이스에 의존
Workflow:
sequenceDiagram participant Client participant OrderController participant OrderService participant PaymentService participant NotificationService participant PaymentGateway Client->>OrderController: 주문 요청 OrderController->>OrderService: 주문 처리 OrderService->>PaymentService: 결제 처리 PaymentService->>PaymentGateway: 결제 게이트웨이 호출 PaymentGateway-->>PaymentService: 결제 결과 PaymentService-->>OrderService: 결제 결과 OrderService->>NotificationService: 알림 발송 NotificationService-->>OrderService: 알림 완료 OrderService-->>OrderController: 주문 완료 OrderController-->>Client: 응답
역할 분담:
- OrderController: HTTP 요청/응답 처리, 입력 검증
- OrderService: 비즈니스 로직 조율, 트랜잭션 관리
- PaymentService: 결제 처리 추상화, 다양한 결제 수단 지원
- NotificationService: 다중 채널 알림 발송
- Repository 계층: 데이터 영속성 관리
실무에서 효과적으로 적용하기 위한 고려사항 및 권장 사항
구분 | 고려사항 | 설명 | 권장 사항 |
---|---|---|---|
설계 단계 | 책임 경계 설정 | 요구사항 기반으로 책임과 역할을 명확히 구분 | 도메인 모델링을 병행하고, 점진적으로 설계 |
인터페이스 중심 설계 (IDD) | 구현보다 인터페이스부터 먼저 정의 | 실제로 변경 가능성이 있는 영역만 추상화 | |
과도한 추상화 방지 | 구조가 불필요하게 복잡해질 수 있음 | 기능 중심의 단순한 추상화 적용, YAGNI 원칙 준수 | |
구현 단계 | DIP 및 DI 전략 수립 | 구현체 교체 가능성을 염두에 둔 구조 설계 | 생성자 주입 우선, 필요 시 세터/필드 주입 보조 |
테스트 단계 | 테스트 전략 정립 | 인터페이스 기반 테스트 구성 용이 | Mock, Stub 등 테스트 더블 활용, 실제 객체와의 균형 유지 |
자동화 테스트 확보 | 구조 변경 시 회귀 오류 방지 | 단위/통합 테스트 모두 작성 | |
리팩토링 | 주기적 리팩토링 | 초기 설계의 한계를 점진적으로 개선 | 작은 단위로 반복적 개선, 코드 냄새 탐지 도구 활용 |
팀 협업 | 팀 내 합의 및 표준화 | 개발자 간 설계 원칙과 구조 이해도 차이 해소 필요 | 아키텍처 가이드 작성, 코드 리뷰와 교육으로 지속적 정렬 |
문서화 | 설계 의도 및 제약사항 명시 | 코드로 드러나지 않는 의사결정을 공유해야 할 경우 | 변경 가능성 높은 로직이나 정책은 문서화, 과도한 문서화는 지양 |
적용 범위 | 변화 가능성이 높은 영역부터 적용 | 시스템 전체에 일괄 적용하기보단, 중요 영역 중심으로 적용 시작 | 도메인 복잡성/변화율 기준으로 적용 우선순위 결정 |
성능 고려 | 추상화와 DI 의 성능 영향 | 런타임 성능에 미치는 영향이 크지 않지만 크리티컬 영역에선 주의 필요 | 프로파일링을 통해 성능 민감 코드에서는 최적화 적용 |
최적화하기 위한 고려사항 및 주의할 점
구분 | 고려사항 | 주의할 점 | 권장 사항 |
---|---|---|---|
메모리 사용 | 객체 생성 비용과 재사용성 | 과도한 객체 생성은 GC (Garbage Collection) 부하 | 싱글톤 (Singleton), 객체 풀링 (Object Pooling) 적극 활용 |
호출 경로 | 추상화 계층의 호출 깊이 | 간접 호출로 인해 호출 스택 깊어질 수 있음 | 성능 민감 영역은 직접 호출 또는 최소 추상화 적용 |
인터페이스 호출 | 가상 함수 호출 비용 | JIT 최적화 (인라이닝 등) 에 제약 가능 | 구조적으로 인라인 최적화 유도 |
DI 전략 | DI 컨테이너 초기화 및 주입 비용 | 런타임 의존성 해결에 따른 성능 저하 | Lazy Initialization, 컴파일 타임 DI (예: Dagger) 고려 |
캐싱 전략 | 추상화된 접근에 따른 캐싱 난이도 | 캐시 무효화 또는 갱신 로직의 복잡성 | 계층형 캐싱 전략 (서비스, DAO 등) 분리 적용 |
객체 수 증가 | 클래스 분리 및 구성 요소 증가 | 설계 복잡성이 성능에 영향을 미칠 수 있음 | SRP 기반의 관심사 분리, 필요 최소 단위로 클래스 구성 |
코드 복잡성 | 추상화와 최적화 간의 균형 | KISS(Keep It Simple, Stupid) 원칙 무시 위험 | 단순하고 명확한 추상화 설계 유지 |
프로파일링 | 병목 구간 식별의 정확성 필요 | 추측 기반 최적화는 오히려 비효율적일 수 있음 | 성능 프로파일링 도구 활용 및 테스트 자동화 |
초기 설정 시간 | 프레임워크 초기화 비용 | 구동 시간 및 리소스 점유 증가 | profile 기반 로딩 제어 (Spring Profile 등), Lazy 주입 |
주제와 관련하여 주목할 내용
분류 | 항목 | 설명 |
---|---|---|
객체지향 설계 | SOLID 원칙 | 유지보수성, 확장성, 유연성을 높이는 핵심 원칙들의 집합 |
SRP (단일 책임 원칙) | 클래스는 하나의 책임만 가져야 하며, 하나의 변경 이유만 가져야 함 | |
OCP (개방/폐쇄 원칙) | 기존 코드를 수정하지 않고 기능을 확장 가능해야 함 | |
LSP (리스코프 치환 원칙) | 하위 타입은 상위 타입을 완전히 대체할 수 있어야 함 | |
ISP (인터페이스 분리 원칙) | 클라이언트에 맞게 인터페이스를 분리하여 불필요한 의존을 줄임 | |
DIP (의존성 역전 원칙) | 고수준/저수준 모듈 모두 추상화에 의존하고, DI 로 구현체를 주입 | |
아키텍처 적용 | 마이크로서비스 아키텍처 | SOLID 원칙이 서비스 경계, API 분리, 인터페이스 안정성에 적용됨 |
도메인 주도 설계 (DDD) | 바운디드 컨텍스트 및 애그리게이트 설계 시 SOLID 원칙과 함께 사용 | |
클린 아키텍처 | 의존성 방향을 내부로 제한하는 구조에서 SOLID 원칙과 잘 통합됨 | |
프로그래밍 언어 | 함수형 프로그래밍 (FP) | 함수 단위 책임 분리, 불변성 유지, 모듈 수준의 OCP/LSP 적용 가능 |
동적 타이핑 언어 | Python/JavaScript 에서 덕 타이핑 기반 DIP, ISP 실현 가능 | |
기술 도구/도입 | DI 컨테이너 | Spring, Angular,.NET Core 등에서 DIP 를 쉽게 적용하는 인프라 제공 |
코드 분석 도구 | SonarQube, ESLint 등으로 SOLID 위반 탐지 및 리팩토링 포인트 확인 가능 | |
성능 최적화 | 컴파일 타임 최적화 | 제네릭, 템플릿 등 정적 타입 기능으로 런타임 오버헤드 최소화 |
메모리 효율성 | 객체 풀링, Flyweight 패턴 등을 통한 메모리 사용량 감소 |
하위 주제로 추가 학습 내용
카테고리 | 주제 | 설명 |
---|---|---|
SOLID 원칙 | SRP (단일 책임 원칙) | 클래스는 하나의 책임만 가지며, 변경 이유는 하나뿐이어야 함 |
OCP (개방/폐쇄 원칙) | 기능 확장에는 열려 있고, 코드 수정에는 닫혀 있어야 함 | |
LSP (리스코프 치환 원칙) | 하위 클래스는 상위 클래스를 대체할 수 있어야 함 | |
ISP (인터페이스 분리 원칙) | 클라이언트에 맞게 인터페이스를 분리하여 불필요한 의존성 제거 | |
DIP (의존성 역전 원칙) | 고수준, 저수준 모듈이 추상화에 의존하도록 하며, DI 로 구현체를 주입 | |
설계 원칙 | DRY, KISS, YAGNI | 중복 제거, 단순 설계, 필요 없는 기능 제거 등의 실용적 개발 원칙 |
디자인 패턴 | 생성 패턴 (Factory 등) | 객체 생성 책임을 추상화하여 유연성 확보 |
구조 패턴 (Adapter 등) | 클래스/객체 간 구조적 결합을 느슨하게 만듦 | |
행위 패턴 (Strategy 등) | 책임 위임과 실행 알고리즘의 유연한 교체 | |
아키텍처 스타일 | 레이어드 아키텍처 | 계층 간 의존 방향을 위에서 아래로 유지하여 책임 분리 |
헥사고날 아키텍처 | 내부 도메인과 외부 어댑터 간의 명확한 경계 설정 | |
이벤트 드리븐 아키텍처 | 이벤트를 중심으로 비동기 메시징 구조 설계 | |
의존성 관리 | DI, IoC, 서비스 로케이터 | 객체 생성 책임 분리와 결합도 감소를 위한 의존성 관리 전략 |
테스트 전략 | 단위 테스트 | DI 기반으로 각 구성요소를 독립적으로 테스트 가능 |
통합 테스트 | 실제 계층/구성요소 간 연동 테스트 | |
계약 테스트 | API 및 인터페이스의 계약 위반 여부 검증 | |
유지보수 전략 | 리팩토링 | SOLID 기반 구조 개선 및 가독성, 확장성 향상 |
품질 보증 | 테스트 자동화 | CI/CD 환경에서 자동화된 검증과 회귀 방지 |
관련 분야 추가 학습 내용
관련 분야 | 주제 | 설명 |
---|---|---|
소프트웨어 공학 | 코드 품질 메트릭 | 응집도, 결합도, 순환 복잡도 측정 방법 |
리팩터링 기법 | 코드 냄새 제거와 구조 개선 방법론 | |
기술 부채 관리 | SOLID 위반으로 인한 기술 부채 식별과 해결 전략 | |
개발 방법론 | 애자일 개발 | 반복 개발에서 SOLID 원칙 점진적 적용 |
TDD (테스트 주도 개발) | 테스트 우선 설계와 SOLID 원칙의 시너지 | |
BDD (행위 주도 개발) | 비즈니스 요구사항과 기술 설계의 연결 | |
성능 엔지니어링 | 프로파일링 | SOLID 적용 시 성능 영향 측정 및 최적화 |
메모리 관리 | 객체 생명주기와 가비지 컬렉션 최적화 | |
보안 | 보안 설계 원칙 | 최소 권한 원칙과 인터페이스 분리의 연관성 |
의존성 보안 | 써드파티 라이브러리 의존성 관리와 보안 취약점 |
용어 정리
용어 | 설명 |
---|---|
추상화 (Abstraction) | 복잡한 구현 세부사항을 숨기고 핵심 기능 또는 동작만 외부에 노출하는 설계 기법 |
의존성 주입 (DI, Dependency Injection) | 객체가 의존하는 다른 객체를 외부에서 주입받아 결합도를 낮추는 설계 기법 |
결합도 (Coupling) | 모듈 또는 클래스 간의 상호 의존성 정도. 낮을수록 모듈성이 높음 |
응집도 (Cohesion) | 모듈 내부 구성요소들이 하나의 책임에 집중하는 정도. 높을수록 유지보수에 유리함 |
리팩토링 (Refactoring) | 기능 변경 없이 코드 구조와 가독성, 유지보수성을 향상시키는 개선 작업 |
액터 (Actor) | SRP(단일 책임 원칙) 에서 클래스의 변경을 요구하는 외부 사용자 또는 이해관계자 |
다형성 (Polymorphism) | 동일한 인터페이스로 다양한 구현체를 동적으로 사용할 수 있는 객체지향 특성 |
코드 냄새 (Code Smell) | 향후 버그나 유지보수 비용 증가를 유발할 수 있는 비정상적 또는 비효율적인 코드 구조 |
인버전 오브 컨트롤 (IoC) | 객체의 제어 권한 (생성, 관리 등) 을 개발자가 아닌 프레임워크나 컨테이너에 위임하는 원칙 |
테스트 더블 (Test Double) | 테스트 시 실제 객체 대신 사용하는 가짜 객체. Mock, Stub, Spy, Fake 등을 포함 |
SOLID | 객체지향 설계의 다섯 가지 핵심 원칙을 총칭 (SRP, OCP, LSP, ISP, DIP) |
SRP (Single Responsibility Principle) | 단일 책임 원칙. 클래스는 하나의 책임만 가져야 하며 하나의 액터에만 영향을 받아야 함 |
OCP (Open/Closed Principle) | 확장에는 열려 있고, 기존 코드 변경에는 닫혀 있어야 하는 개방/폐쇄 원칙 |
LSP (Liskov Substitution Principle) | 리스코프 치환 원칙. 하위 클래스는 상위 클래스의 기능을 대체 가능해야 함 |
ISP (Interface Segregation Principle) | 인터페이스 분리 원칙. 클라이언트가 사용하지 않는 메서드에 의존하지 않도록 인터페이스를 분리 |
DIP (Dependency Inversion Principle) | 의존성 역전 원칙. 고수준/저수준 모듈이 추상화에 의존하고, 구체 구현에는 의존하지 않음 |
참고 및 출처
- SOLID: The First 5 Principles of Object Oriented Design | DigitalOcean
- The SOLID Principles of Object-Oriented Programming Explained in Plain English | freeCodeCamp
- SOLID Principles in Programming: Understand With Real Life Examples | GeeksforGeeks
- A Solid Guide to SOLID Principles | Baeldung
- SOLID Design Principles in Java Application Development | JRebel by Perforce
- SOLID Principles: Improve Object-Oriented Design in Python – Real Python
- A Guide for Applying SOLID Principles in JavaScript | DhiWise
- Why SOLID principles are still the foundation for modern software architecture - Stack Overflow Blog
- An Engineering Leader’s Guide to SOLID Principles | LeadDev
- What are SOLID Principles? | Contabo Blog
- BMC Software - SOLID Principles in Object Oriented Design
- Coderspace - What is SOLID? SOLID Principles with Examples
- Wikipedia - SOLID
- IdeaSoft - SOLID Principles of Software Architecture
- Codefinity - The SOLID Principles in Software Development
- Martin Fowler - Design Principles
- Clean Architecture by Robert C. Martin