스택큐힙리스트

“바꿔 끼워도 그대로 동작” ― LSP(리스코프 치환 원칙) 핵심 가이드 본문

개발

“바꿔 끼워도 그대로 동작” ― LSP(리스코프 치환 원칙) 핵심 가이드

스택큐힙리스트 2025. 7. 13. 13:36
반응형

하위 타입은 언제든 상위 타입을 대체할 수 있어야 한다.”

1988년 바버라 리스코프가 제시한 이 정의가 바로 Liskov Substitution Principle(LSP)입니다. 상속·다형성이 진짜 힘을 발휘하려면 LSP가 지켜져야 하죠. 오늘은 현업 코틀린 예제와 국내 인기 블로그 사례를 묶어, “교체 가능한 설계”를 만드는 방법을 빠르게 살펴봅니다.


1. LSP란 무엇인가?

  • 정의
    상위 타입을 기대하는 모든 곳에서 하위 타입을 넣어도 프로그램의 행위가 변하지 않아야 한다. 요컨대 “하위 타입이 상위 타입의 규약(계약)을 깨지 말라”는 약속입니다.
  • 왜 중요한가?
    1. 예측 가능한 코드 ― 호출자가 “서프라이즈”를 안 당함
    2. 리팩터링 자유도 ― 구현 교체해도 테스트가 그대로 통과
    3. 재사용성 UP ― 라이브러리·플러그인 아키텍처에 필수

2. LSP가 깨지는 흔한 징후

  • Override로 기능 제한: 상위 메서드를 하위 클래스가 “사용 불가”로 만들 때
  • 사전·사후조건 변경: 메서드 입력 범위를 좁히거나, 예외를 추가할 때
  • ‘is-a’ 오해: 개념상 틀린 상속(예: Square extends Rectangle)

이런 코드를 방치하면 다형성이 무용지물이 됩니다.


3. Kotlin 예제로 보는 Before ↔ After

❌ Before ― LSP 위반

open class Bird {
    open fun fly() { /* ... */ }
}

class Ostrich : Bird() {
    override fun fly() = error("I can't fly!") // 상위 계약 파괴
}

Bird를 쓰는 호출자는 fly()가 항상 성공한다고 믿는데, Ostrich가 등장하며 폭발합니다.

✅ After ― 서브타입 분리

interface Flyable { fun fly() }

open class Bird

class Eagle : Bird(), Flyable {
    override fun fly() { /* ... */ }
}

class Ostrich : Bird() // Flyable 아님
 

이제 ‘날 수 있음’(행동)을 별도 인터페이스로 분리해 계약을 지켰습니다. DevForYou


4. 실무 적용 4-Step

  1. 계약 명시: Javadoc·KDoc에 “입력 범위·예외·상태 변화”를 써두면 위반이 눈에 띕니다.
  2. 행동 기반 인터페이스: “능력” 단위(Flyable, Drawable)로 설계해 상속 오용을 차단.
  3. 테스트 더블 활용: 상위 타입 Mock으로 단위 테스트를 짜면, 하위 타입 치환 가능 여부가 즉시 드러납니다.
  4. 정적 분석 도구: Kotlin Detekt ForbiddenOverride 규칙, SonarQube 상속 검사로 자동 감시.

5. 오늘 당장 실천해 볼 것

  1. 프로젝트에서 override fun 중 예외를 던지거나 파라미터 범위를 바꾼 메서드를 검색.
  2. 위 메서드가 상위 계약을 위반한다면, 행동 인터페이스 분리 혹은 구성(Composition) 으로 수정.
  3. CI에 계약 테스트(contract test) 를 추가해 치환 가능성을 지속 검증.
반응형
Comments