
(Robert C. Martin (Uncle Bob))
SOLID 원칙(Principle)이란 무엇인가?
OOP는 사실 SOLID 원칙을 사용하지 않고, 작성할 수 있다.
SOLID 원칙을 무시한 채 클래스,상속or 컴포짓,캡슐화,다형성 만으로도 충분히 프로그램은 동작한다.
그렇다면 왜 SOLID 원칙이 등장했을까?
SOLID 원칙은 유지보수성, 확장성,결합도 관리를 위해 일종의 가이드 라인이다.
즉 고품질 'OOP' 코드를 작성하는 가이드 라인이 SOLID 원칙인 것이다.

"Agile Software Development: Principles, Patterns, and Practices" (2002)
SOLID 원칙의 시작
SOLID 원칙은 "Agile Software Development: Principles, Patterns, and Practices" (2002) 에서 나온 것이다.
정확히 말해서 각 세부 원칙은 제각기 이전에 존재했지만 SOLID 라고 로버트 C 마틴이 묶고, 마이클 C 페더스가 그 앞 글자들을 따서 S.O.L.I.D라고 제안했다고 알려져 있다.
로버트 C 마틴은 애자일과 클린 코드 등 세계 프로그래머 계에 거대한 족적을 남긴 위대한 프로그래머이다.
우선 이 원칙의 시작을 설명하기 전 우리는 '애자일Agile'을 알아야한다.
그래야 왜 저 책이 혁명적이었고, 성경 취급을 받는지 알 수 있을 것이니까.

(애자일 선언문)
2001년 프로그래머는 Agile 선언문을 선언한다. 이는 프로그래밍의 철학적 선언이라는 가치에 무게를 둘 수 있지만
'구체적으로 어떻게 실행할 것인가?' 에 대한 답이 없었다.
(애자일의 역사를 설명하는 것도 복잡하지만 대강 이런 것이 있었다만 설명하겠다)
로버트 C 마틴은 Agile Software Development: Principles, Patterns, and Practices(2002)에서 그 구체적인 실행 방안을 작성했고,
애자일 '철학'을 '방법론'으로 만든다.
이 결과 Agile이 '이론'에서 '실무 표준'으로 자리잡는데 기여하게 된다.

실제 초판에서는 SRP, OCP, LSP, ISP, DIP가 개별적으로 소개되며, 이후 이 책이후, 마이클 C. 페더스의 제안으로 SOLID라는 이름이 붙여졌다
(자세한 연표는 약 2004년 경에 SOLID라는 이름이 붙여진 것으로 추측됨)
그리고 이것들은 Agile 디자인에 대한 방법론으로 묘사되는데,
Agile 원칙은 단순히 빠르게 코딩하는게 아닌, 장기적 유지 보수성과 변화에 대한 적응력을 갖춘 설계를 목표로 한다고 설명한다.
즉 SOLID 원칙을 따른다는 것은 기본적으로 애자일 디자인 방법론을 내면화하는데 큰 도움이 된다.
이후에 로버트 마틴(엉클 밥)은 마이클 C 페더스의 제안을 따라 블로그나 강연에서 SOLID 라고 이름 명명하며, SOLID Principle이라고 우리가 아는 형태가 된다.
(SOLID는 고체, 단단한 이라는 의미라서 말장난의 의미가 있으니까)

이 책에서 7장을 요약하자면
설계 냄새(Smells) 코드에서 나타나는 경직성(Rigidity),취약성(Fragility),부동성(Immobility),점성(Viscosity)등으로 문제를 식별하고,
점진적 개선을 통해 설계를 처음부터 완벽히 하지 않고, 반복적으로 리팩토링하며 개선하는 것을 말하는데, 이에 대한 실무적 방법론이 SOLID 원칙이다.
책에서 말하는 냄새가 나는 코드의 특성은 다음과 같다
경직성(Rigidity)
정의
-소프트웨어가 변경하기 어렵다. 즉 코드가 뻣뻣한 상태를 가진 것, 한 부분을 수정하려 할때,
-변경이 시스템의 다른 여러 부분에 영향을 미쳐 예상보다 훨씬 많은 작업이 필요해지는 것을 의미.
특징
-한 클래스의 메서드를 수정하려 했더니, 이를 호출하는 수십 개의 다른 클래스를 함께 수정해야 하는 경우.
-데이터베이스 스키마를 변경하려면 UI, 비즈니스 로직, 데이터 액세스 계층 전체를 손대야 하는 상황.
예시
-코드가 단단하게 결합(Tightly Coupled)되어 있어, 작은 변경이 연쇄적인 수정 요구를 발생시킴
-요구사항 변화에 맞춰 변경이 어려움
문제원인 : 높은 결합도(High Coupling)과 낮은 응집도(Low Cohesion)
해결방법: SOLID의 SRP(단일 책임 원칙)와 DIP(의존 역전 원칙)을 통해 모듈 간 결합도를 낮추고, 변경이 다른 부분에 미치는 영향을 최소화한다.
취약성(Fragility)
정의
-소프트웨어가 쉽게 깨지거나 오류가 발생하는 상태를 의미
-한 부분을 수정했을 때, 예상치 못한 다른 부분에서 문제가 발생하거나 시스템이 불안정해지는 경우
특징
-코드 수정 후 버그가 빈번히 발생합니다.
-수정한 부분과 직접 관련 없는 영역에서 오류가 나타남
예시
-결제 모듈을 수정했는데, 갑자기 로그인 시스템이 작동하지 않는 경우.
-한 메서드의 로직을 개선했는데, 이를 사용하던 다른 모듈에서 런타임 오류가 발생하는 상황.
문제 원인: 부적절한 상속 사용(예: LSP 위반)이나 의존성 관리 실패.
해결방법: LSP(리스코프 치환 원칙)를 준수하여 상속 구조의 안정성을 확보하고, ISP(인터페이스 분리 원칙)로 불필요한 의존성을 제거
부동성 (Immobility)
정의
-소프트웨어의 구성 요소가 재사용하기 어렵거나 이동하기 힘든 상태를 의미
-특정 모듈이나 코드를 다른 프로젝트나 다른 맥락에서 사용하려 할 때, 과도한 의존성 때문에 분리하기 어렵거나 재사용이 불가능한 경우
특징
-코드가 특정 환경에 너무 강하게 묶여 있는 것
-재사용하려면 많은 수정이 필요
예시
-데이터베이스 쿼리 로직이 UI 코드와 얽혀 있어, 다른 프로젝트에서 쿼리 로직만 재사용할 수 없는 경우.
-특정 하드웨어에 의존적인 코드가 다른 플랫폼에서 동작하지 않는 상황.
문제 원인: 모듈 간 높은 결합도와 추상화 부족.
해결방법: DIP(의존 역전 원칙)를 통해 구체적인 구현이 아닌 추상화에 의존하도록 설계하고, OCP(개방-폐쇄 원칙)로 재사용 가능한 구조를 만듬.
점성 (Viscosity)
정의
-소프트웨어 개발 환경이나 코드가 작업을 어렵고 느리게 만드는 상태를 의미.
-점성이 높다는 것은 "올바른 방법"으로 작업하는 것이 "잘못된 방법"보다 더 힘들 때를 말한다.
특징
-주로 두 가지 형태로 나뉜다
-소프트웨어 점성 (Viscosity of the Software): 코드 자체가 유지보수하기 어렵거나, 좋은 설계를 적용하기 힘든 경우.
-환경 점성 (Viscosity of the Environment): 빌드, 테스트, 배포 등의 개발 환경이 비효율적이어서 작업 속도가 느려지는 경우.
예시
-리팩토링 대신 복사-붙여넣기로 코드를 추가하는 것이 더 쉬운 경우(소프트웨어 점성).
-빌드 시간이 너무 길어 코드 수정 후 확인이 느려지는 상황(환경 점성).
문제 원인: 설계 원칙 미준수, 복잡한 개발 프로세스.
해결방법: SRP로 코드 단순성을 유지하고, 지속적인 리팩토링과 자동화된 테스트/빌드 환경을 통해 점성을 낮춤
다만 SOLID 원칙으로 설계의 모든 냄새를 제거 할 수는 없다.
그저 하나의 고품질 가이드라인 일뿐, 이러한 코드의 냄새는 개인의 아키텍쳐 철학과 주관, 그리고 필요성에 따라 달라진다.
예시를 들어보면
대표적 문제인 산탄총 수술(Shotgun Surgery)같은 경우 SOLID 원칙만으로 해결이 어렵다.
산탄총 수술(Shogun Surgery)의 원인은 일반적으로 과도한 책임 분산에 있기때문이다.
산탄총 수술: 이러한 패턴은 안티패턴으로, 잘못된 코드 제작의 사례이지만 자주 나오는 사례를 일반적으로 '안티 패턴'이라고 부르며, 산탄총 수술은 너무 많은 책임 분산을 해버린 경우 발생하는 경우가 많다.
SOLID와 애자일(Agile)은 어떻게 연관되는가?
- SRP는 코드의 경직성(Rigidity)을 줄인다
- OCP는 기존 코드를 수정하지 않고, 확장 가능하게 한다 이는 코드간의 점성(Viscosity)을 줄인다.
- LSP는 상속 구조의 안전성을 보장해 취약성(Fragility)을 줄인다
- DIP와 ISP는 결합도를 낮춰 부동성(Immobility)을 줄인다

(이미지 출처:https://www.instagram.com/techwithisha/reel/C1Ws1ZDt8_j/)
SOLID 원칙?
자, 그럼 위의 원인에 대해서 알았으니 이제는 SOLID 원칙에 대해서 알아보자

내가 생각하는 모든 지적인 사고의 특징을 설명해 보겠다.
그것은, 자신이 다루는 주제의 특정 측면을 깊이 연구하는 태도를 의미한다.
그리고 이 연구는 그 측면 자체의 일관성을 유지하기 위한 것이며, 동시에 자신이 다루고 있는 것이 전체의 일부에 불과하다는 사실을 인지하는 상태에서 이루어진다.
우리는 프로그램이 올바르게 동작해야 함을 알고 있으며,
그 관점에서만 프로그램을 연구할 수 있다.
또한, 프로그램이 효율적이어야 함을 알고 있으며,
이를 분석하는 작업은 다른 날, 말하자면 따로 수행할 수도 있다.
어떤 때에는, 프로그램이 정말로 필요하며, 그렇다면 왜 그런지에 대해 고민할 수도 있다.
그러나 이러한 다양한 측면을 동시에 다루는 것은 아무런 이득이 없으며, 오히려 방해만 될 뿐이다.
나는 이것을 "관심사의 분리(Separation of Concerns)" 라고 부르는데,
비록 완벽하게 실현할 수는 없지만, 사고를 효과적으로 정리하는 유일한 방법이라고 생각한다.
내가 "어떤 한 가지 측면에 초점을 맞춘다" 라고 말할 때,
그것은 다른 측면을 무시한다는 의미가 아니다.
오히려, 특정 측면의 관점에서 보면 다른 측면은 당장은 중요하지 않다는 사실을 받아들이는 것이다.
즉, 한 가지에 집중하면서도 동시에 여러 가지를 고려하는 사고방식을 의미하는 것이다. -에츠허르 다익스트라(On the role of scientific thought. 1982)
1. Single Responsibility Principle (SRP, 단일 책임 원칙)
제안자: Robert C. Martin
정의
-"한 클래스는 하나의 책임만 가져야 한다." 즉, 클래스는 단 하나의 이유로만 변경되어야 한다.
의미
-클래스가 여러 역할을 맡으면, 한 역할의 변경이 다른 역할에 영향을 미쳐 경직성과 취약성이 증가한다.
-책임을 분리하면 코드가 단순해지고 유지보수가 쉬워진다
예시 코드

잘못된 경우: Employee 클래스가 급여 계산과 데이터베이스 저장을 모두 처리 ->급여 로직이 바뀌면 DB 코드도 영향을 받음.

올바른 경우: SalaryCalculator와 EmployeeRepository로 분리.
장점: 코드의 응집도(Cohesion)가 높아지고, 결합도(Coupling)가 낮아짐.
연관 설계 냄새: 경직성, 점성 완화.

(로버트 C 마틴 블로그에서 SRP 설명)
역사적으로는 로버트 C 마틴 블로그 에 따르면 데이비드 L 파르나스의 모듈 분해와 다익스트라의 관심사 분리라는 용어를 시작으로
결합과 응집의 개념이 프로그래밍 커뮤니티에서 퍼져있을때, 각 개념들을 추려내 종합했다고 나와있다.
로버트 C 마틴 블로그에 말해지듯이 SRP는 사람에 관한 것이다.
현실적으로 소프트 웨어는 기업과 조직의 요구사항에 따라 변화하기때문에, 각 모듈이 비즈니스 기능만 담당하게 만들어서,
그 기능을 변경하는 팀이 누군지 쉽게 알기 위해서 만들어진 것이다.

2. Open/Closed Principle (OCP, 개방-폐쇄 원칙)
제안자: Betrand Meyer
정의
-"소프트웨어 개체(클래스, 모듈 등)는 확장에는 열려 있고, 수정에는 닫혀 있어야 한다."
의미
-기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있어야 한다.
-추상화(인터페이스, 추상 클래스)와 다형성을 활용해 구현한다.
예시

잘못된 경우: PaymentProcessor 클래스에 새로운 결제 방식(카드, 현금)을 추가할 때마다 if-else 조건을 수정.

올바른 경우: IPayment 인터페이스를 만들고, CardPayment, CashPayment 클래스로 확장.
장점: 기존 코드의 안정성을 유지하며 새로운 요구사항에 유연하게 대응.
연관 설계 냄새: 경직성, 부동성 완화.
"Object-Oriented Software Construction" (1988)에서
개방-폐쇄 원칙(OCP)은 제2장 "Modularity에 나타난다.

(Data Abstraction and Hierarchy) (1987, OOPSLA 컨퍼런스)
3. Liskov Substitution Principle (LSP, 리스코프 치환 원칙)
제안자: 바바라 리스코프 (Barbara Liskov)
정의
-"자식 클래스는 부모 클래스의 동작을 방해하지 않고 대체 가능해야 한다."
-즉, 프로그램에서 부모 타입을 자식 타입으로 교체해도 문제없이 작동해야 한다.
의미
-상속 관계에서 자식 클래스가 부모 클래스의 계약(Contract)을 위반하면 안된다.
-다형성을 안전하게 사용하기 위한 원칙
예시

잘못된 경우: Bird 클래스의 Fly() 메서드가 있고, Penguin 자식 클래스가 이를 무시하거나 예외를 던짐.

올바른 경우: FlyingBird와 WalkingBird로 분리해 Penguin은 Fly()를 강요받지 않음.
장점:상속 구조의 안정성과 예측 가능성 확보.
연관 설계 냄새: 취약성 완화.
1987년 OOPSLA 컨퍼런스에서 발표된 논문에서 파생된 것으로,
논문에서 데이터 추상화와 계층구조를 다루면서 객체 지향에서 올바른 '상속(Inheritance)'이 무엇인가? 에 대한 철학적 실무적 가이드라인을 제시한다.
여기서 데이터 추상화를
프로그램에서 데이터의 내부 구현을 숨기고, 인터페이스를 통해서만 접근하도록 하는 것을 지칭한다.
결국 따지고보면 현실의 문제를 추상화할때, 얼마나 잘 추상화 시키느냐의 문제이다.
포유류를 정의할때 발이 달렸다라고 정의해버리면 고래를 포유류라고 칭하기 어려워지듯,
어떤 문제에 대한 추상화를 어떻게 잘 시키느냐에 대한 문제라고 이해함이 옳다.
4. Interface Segregation Principle (ISP, 인터페이스 분리 원칙)
제안자: 로버트 C. 마틴 (Robert C. Martin)
정의
-"클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다."
-즉, 인터페이스를 작고 구체적으로 분리해야 한다는 것
의미
큰 범용 인터페이스 대신, 클라이언트가 필요로 하는 기능만 제공하는 인터페이스를 설계한다.
불필요한 의존성을 제거해 결합도를 낮춤
예시

잘못된 경우: IWorker 인터페이스에 Work()와 Eat()가 모두 포함되어, Robot 클래스가 불필요한 Eat()을 구현해야 함.

올바른 경우: IWorkable과 IEatable로 분리해 Robot은 IWorkable만 구현.
장점: 코드의 유연성과 재사용성 증가.
연관 설계 냄새: 취약성, 점성 완화.

(1996년 로버트 C 마틴의 에세이에서 나온 DIP)
5. Dependency Inversion Principle (DIP, 의존 역전 원칙)
제안자: 로버트 C. 마틴 (Robert C. Martin)
정의
-"상위 모듈은 하위 모듈에 의존하지 않아야 하며, 둘 다 추상화에 의존해야 한다."
-또한, "구체적인 것에 의존하지 말고 추상적인 것에 의존하라."
의미
-모듈 간 의존성을 줄이기 위해 인터페이스나 추상 클래스를 중간에 둠
-의존성 주입(Dependency Injection)을 통해 구현됨
예시

잘못된 경우: OrderService가 직접 SqlDatabase에 의존 → DB를 교체하려면 코드 수정 필요.

올바른 경우: IDatabase 인터페이스를 만들고 OrderService가 이를 의존, SqlDatabase는 구현체로 제공.
장점: 시스템의 유연성과 테스트 용이성 증가.
연관 설계 냄새: 경직성, 부동성 완화.
SOLID 원칙의 전체적 의의는
유지보수성과 확장성을 높여 유연하게 대응하는 소프트웨어를 만드는 것이다.
S - 단일 책임 원칙 (SRP) | 한 클래스는 하나의 책임만 가져야 한다. | Robert C. Martin(2002) |
O - 개방-폐쇄 원칙 (OCP) | 기존 코드를 변경하지 않고 확장할 수 있어야 한다. | Bertrand Meyer (1988) |
L - 리스코프 치환 원칙 (LSP) | 하위 클래스는 상위 클래스를 대체할 수 있어야 한다. | Barbara Liskov (1996) |
I - 인터페이스 분리 원칙 (ISP) | 클라이언트가 사용하지 않는 인터페이스에 의존하면 안 된다. | Robert C. Martin(2002) |
D - 의존 역전 원칙 (DIP) | 고수준 모듈은 저수준 모듈에 의존하면 안 된다. | Robert C. Martin(1996) |
하지만 반드시 정답일까?
SOLID 원칙이 무조건 적인 정답이라고 할 수 있을까?
당연히 아니다. SOLID는 가이드라인일뿐이다.

가령 언리얼 엔진의 Actor는
물리,충돌,조명,메시 렌더링, 등등 수 많은 것들을 작업한다
즉 단일 클래스에서 너무 많은 작업을 한다. 그렇다면 이걸 분리하면 과연 편해질까? 전혀 아니다. 오히려 나누는 비용대비 효과가 힘들다.
Actor는 언리얼 엔진의 핵심적 기반 클래스이고 SRP를 나누도록 분리한다면 엔진 전체에 영향을 끼친다.
그렇다면 이게 잘못된 설계일까? 아니다. 게임은 Object를 위주로 설계하기때문에 이 단위에서 충분히 납득가는 설계 이유이다.
오히려 저걸 나누어 버린다면 객체별로 다시 컴포짓하는데 너무나 많은 비용이 든다.
LSP도 그렇다. GUI 프레임워크에서 Button이 Widget을 상속받을때, 반드시 기존 부모의 버튼 동작을 보장해야하는데,
이러면 오히려 현실적으로 문제가 된다. 이렇게 될 경우 버튼 디자인을 다양하게 설계해야할때, 디자인적 독창성이 깨질 수가 있다.
이때문에 이러한 예외를 많은 GUI 프레임워크에서는 보장하고 있다. (C++의 QT 프레임워크, GTK 등등)
그리고 또한 인터페이스에서 오는 오버헤드 등으로 OOP자체가 아닌 DOP등의 다른 지향을 선택해야하는 것이 맞고.
SOLID는 OOP 설계에 맞는 좋은 가이드라인이지만, OOP가 가질 수 있는 성능적 한계, 그리고 설계적 맥락에 의해서 명확하게 어길때를 알아야하기도 하다
자 그럼 SOLID를 어길만한 순간은 어떻게 정해야할까?
SRP: 강한 응집력을 가진 기능들인 경우, SRP를 엄격하게 지키면 클래스간 메서드 호출이 빈번해져 성능 오버헤드가 발생할 수 있다.
OCP: 확장이 빈번하면 좋지만, 성능이 우선이면 수정이 더 나을 수도 있다.
LSP: 상속 구조가 단순할 땐 강요할 필요 없다
ISP: 인터페이스가 너무 많아지면 오히려 혼란스럽다
DIP: 추상화가 오버헤드를 만들면 구체 의존이 더 낫다.

당신의 코드를 유지보수할 사람이 당신의 집 주소를 알고 있는 폭력적인 사이코패스라고 생각하고 코딩하라.- John F Woods(1991)
SOLID의 본질과 실용성의 균형
SOLID 원칙은 애자일 철학에서 태어나 유지보수성과 확장성을 위한 강력한 도구로 자리잡았지만,
그것이 모든 상황의 '은 탄환(Silver Bullet)
언리얼 엔진의 Actor처럼 도메인 특성이 강한 경우나, GUI 프레임워크에서 창의성을 발휘해야 할 때, 또는 성능이 생명인 시스템에선 SOLID를 억지로 따르기보다 맥락에 맞는 설계를 선택하는 게 더 중요하다.
John F. Woods라는 C++ 프로그래머가 1991년 말한,
"당신의 코드를 유지보수할 사람이 폭력적인 사이코패스라고 생각하라"는 격언은 단순히 가독성 높은 코드를 쓰라는 경고가 아니다.
그 뒤에 숨은 뜻은, 코드를 다루는 사람이 어떤 상황에서도 쉽게 이해하고 적응할 수 있게 하라는 것이다.
SOLID는 그 목표를 위한 하나의 길일 뿐, 유일한 길은 아니죠. 때론 SRP를 어기고 통합된 클래스를 유지하거나, DIP를 무시하고 구체 의존을 선택하는 것이
그 "사이코패스"가 폭력을 휘두르지 않게 만드는 실용적인 선택일 수도 있다.
댓글 영역
획득법
① NFT 발행
작성한 게시물을 NFT로 발행하면 일주일 동안 사용할 수 있습니다. (최초 1회)
② NFT 구매
다른 이용자의 NFT를 구매하면 한 달 동안 사용할 수 있습니다. (구매 시마다 갱신)
사용법
디시콘에서지갑연결시 바로 사용 가능합니다.