effective-swift

item 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

목차


상속을 위한 문서화

클래스를 안전하게 상속할 수 있도록 문서화를 해놓는 것을 권장하고 있습니다. 특히, 재정의 가능 메서드를 호출할 수 있는 모든 상황을 문서로 남겨야합니다.

부작용

문서화할 때 ‘어떻게’ 동작하는지도 설명해야 하는 것이 부작용입니다. 좋은 API 문서는 API가 ‘어떻게’ 동작하는지 아닌 ‘무엇’을 하는지만을 설명해야 하지만, 상속으로 인해 캡슐화를 해치게 되어 내부 구현 방식을 설명해야하기 때문입니다.


Java

자바의 경우 API 문서에서는 Implementation Requirements로 시작하는 절이 있는데, 해당 절은 @implSpec 태그를 붙여주면 자바독 도구가 생성해줍니다.

Javadoc : Java 소스를 문서화 하는 방법, html로 열 수 있습니다.

Swift

스위프트의 경우 JavaDoc처럼 따로 문서를 만들 수는 없지만, Xcode에서 다양한 주석 및 마크다운을 이용해 메서드 상단에 명시함으로써 호출하는 곳에서 해당 메서드의 설명을 팝업으로 볼 수 있게 되어있습니다.


상속을 위한 설계

어떤 메서드를 재정의 할 수 있게 해야하나?

  1. 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 재정의 가능한 메서드를 제공해야 할 수도 있습니다.

    hook(hooking) : 함수 호출, 중간에서 가로챈다고 표현한다.


예제에서는 Java의 removeRange 메서드를 예로 들고있습니다. 해당 메서드를 protected로 제공한 이유는 clear 메서드가 아래에 나와있는 removeRange 메서드를 부르기 때문입니다.

protected void removeRange(int fromIndex, int toIndex) {
    ListIterator<E> it = listIterator(fromIndex);
    for (int i=0, n=toIndex-fromIndex; i<n; i++) {
        it.next();
        it.remove();
    }
}

removeRange메서드 내부의 it.remove()가 O(n)이 걸리게 되면 removeRange는 O(n^2)이 걸리게 됩니다. 따라서 해당 메서드를 호출하는 clear 메서드를 고성능으로 만들기 쉽게 하기 위해 removeRange 메서드를 재정의 할수있도록 제공하는 것입니다.

  1. 상속용 클래스를 설계할때 어떤 메서드를 재정의하도록 노출해야 할지는 직접 하위 클래스를 만들어보면서 테스트(검증) 해보는것이 ‘유일’한 방법입니다.
    • 테스트를 지속해보면서 private하게 만들어야하는 메서드와 재정의 가능하게 해야하는 메서드를 나눌 수 있게 됩니다.

제약사항

상속용 클래스의 생성자는 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안됩니다.

class Super {
    init() {
        overrideMe()
    }
            
    public func overrideMe() { }
}
        
class Sub: Super {
    private var subProperty: String!
            
    init(subProperty: String?) {
        self.subProperty = subProperty
    }
            
    override func overrideMe() {
        print(subProperty.description)
    }
}

let sub = Sub(subProperty: nil) // 런타임 에러 발생! Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
sub.overrideMe()


상속용 클래스가 아닌 일반적인 클래스에 대한 경우

클래스가 final로 선언되지도 않았고, 상속용으로 설계되거나 문서화도 해놓지 않았을 경우입니다. 이러한 클래스는 수정이 생길때마다 하위 클래스가 오동작 할 수 있는 가능성이 있기 때문에 해결할 수 있는 몇가지 방법들이 있습니다.

  1. final 클래스로 만들어 상속을 금지 시킨다.
  2. 모든 생성자를 private으로 선언하고 정적팩터리 메서드를 제공합니다. (아이템 1, 17 참조)

그래도 상속을 해야하는 경우

  1. 내부에서 재정의 가능 메서드를 호출하지 않게 만들고 이를 문서화 합니다.
  2. 재정의 가능 메서드의 내부 로직을 private한 ‘도우미 메서드’로 옮기고, 이 ‘도우미 메서드’를 호출하도록 수정합니다. 이렇게 하면 재정의 가능 메서드를 호출하는 다른 코드들은 ‘도우미 메서드’만을 호출하도록 합니다.


참고한 곳