처음 배우는 SOLID 원칙, 이렇게 하면 어렵지 않아요!

🔍 SOLID 원칙이 뭐길래 그렇게 중요할까요?

개발자분들이라면 한 번쯤 들어보셨을 ‘SOLID 원칙’. 하지만 들어는 봤는데, 머릿속이 복잡해지기만 하고 실제 코드로 와닿지 않으셨던 경험 있으시죠? SOLID는 객체 지향 프로그래밍에서 유지보수성과 확장성을 높이기 위한 5가지 설계 원칙을 말하는데요, 이걸 제대로 이해하면 코드가 ‘말을 잘 듣는 아이’처럼 변하게 됩니다. 개발 과정에서 반복되는 재사용, 변경, 테스트의 과정을 좀 더 유연하게 만들고, 팀 내 협업 시 발생할 수 있는 충돌도 줄일 수 있죠. 그럼 이 다섯 가지 원칙을 구체적인 코드 예제와 함께 하나씩 찬찬히 알아보겠습니다. 어렵다고 느끼셨던 분들도, 이제는 “아, 이런 거였구나!” 하실 수 있게끔 풀어드리겠습니다.

1️⃣ 단일 책임 원칙(SRP: Single Responsibility Principle)

이 원칙은 클래스가 하나의 책임만 가져야 한다는 말인데요, 말은 쉬운데 도대체 책임이 뭐냐고요? 예를 들어 Report라는 클래스를 만들었다고 해보죠. 이 클래스가 보고서를 생성도 하고, 저장도 하고, 출력도 한다면 너무 많은 걸 혼자 다 하고 있는 셈입니다. 아래 예제를 보시죠:

class Report:
def generate(self):
# 보고서 생성 로직
pass

def save_to_file(self):
# 파일로 저장
pass

def print_report(self):
# 보고서 출력
pass
이걸 SRP에 맞게 나누면 이렇게 됩니다:

class ReportGenerator:
def generate(self):
pass

class ReportSaver:
def save_to_file(self):
pass

class ReportPrinter:
def print_report(self):
pass

각 클래스가 하나의 책임만 가지게 됐죠. 이렇게 하면 나중에 출력 방식을 바꾸거나 저장소를 바꿔야 할 때, 관련된 부분만 수정하면 되니 유지보수가 정말 쉬워집니다.

2️⃣ 개방-폐쇄 원칙(OCP: Open/Closed Principle)

“확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다”는 원칙인데요, 처음 들으면 무슨 철학적인 문구 같기도 하죠. 쉽게 말해, 기존 코드를 건드리지 않고 기능을 추가할 수 있어야 한다는 말입니다. 예를 들어 결제 시스템이 있다고 해보죠.

class PaymentProcessor:
def process(self, method):
if method == “credit”:
print(“신용카드 결제”)
elif method == “paypal”:
print(“페이팔 결제”)

이 코드는 새로운 결제 방식이 추가될 때마다 조건문을 수정해야 하므로 OCP를 어기고 있어요. 대신 이렇게 바꿔볼 수 있습니다:

class PaymentMethod:
def process(self):
pass

class CreditCard(PaymentMethod):
def process(self):
print(“신용카드 결제”)

class PayPal(PaymentMethod):
def process(self):
print(“페이팔 결제”)

class PaymentProcessor:
def process(self, method: PaymentMethod):
method.process()

이제 Bitcoin 클래스를 추가하면 PaymentProcessor는 건드릴 필요가 없습니다. 확장은 자유롭게, 기존 코드는 안전하게! 이것이 바로 OCP입니다.

3️⃣ 리스코프 치환 원칙(LSP: Liskov Substitution Principle)

리스코프 원칙은 부모 클래스를 사용하는 곳에 자식 클래스를 넣어도 시스템이 망가지면 안 된다는 원칙입니다. 예를 들어보겠습니다. 다음과 같은 구조가 있다고 할게요.

class Bird:
def fly(self):
pass

class Sparrow(Bird):
def fly(self):
print(“참새가 납니다.”)

class Ostrich(Bird):
def fly(self):
raise Exception(“타조는 못 날아요!”)

이 경우 Bird 타입을 사용하는 코드에서는 모든 새가 날 수 있다고 기대하지만, 타조는 예외를 발생시키죠. 이렇게 되면 상속을 통한 치환이 깨지는 겁니다. 이를 해결하려면 구조를 다시 설계해야 합니다.

class Bird:
pass

class FlyingBird(Bird):
def fly(self):
pass

class Sparrow(FlyingBird):
def fly(self):
print(“참새가 납니다.”)

class Ostrich(Bird):
def walk(self):
print(“타조가 걷습니다.”)

이제 타조는 Bird는 맞지만 날 수 있는 새는 아니라는 점이 명확하게 드러납니다. LSP는 단순한 상속이 아니라 ‘행동의 일관성’을 지키는 데 초점을 둡니다.

4️⃣ 인터페이스 분리 원칙(ISP: Interface Segregation Principle)

이 원칙은 “불필요한 인터페이스는 만들지 말자”는 이야기인데요, 클라이언트가 사용하지 않는 메서드에 의존하게 되면 코드가 불필요하게 복잡해집니다. 예를 들어 모든 로봇이 같은 인터페이스를 구현하도록 강제한다면?

class Robot:
def move(self):
pass

def fly(self):
pass

def swim(self):
pass

이걸 상속받은 육지 로봇은 fly나 swim 같은 불필요한 메서드를 구현해야 하죠. 이를 ISP에 따라 나누면 다음과 같습니다:

class Movable:
def move(self):
pass

class Flyable:
def fly(self):
pass

class Swimmable:
def swim(self):
pass

class LandRobot(Movable):
def move(self):
print(“걷습니다.”)

이렇게 하면 각 객체는 자신이 필요한 기능만 구현하게 되어 코드가 훨씬 깔끔해집니다.

5️⃣ 의존 역전 원칙(DIP: Dependency Inversion Principle)

이 원칙은 “고수준 모듈은 저수준 모듈에 의존하면 안 되고, 둘 다 추상화에 의존해야 한다”는 말입니다. 너무 추상적인가요? 코드를 보면서 이해해보죠.

class Keyboard:
def input(self):
return “입력됨”

class Computer:
def __init__(self):
self.keyboard = Keyboard()

def run(self):
print(self.keyboard.input())

이 코드는 Computer가 Keyboard에 강하게 의존하고 있어서, 나중에 BluetoothKeyboard로 바꾸기 어렵습니다. DIP를 적용하면 이렇게 됩니다:

class IKeyboard:
def input(self):
pass

class WiredKeyboard(IKeyboard):
def input(self):
return “유선 키보드 입력”

class Computer:
def __init__(self, keyboard: IKeyboard):
self.keyboard = keyboard

def run(self):
print(self.keyboard.input())

이제는 어떤 종류의 키보드든 IKeyboard를 구현하기만 하면 교체가 가능합니다. 유연성, 바로 이것이 DIP의 힘입니다.

🧠 마무리하며: SOLID는 개발자의 사고방식 자체를 바꾸는 열쇠입니다

지금까지 SOLID 원칙을 구체적인 코드 예제와 함께 하나씩 알아보았습니다. 사실 이 원칙들은 그냥 “이렇게 코딩하세요”라는 규칙이 아니라, 좀 더 ‘잘’ 짤 수 있게 도와주는 가이드라고 보시면 됩니다. 처음에는 조금 어렵게 느껴질 수 있지만, 실제 코드에 적용하다 보면 ‘왜 이렇게 짜야 하는지’가 점점 와닿기 시작합니다. 결국 좋은 코드는 ‘잘 돌아가는 코드’가 아니라, ‘변화에 잘 대응할 수 있는 코드’라는 점을 꼭 기억해 주세요. 오늘부터 여러분의 코드가 더 견고해지고, 더 읽기 쉬워지고, 더 확장 가능해지기를 진심으로 응원하겠습니다.

❓자주 묻는 질문(FAQs)

Q1. SOLID 원칙을 처음 배울 때 가장 중요한 순서가 있을까요?
A1. 단일 책임 원칙(SRP)부터 시작하시는 걸 추천드립니다. SRP를 이해하면 나머지 원칙도 점점 체계적으로 정리됩니다.

Q2. SOLID 원칙을 꼭 모든 코드에 다 적용해야 하나요?
A2. 모든 경우에 적용하려 하면 오히려 과도한 설계가 될 수 있습니다. 상황에 맞게 유연하게 적용하는 것이 가장 중요합니다.

Q3. 절차지향 언어에도 SOLID 원칙을 적용할 수 있나요?
A3. 원칙 자체는 객체지향에서 출발했지만, 함수 단위나 모듈 단위로도 응용할 수 있습니다.

Q4. SOLID 원칙을 위반하면 코드가 어떻게 되나요?
A4. 유지보수가 어렵고, 변경에 취약한 코드가 되어 시간이 갈수록 리팩터링 비용이 기하급수적으로 늘어납니다.

Q5. SOLID 외에도 알아두면 좋은 설계 원칙이 있나요?
A5. 네, 예를 들어 DRY(Don’t Repeat Yourself), KISS(Keep It Simple, Stupid), YAGNI(You Aren’t Gonna Need It) 같은 원칙도 함께 공부하시면 좋습니다.

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다