피드로 돌아가기
원문 읽기
LINE Engineering
Backend코드 품질 개선 기법 28편: 제약 조건에도 상속세가 발생한다
LY Corporation이 상속 가능한 불변 클래스 설계의 위험성을 분석해 제약 조건 검증 체크리스트 도입
AI 요약
Context
Kotlin에서 IntArray를 래핑한 ImmutableIntList 클래스를 상속 가능하게 설계했을 때, 자식 클래스에서 get() 메서드를 오버라이딩하거나 set() 메서드를 추가해 불변성을 깨뜨릴 수 있다. 자식 클래스의 MutableIntList가 부모 클래스의 ImmutableIntList 타입으로 업캐스팅되어도 실제로는 가변 상태를 유지할 수 있다.
Technical Solution
- 불변 클래스를 상속 불가능하게 선언: open 키워드를 제거하고 final로 명시해 예상치 못한 패키지에서의 상속 방지
- protected 멤버 접근 제한: valueArray를 private으로 설정하되, 자식 클래스가 오버라이딩 가능한 메서드 목록을 명시적으로 통제
- 가변/불변 객체의 상속 관계 재설계: 가변 객체와 불변 객체가 직접 상속 관계를 가지지 않도록 구조화
- 읽기 전용 부모 클래스 도입: 공통 기능이 필요한 경우 읽기 전용(read-only) 인터페이스를 중간 계층으로 삽입해 Kotlin의 List처럼 get() 메서드만 노출
- 제약 조건 명시 검증: 부모 클래스의 제약 조건(불변성, 정렬 상태 등)이 자식 클래스의 제약 조건 부분집합인지 아키텍처 리뷰 단계에서 검증
Key Takeaway
불변성을 보장해야 하는 클래스는 final로 선언해 상속을 원천 차단하고, 만약 상속이 필요하면 변경 가능한 메서드가 없는 읽기 전용 인터페이스를 부모로 설계해야 한다. 범용적인 이름의 기본 클래스는 자신이 제공하는 제약 조건(불변성)이 자식 클래스에 의해 체계적으로 위반될 가능성이 높으므로 상속 가능성 자체를 설계 단계에서 제거해야 한다.
실천 포인트
Kotlin/Java에서 불변 컬렉션 또는 불변 값 객체를 설계할 때 final 선언을 기본값으로 하고, 상속이 반드시 필요한 경우에만 명시적으로 open 또는 abstract로 변경하면서 오버라이딩 가능한 메서드를 제한하면 런타임에 예상치 못한 타입 위반 버그를 방지할 수 있다.