LSP

안녕하세요 Jercy입니다. 오늘은 Swift에서의 LSP(Liskov Substitution Principle, 리스코프 치환 원칙)에 대해 알아보겠습니다.
LSP란 "하위 형식은 그것의 기반 형식에 대해 대체 가능해야 한다"는 객체지향 설계 원칙 중 하나로, 1988년 바바라 리스코프가 그의 논문에서 처음 소개한 개념입니다. 쉽게 말해, 자료형 S가 자료형 T의 하위 형식이라면, 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체할 수 있어야 함을 의미합니다.
예를 들어, 직사각형과 정사각형의 관계를 생각해 봅시다. 일반적으로 정사각형은 직사각형의 일종이라고 여깁니다. 그렇다면 상속 관계를 어떻게 설계해야 할까요? 직사각형을 상속받은 정사각형을 만드는 것이 직관적으로 맞아 보입니다.
그러나 문제가 있습니다. 만약 직사각형에 width와 height를 별도로 설정하는 메서드가 있다면, 정사각형은 이를 오버라이드하여 width와 height가 항상 같도록 구현해야 합니다. 이는 기반 클래스(직사각형)의 동작 방식을 하위 클래스(정사각형)가 변경하는 것이므로 LSP에 위배됩니다.
만약 LSP를 지키지 않은 채로 정사각형 클래스를 만들면, 직사각형 타입을 사용하는 코드에서 정사각형 객체를 사용할 때 예상치 못한 결과가 발생할 수 있습니다. 예컨대 직사각형의 크기를 변경하는 코드는 정사각형 객체에서 제대로 동작하지 않을 것입니다.
이처럼 LSP는 상속을 설계할 때 매우 중요하게 고려되어야 할 원칙입니다. LSP를 어기는 잘못된 상속은 코드를 복잡하고 이해하기 어렵게 만듭니다. 하위 클래스가 기반 클래스의 규약을 제대로 지키지 못하면 다형성을 해치고, 기반 클래스를 사용하는 클라이언트 코드에 나쁜 영향을 미치게 됩니다.
물론 현실적으로 모든 코드에서 LSP를 완벽히 지키는 것은 어렵습니다. 하지만 LSP의 개념을 이해하고 상속 관계를 설계할 때 염두에 둔다면 보다 유연하고 확장성 있는 코드를 작성하는 데 큰 도움이 될 것입니다.
LSP를 잘 지키는 예시로는 스위프트의 프로토콜과 익스텐션을 들 수 있습니다. 프로토콜은 인터페이스를 추상화하고, 익스텐션을 통해 프로토콜을 준수하는 모든 타입에 대해 공통의 기본 구현을 제공할 수 있죠. 이는 타입 계층과 무관하게 다형성을 실현하는 훌륭한 방법으로, LSP 정신에 부합합니다.
이처럼 LSP는 클래스 상속뿐 아니라 프로토콜 설계에도 응용될 수 있는 중요한 원칙입니다. 상속과 프로토콜은 코드 재사용과 다형성을 위한 핵심 도구이므로, LSP에 대한 이해는 좋은 객체지향 설계를 위해 반드시 필요합니다.
그런데 한 가지 주의할 점은 LSP를 맹목적으로 적용하다 보면 오히려 과도한 추상화로 인해 코드가 복잡해질 수 있다는 것입니다. 따라서 상속이 정말 필요한지, 하위 클래스가 기반 클래스의 규약을 온전히 지킬 수 있을지 면밀히 검토한 후에 상속을 사용해야 합니다.
이상으로 Swift에서의 LSP에 대해 알아보았습니다. SOLID 원칙 중에서도 LSP는 상속과 관련된 설계에 있어 매우 중요한 지침이 되는 원칙이라 할 수 있겠네요.
이 내용에 대해 몇 가지 생각해볼 점을 제시하면:
1.
우리가 작성한 코드에서 LSP를 위반하는 사례는 없었나요? 있다면 어떤 문제를 초래할 수 있을까요?
2.
프로토콜과 익스텐션을 활용할 때 LSP를 염두에 둔다면 어떤 장점이 있을까요?
3.
반대로 LSP를 과도하게 적용하려다 보니 코드가 오히려 난해해진 경험은 없나요?
이에 대한 제 생각은 다음과 같습니다:
1.
실제 프로젝트에서 LSP를 위반하는 상속 관계를 발견하고 이를 리팩토링 했던 경험이 있습니다. LSP 위반은 예상치 못한 버그를 낳고 코드 유지보수를 어렵게 하므로 경계해야 합니다.
2.
프로토콜은 타입의 계층과 무관하게 행위를 추상화하고 재사용할 수 있게 해주므로 LSP와 잘 어울립니다. 익스텐션으로 기본 구현을 제공할 때도 LSP를 만족시키면 solidity 있는 설계가 가능할 것 같습니다.
3.
테스트 코드를 작성하던 중 모든 테스트 케이스에서 공통으로 수행하는 로직을 슈퍼클래스로 옮기려다 보니 오히려 테스트 코드가 더 복잡해지고 각 케이스의 독립성이 훼손되었던 적이 있습니다. LSP를 맹신하기보다는 구체적인 상황에 맞게 원칙을 적용하는 지혜가 필요하다고 느꼈습니다.
여러분들의 경험과 생각도 궁금하네요. 댓글로 의견을 남겨주시면 함께 배우고 성장하는 기회가 될 것 같습니다. 감사합니다!