본문 바로가기
개발/Clean Code

[오브젝트] 10장. 상속과 코드 재사용

by 달사쿠 2021. 6. 4.
반응형

상속과 중복 코드

DRY 원칙

모든 지식은 시스템 내에서 단일하고, 애매하지 않고, 정말로 믿을 만한 표현 양식을 가져야 한다.
  • DRY(Don't Repeat Yourself)는 '반복하지 마라'라는 뜻
  • 중복 코드는 변경을 방해
  • 코드를 수정하는 데 필요한 노력을 몇 배로 증가 시킴
  • 중복 여부를 판단하는 기준은 변경
    → 요구사항이 변경 됐을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복

중복과 변경

  • 중복코드는 새로운 중복코드를 부르고, 버그 발생가능성도 높아짐 (아주아주 문제가 많다 이마리야)
  • 민첩하게 변경하기 위해서는 중복코드를 추가하는 대신 제거해야한다.
  • 중복을 제거하는 방법 중 하나: 클래스를 하나로 합쳐 타입코드를 추가해 로직 분기
    단점: 낮은 응집도와 높은 결합도를 갖게됨

 

상속을 이용해서 중복코드 제거하기

상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야 한다. 이것은 자식 클래스의 작성자가 부모 클래스의 구현 방법에 대한 정확한 지식을 가져야 한다는 것을 의미한다. 따라서 상속은 결합도를 높인다고 이야기할 수 있다. (p320)

 

자식 클래스의 메서드 안에서 super 참조를 이용해 부모 클래스의 메서드를 직접 호출할 경우 두 클래스는 강하게 결합된다. super호출을 제거할 수 있는 방법을 찾아 결합도를 제거하라.

 


취약한 기반 클래스 문제

취약한 기반 클래스 문제란?

상속 관계로 연결된 자식 클래스가 부모 클래스의 변경에 취약해지는 현상,
즉 부모클래스의 불필요한 세부사항에 엮이게 되는 현상
  • 캡슐화를 약화하고 결합도를 높인다
     상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만듬
  • 객체 사용의 이유는 구현과 관련된 세부사항을 인터페이스 뒤로 캡슐화하는 것
  • 상속을 사용하면 부모 클래스의 퍼블릭 인터페이스가 아닌 구현을 변경하더라도 자식클래스가 영향받기 쉬워짐
  • 따라서, 상속은 코드의 재사용을 위해 캡슐화의 장점을 희석시키고 구현에 대한 결합도를 높임

 

불필요한 인터페이스 상속 문제

상속받은 부모 클래스의 메서드가 자식 클래스의 내부 구조에 대한 규칙을 깨트릴 수 있다.

 

예시. 자바 초기 버전에서 상속을 잘못 사용한 사례

1. Vector와 Stack의 상속관계

출처: 오브젝트 p324

2. Properties와 Hashtable의 상속관계

출처: 오브젝트 p325

 

불필요한 인터페이스 상속 문제

자식 클래스가 부모 클래스의 메서드를 오버라이딩할 경우,
부모 클래스가 자신의 메서드를 사용하는 방법에 자식 클래스가 결합될 수 있다.

 

조슈아 블로치는 클래스가 상속되기를 원한다면 상속을 위해 클래스를 설계하고 문서화해야 하며, 그렇지 않은 경우에는 상속을 금지시켜야 한다고 주장한다.

블로치는 내부 구현을 문서화하라고 말하고 있다. 객체 지향의 핵심이 구현을 캡슐화하는 것인데 이렇게 내부 구현을 공개하고 문서화 하는 것이 옳은가,,?

설계는 트레이드오프 활동이라는 사실을 기억하라. 상속은 코드 재사용을 위해 캡슐화를 희생한다. 완벽한 캡슐화를 원한다면 코드 재사용을 포기하거나 상속 이외의 다른 방법을 사용해야 한다.

 

부모 클래스와 자식 클래스의 동시 수정 문제

상속을 사용하면 자식 클래스가 부모 클래스의 구현에 강하게 결합되기 때문에,
불필요한 인터페이스를 상속받지 않아도 동시에 수정해야할 수 있다.

 

상속은 기본적으로 부모 클래스의 구현을 재사용한다는 기본전제를 따르기 때문에 자식 클래스가 부모 클래스의 내부에 대해 속속들이 알도록 강요한다.

 


상속으로 인한 피해를 최소화할 수 있는 방법

추상화에 의존하자

부모 클래스와 자식 클래스 모두 추상화에 의존하도록 수정해야 한다.

  • 두 메서드가 유사하게 보인다면 차이점을 메서드로 추출하라. 메서드 추출을 통해 두 메서드를 동일한 형태로 보이도록 만들 수 있다.
  • 부모 클래스의 코드를 하위로 내리지 말고 자식 클래스의 코드를 상위로 올려라. 재사용성과 응집도 측면에서 더 뛰어나다.

 

차이를 메서드로 추출하라

변하는 것으로 부터 변하지 않는 것을 분리해라
변하는 부분을 찾고 이를 캡슐화해라(메서드로 만들어라)

 

변하는 부분을 따로 빼게되면, 변하지 않는 부분들은 완전히 동일해진다. 이 같은 코드를 부모 클래스로 올려라.

 

중복 코드를 부모 클래스로 올려라

자식 클래스들 사이의 공통점을 부모클래스로 옮김으로써
실제 코드를 기반으로 상속 계층을 구성할 수 있다.

  • 공통 코드를 옮길 때 인스턴스 변수보다 메서드를 먼저 이동하는 것이 편하다.
    → 메서드를 다 옮기고 나서 필요한 인스턴스 변수나 메서드가 무엇인지 컴파일 에러를 통해 자동으로 알 수 있기 때문 (약간 꿀팁 느낌)
  • 컴파일 에러를 바탕으로 부모 클래스에 꼭필요한 코드만 이동시킬 수 있다.

 

추상화가 핵심이다

공통 코드를 이동시킨 후 각 클래스는
서로 다른 변경의 이유를 가진다는 것에 주목하라

출처: 리펙토링 p339
  • 낮은 결합도, 높은 응집도를 가지고 있음 (단일 책임 원칙 준수, 추상화 의존, 의존성 역전 원칙 준수, 개방-폐쇄 원칙 준수)
    • AbstractPhone은 전체 통화 목록을 계산하는 방법이 바뀔 경우에만 변경
    • Phone은 일반 요금제의 통화 한 건을 계산하는 방식이 바낄 경우에만 변경
    • NightlyDiscountPhone은 심야 할인 요금제의 통화 한건을 계산하는 방식이 바뀔 경우에만 변경

 

지금까지 살펴본 모든 장점은 클래스들이 추상화에 의존하기 때문에 얻어지는 장점이다.

 

+) 의도를 드러내는 이름으로 변경하기

+) 세금 추가하기

상속으로 인한 클래스 사이의 결합을 피할 수 있는 방법은 없다. 메서드 구현에 대한 결합은 추상 메서드를 추가함으로써 어느정도 완화할 수 있지만 인스턴스 변수에 대한 잠재적인 결합을 제거할 수 있는 방법은 없다. 최대한 상속 계층 전체에 걸쳐 부작용이 퍼지지 않게 막는 방법 뿐

 


차이에 의한 프로그래밍

시간이 흐르고 객체지향에 대한 이해가 깊어지면서 사람들은 코드를 재사용하기 위해 맹목적으로 상속을 사용하는 것이 위험하다는 사실을 깨닫기 시작했다. 상속이 코드 재사용이라는 측면에서 매우 강력한 도구인 것은 사실이지만 강력한 만큼 잘못 사용할 경우에 돌아오는 피해 역시 크다는 사실을 뼈저리게 경험한 것이다. 상속의 오용과 남용은 애플리케이션을 이해하고 확장하기 어렵게 만든다. 정말로 필요한 경우에만 상속을 사용해라.

 

상속은 코드 재사용과 관련된 대부분의 경우에 우아한 해결방법이 아니다. 상속의 단점을 피하면서도 코드를 재사용할 수 있는 더 좋은 방법은 합성이다.

반응형

댓글