SW/면접

유지보수하기 쉬운 소프트웨어 설계 원칙 3가지 (SRP, OCP, DIP) 완벽 정리

얇은생각 2025. 4. 16. 07:30
반응형

서론: 왜 이런 원칙이 필요할까요?

소프트웨어 개발을 하다 보면 이런 순간이 한두 번이 아닙니다. "이 코드 대체 왜 이렇게 꼬였지?", "이거 수정하려면 어디까지 손대야 하는 거야?" 같은 생각, 해보셨죠? 한 번 엉킨 코드 베이스는 풀기 어려워지고, 유지보수가 힘들어지면서 프로젝트가 산으로 갑니다.

그래서 오늘은 더 깔끔하고, 유지보수하기 편한 코드를 작성할 수 있도록 도와줄 세 가지 필수 원칙을 소개하려고 합니다. 바로 단일 책임 원칙(SRP), 개방-폐쇄 원칙(OCP), 그리고 **의존성 역전 원칙(DIP)**입니다. 각각이 무슨 의미인지, 실제 코드에서는 어떻게 적용할 수 있는지 쉽게 풀어볼게요. 자, 그럼 시작해볼까요? 🚀

 

유지보수하기 쉬운 소프트웨어 설계 원칙 3가지 (SRP, OCP, DIP) 완벽 정리

 


 

원칙 1: 단일 책임 원칙(SRP)

SRP가 뭐냐면요...

쉽게 말해, 클래스 하나가 여러 역할을 동시에 하지 말자! 라는 원칙입니다. 기능이 너무 많으면 수정할 때 문제가 생기고, 코드가 금방 난장판이 될 수 있어요.

 

SRP를 안 지키면 벌어지는 일

이 코드 한번 볼까요?

class AuthService:
    def login(self, username, password):
        print("사용자 인증 완료")
        self.log_event("사용자 로그인")

    def log_event(self, event):
        with open("log.txt", "a") as file:
            file.write(event + "\n")

 

인증하는 코드랑 로그 기록하는 코드가 한 곳에 엉켜 있습니다. 나중에 로그 방식이 바뀌면? 인증 코드도 같이 수정해야 하는 불상사가 생길 수 있어요.

 

SRP를 지키면 이렇게 바뀝니다

class Logger:
    def log(self, event):
        with open("log.txt", "a") as file:
            file.write(event + "\n")

class AuthService:
    def __init__(self, logger):
        self.logger = logger

    def login(self, username, password):
        print("사용자 인증 완료")
        self.logger.log("사용자 로그인")

 

이제 인증과 로그 기록이 완전히 분리되었죠? 덕분에 로그인 관련 기능만 수정하고 싶을 때 로그 쪽은 신경 쓰지 않아도 됩니다! 😃

 


 

 원칙 2: 개방-폐쇄 원칙(OCP)

OCP란?

새로운 기능을 추가할 때 기존 코드를 건드리지 않는 것, 바로 이게 개방-폐쇄 원칙입니다.

 

OCP를 안 지키면 벌어지는 일

class DiscountCalculator:
    def calculate(self, price, discount_type):
        if discount_type == "percentage":
            return price * 0.9
        elif discount_type == "fixed":
            return price - 10
        else:
            return price

 

할인 방식이 추가될 때마다 이 코드 수정해야 합니다. 조건문이 늘어나면서 코드가 점점 지저분해지겠죠? 😨

 

OCP를 지키면 이렇게 바뀝니다

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply_discount(self, price):
        pass

class PercentageDiscount(DiscountStrategy):
    def apply_discount(self, price):
        return price * 0.9

class FixedDiscount(DiscountStrategy):
    def apply_discount(self, price):
        return price - 10

class DiscountCalculator:
    def __init__(self, discount_strategy):
        self.discount_strategy = discount_strategy

    def calculate(self, price):
        return self.discount_strategy.apply_discount(price)

 

이제 새로운 할인 정책을 추가할 때 기존 코드를 건드릴 필요가 없어요! 새로운 할인 클래스를 만들어 추가하기만 하면 되니까 훨씬 깔끔합니다. 👍

 


 

원칙 3: 의존성 역전 원칙(DIP)

DIP란?

핵심 기능이 세부 구현에 직접 의존하면 안 된다는 원칙입니다. 쉽게 말하면, 인터페이스를 통해 의존성을 관리하라는 뜻이에요.

 

DIP를 안 지키면 이런 일이 생깁니다

class EmailNotifier:
    def send(self, message):
        print(f"이메일 전송: {message}")

class UserService:
    def __init__(self):
        self.notifier = EmailNotifier()

    def notify_user(self, message):
        self.notifier.send(message)

 

UserService가 EmailNotifier에 직접 의존하고 있어요. 만약 알림 방식을 SMS로 바꾸려면? UserService까지 수정해야 합니다. 비효율적이죠.

 

DIP를 적용하면?

from abc import ABC, abstractmethod

class Notifier(ABC):
    @abstractmethod
    def send(self, message):
        pass

class EmailNotifier(Notifier):
    def send(self, message):
        print(f"이메일 전송: {message}")

class SMSNotifier(Notifier):
    def send(self, message):
        print(f"SMS 전송: {message}")

class UserService:
    def __init__(self, notifier):
        self.notifier = notifier

    def notify_user(self, message):
        self.notifier.send(message)

 

이제 UserService는 Notifier 인터페이스에 의존하기 때문에, EmailNotifier든 SMSNotifier든 쉽게 바꿀 수 있습니다. 확장성도 좋아지고 유지보수도 쉬워지죠! 💡

 


 

결론: 이 원칙들, 왜 중요한 걸까요?

지금까지 SRP, OCP, DIP 원칙을 살펴봤어요. 이 원칙들을 잘 적용하면 코드가 훨씬 깔끔해지고, 유지보수도 쉬워지며, 확장도 편해집니다. 처음에는 귀찮을 수도 있지만, 장기적으로 보면 개발 속도가 빨라지고 코드가 더 탄탄해지는 걸 경험하게 될 거예요.

혹시 지금 코드가 너무 복잡해서 스트레스 받고 있다면, 이 원칙들을 하나씩 적용해 보세요. 결과적으로 더 유연하고 효율적인 소프트웨어 개발이 가능해질 겁니다. 😃

그럼, 즐거운 코딩 하세요! 🚀

반응형