base

개방 폐쇄 원칙(Open Closed Principle, OCP)이란

개발을 하다 보면 기능이 점점 늘어나고 요구사항도 계속 바뀌기 마련입니다. 처음에는 깔끔하고 잘 작성된 코드가 시간이 지나면 점점 복잡해지고, 무언가를 수정할 때마다 연쇄적으로 다른 부분까지 영향을 미쳐서 코드를 건드리는 것이 두려워질 때도 있습니다. 이런 문제를 방지하고 코드를 안정적이고 깔끔하게 유지하기 위한 원칙이 바로 **개방 폐쇄 원칙(Open Closed Principle, OCP)**입니다.


이번 포스팅에서는 SOLID 원칙 중 두 번째 원칙인 OCP에 대해 명확하게 이해하고, 실제로 적용할 수 있는 방법을 예시와 함께 살펴보겠습니다.


개방 폐쇄 원칙(OCP)의 의미와 필요성

개방 폐쇄 원칙(Open Closed Principle, OCP)은 로버트 C. 마틴(Robert C. Martin, Uncle Bob)이 제안한 SOLID 원칙 중 두 번째 원칙으로, 아래와 같이 정의됩니다. "소프트웨어 요소는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다." 조금 쉽게 말하자면, 기존에 잘 작동하는 코드는 그대로 두고, 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있도록 만들어야 한다는 원칙입니다.


OCP를 준수해야 하는 이유

OCP를 잘 지키면 다음과 같은 장점이 있습니다:

  • 유지보수 용이성: 새로운 기능을 추가할 때 기존 코드의 수정 없이 확장만 하면 되므로 유지보수가 쉬워집니다.
  • 버그 예방: 기존 코드의 변경을 최소화하여 안정성을 높이고, 연쇄적인 버그 발생을 줄일 수 있습니다.
  • 확장성 향상: 시스템의 유연성을 높여 기능 추가나 요구사항 변경에 효과적으로 대응할 수 있습니다.
  • 테스트 용이성: 변경하지 않은 기존 코드의 테스트는 그대로 유지할 수 있고, 확장된 부분만 추가로 테스트하면 되기 때문에 전체적인 테스트 비용을 줄일 수 있습니다.

OCP 적용 전후 비교 예시

이제 간단한 예시를 통해 OCP를 적용하기 전과 후를 비교해 보겠습니다.

OCP를 위반한 예시

다음은 결제 시스템에서 OCP를 위반한 코드입니다.

class PaymentProcessor {
  processPayment(paymentType: string, amount: number) {
    if (paymentType === 'credit') {
      // 신용카드 결제 로직
    } else if (paymentType === 'paypal') {
      // 페이팔 결제 로직
    }
    // 새로운 결제 방식이 추가될 때마다 if문을 계속 추가해야 함
  }
}

위 코드의 문제는 새로운 결제 방식이 추가될 때마다 기존 코드에 새로운 조건문이 계속 추가되어야 한다는 것입니다. 결국 코드가 복잡해지고 관리가 어려워지며, 변경에 매우 취약한 구조가 됩니다.

OCP를 적용한 개선된 예시

OCP를 적용하면 다음과 같이 변경할 수 있습니다.

interface PaymentMethod {
  pay(amount: number): void
}
 
class CreditCardPayment implements PaymentMethod {
  pay(amount: number) {
    // 신용카드 결제 로직
  }
}
 
class PayPalPayment implements PaymentMethod {
  pay(amount: number) {
    // 페이팔 결제 로직
  }
}
 
class PaymentProcessor {
  processPayment(paymentMethod: PaymentMethod, amount: number) {
    paymentMethod.pay(amount)
  }
}

이제 새로운 결제 방식을 추가할 때는 새로운 클래스만 추가하면 됩니다.

class KakaoPayPayment implements PaymentMethod {
  pay(amount: number) {
    // 카카오페이 결제 로직
  }
}
 
// 새로운 결제 방식 추가 시 기존 코드는 전혀 수정하지 않음
const processor = new PaymentProcessor()
processor.processPayment(new KakaoPayPayment(), 50000)

이렇게 하면 기존 코드의 변경 없이 손쉽게 시스템을 확장할 수 있으며, 코드 유지보수가 편리해집니다.


OCP를 적용할 때의 주의점

OCP를 적용할 때는 아래와 같은 사항들을 주의해야 합니다:

  • 무조건적인 확장성을 추구하면 코드가 복잡해질 수 있습니다. 필요한 곳에만 적절하게 OCP를 적용하는 것이 중요합니다.
  • 추상화와 인터페이스 설계가 중요합니다. 너무 많은 추상화를 사용하면 코드가 복잡해지고, 유지보수가 힘들어질 수도 있습니다. 적절한 수준에서 추상화가 이루어지도록 신중하게 접근하세요.
  • 확장 가능한 부분과 변경될 가능성이 적은 부분을 명확히 구분하는 것이 중요합니다. 모든 코드를 OCP에 맞추기보다, 변화가 예상되는 부분에 더 집중해서 적용하는 것이 효율적입니다.

정리

지금까지 개방 폐쇄 원칙(OCP)에 대해 살펴본 내용을 다시 한번 요약하면 다음과 같습니다.

  • **개방 폐쇄 원칙(OCP)**은 소프트웨어 컴포넌트가 변경에는 닫혀 있고, 확장에는 열려 있어야 한다는 원칙입니다.
  • OCP를 적용하면 기존의 안정적인 코드는 그대로 유지하면서, 새로운 기능 추가가 용이해지고 유지보수 비용이 감소합니다.
  • 인터페이스나 추상 클래스를 적극적으로 활용하여 확장 가능한 구조를 만들어 줍니다.
  • 다만, 무조건적인 추상화나 확장성은 코드 복잡성을 증가시킬 수 있으므로 적절한 수준에서 적용해야 합니다.

결국 OCP를 지키는 것이 장기적으로는 소프트웨어 품질을 높이고 개발 생산성을 증가시키는 가장 효과적인 방법 중 하나입니다.

참고