대기업 레거시 청산 TF팀이 임원 보고용으로 만든 '상속(Inheritance) 폐기' 기술 문서

대기업 레거시 청산 TF팀이 임원 보고용으로 만든 '상속(Inheritance) 폐기' 기술 문서

Poooling·2026년 1월 7일·3

8년 차 개발자가 대기업 레거시 시스템을 겪으며 깨달은 상속의 위험성과 Composition 기반 리팩토링 전략을 공유합니다.

솔직히 고백하겠습니다.

스타트업에서 '야생형 개발자'로 구르던 시절, 저는 상속(Inheritance) 예찬론자였습니다.

BaseController 하나 잘 만들어두고, 모든 컨트롤러가 이걸 상속받게 하면 코드가 획기적으로 줄어든다고 믿었거든요.

그때는 그게 '객체지향의 정수'인 줄 알았습니다.

하지만 8년 차가 되어 네임드 기업으로 이직하고, 10년 묵은 거대한 레거시 시스템을 마주한 순간 제 믿음은 산산조각 났습니다.

"부모 클래스 하나 고쳤는데, 왜 결제 모듈이 뻗어버리죠?"

이직 후 첫 배포 날, 제가 식은땀을 흘리며 팀장님께 했던 말입니다.

문제의 원인은 무려 7단계 깊이의 상속 구조였습니다.

PaymentControllerBaseTransaction을 상속받고, 그게 또 AbstractCommonModule을 상속받는 식이었죠.

가장 위쪽 부모 클래스에 있는 protected 필드 하나를 건드렸더니, 그 아래 수백 개의 자식 클래스가 도미노처럼 무너졌습니다.

그날 밤, 저는 뼈저리게 느꼈습니다.

우리가 학교에서 배운 "개는 동물이다(Dog is an Animal)" 같은 예제는 현실 개발과 아무런 관련이 없다는 것을요.

도대체 왜 이런 일이 벌어지는 걸까요?

최근 Rust 생태계를 공부하며 읽은 기술 아티클에서 그 명쾌한 해답을 찾았습니다.

Rust는 C++이나 Java와 달리 상속이라는 개념 자체를 언어 차원에서 제거했습니다.

이유는 간단합니다.

상속은 캡슐화(Encapsulation)를 깨뜨리고, 결합도(Coupling)를 불필요하게 높이기 때문입니다.

우리는 흔히 상속을 "Is-a 관계"(A는 B이다)를 표현한다고 배웁니다.

Circle(원)은 Shape(도형)이니까 상속받는 게 자연스러워 보이죠.

하지만 컴퓨터의 관점, 즉 메모리 레이아웃(Record Type) 관점에서 상속은 전혀 다른 의미를 가집니다.

class Circle : public Shape { ... }

이 코드는 사실 아래 코드와 다를 게 없습니다.

class Circle {
    public:
    Shape shape; // 이름 없는 필드로 숨겨져 있을 뿐
    ...
}

상속은 부모 클래스를 자식 클래스 안에 '이름 없는 필드'로 강제로 집어넣는 문법적 설탕(Syntactic Sugar)일 뿐입니다.

문제는 이 필드가 숨겨져 있다는 겁니다.

명시적으로 circle.shape.color라고 접근하는 것과, 상속받아서 circle.color로 접근하는 것은 가독성과 유지보수성에서 천지 차이입니다.

전자는 구성(Composition, Has-a)이고, 후자는 상속입니다.

상속을 쓰면 부모와 자식이 한 몸처럼 묶여버립니다.

부모의 변경이 자식에게 즉각적인 영향을 미치죠.

게다가 다중 상속 문제나 다이아몬드 문제 같은 복잡성까지 끌고 들어옵니다.

그래서 모던 백엔드 개발, 특히 MSA(Microservices Architecture) 환경에서는 상속보다는 조합(Composition)인터페이스(Interface/Trait)를 선호합니다.

제가 속한 팀에서는 최근 리팩토링 가이드라인을 이렇게 세웠습니다.

1. `extends` 금지, `implements` 권장.

기능을 물려받으려 하지 말고, 규약(Interface)만 따르십시오.

2. 상속 대신 '필드'로 주입하라.

UserAuth 기능을 써야 한다면, User extends Auth가 아니라 User 클래스 안에 AuthService를 필드로 가지게 하십시오.

3. AI를 활용해 계층을 평탄화하라.

저는 최근 Cursor 에디터나 Claude 같은 AI 도구를 적극 활용합니다.

복잡하게 꼬인 상속 코드를 드래그하고 이렇게 프롬프트를 날립니다.

"이 상속 구조를 Composition 패턴으로 리팩토링해주고, 의존성 주입(DI) 형태로 바꿔줘."

AI는 기계적으로 코드를 분리하는 데 탁월합니다.

사람이 일일이 떼어내다 실수할 수 있는 부분을 꽤 정확하게 잡아줍니다.

물론, 상속이 100% 악(Evil)은 아닙니다.

하지만 비즈니스 로직이 수시로 변하는 스타트업이나, 규모가 거대한 엔터프라이즈 환경에서 상속은 득보다 실이 큽니다.

우리는 코드를 짤 때 "이게 무엇인가(Is-a)"에 집착하기보다, "이게 무엇을 하는가(Has-a / Doable)"에 집중해야 합니다.

Rust가 상속을 버리고 Trait(특성) 시스템을 택한 것도 바로 이 때문입니다.

후배님들, 만약 지금 짜고 있는 코드에 extends를 적으려 한다면 잠시 멈추세요.

그리고 스스로에게 물어보세요.

"내가 지금 게으름을 피우기 위해 미래의 나에게 기술 부채를 떠넘기고 있는 건 아닐까?"

단단한 코드는 화려한 계층 구조가 아니라, 독립적이고 단순한 부품들의 조립에서 나옵니다.

Poooling
PooolingAuthor

Poooling님의 다른 글

댓글 0

첫 번째 댓글을 남겨보세요!