[A Philosophy of Software Design] chapter 7. Different Layer, Different Abstraction

4 minute read

7. Different Layer, Different Abstraction

소스트웨어 시스템은 낮은 레이어가 제공해주는 기능을 사용하는 높은 레이어들로 구성되었다.

잘 구성된 시스템은 각 레이어들이 각자 다른 추상과를 가지고 있다. 만약 어떤 오퍼레이션에 대해서 함수 호출 관계를 보게 된다면 추상화는 각 메소드 콜마다 달라질 것이다.

예를 들면, 파일 시스템에 가장 높은 레이어는 파일 추상과를 구현한다. 그 이후로는 메모리 캐쉬 영역에 대해서 구현할 것이며, 그 다음으로 세컨더리 스토리지와 메모리를 연결시켜주는 디스크 드라이버에 대해서 구현할 것이다.

만약 시스템이 비슷한 추상화와 함께 인접한 레이어가 포함된다면 이것은 클래스 분해와 관련해서 문제가 있다고 할 수 있다. 우리는 이번 챕터에서 이것에 대해서 이야기해볼 것이다.

7.1 Pass-through methods

Pass-through methods: 비슷한 추상 레이어에서 비슷한 역할을 하는 것 처럼 보이지만 실제로는 거의 불리지 않거나 불려도 그것을 바로 다른 객체로 책임을 전가하는 메소드

* Red Flag: Pass-Through Method * 

A pass-through method is one that does nothing except pass its arguments to another method, usually with the same API as the pass-through method. This typically indicates that there is not a clean division of responsibility between the classes.

Pass-through methods는 클래스에 대한 복잡성은 즐하지만 시스템 기능에 대한 증가는 없다. 그 뿐만 아니라 의존성이 있기 때문에 뒤에 의존성이 있는 객체가 변경하게 되면 그것에 대한 영향을 그래도 받는다.

그 뿐만 아니라 클래스 사이에 책임 분산에 대한 혼란을 줄 수 있습니다. 어떤 클래스 A가 인터페이스를 제공해주고 B에 모두 구현이 있다면 이것은 그렇게 좋은 방법은 아닙니다. 기능을 제공해주는 인터페이스와 구현을 제공하는 클래스는 동일한 것이 좋습니다. 만약 이런 경우가 발생한다면 정확하게 어떤 책임을 가지고 있는 클래스 사이에서 찾아보십시오.

해결책으로 다양한 방법이 있습니다. 첫번째로는 추상화 레벨에 따라 클래스를 분리하여서 그것에 대한 객임만을 제공하는 것입니다. 두번째는 추상화와 별개로 각자 기능에 대해서 분리하는 것입니다. 세번째는 그냥 모두 하나의 클래스에서 처리하는 것입니다.

7.2 When is interface duplication OK?

같은 시그니처를 가지는 메소드는 항상 나쁜 것은 아니다. 중요한 것은 하나의 새로운 메소드가 새로운 기능에 대해서 기여한다는 것이다. Pass-through methods는 새로운 기능에 대해서 기여하지 않는다.

같은 시그니처로 같은 메소드를 사용하면서 유용하게 사용되는 메소드는 dispatcher이다. dispatcher는 알규먼트를 활용해서 특정 메소드를 호출한다. 가끔 대부분이나 모든 알규먼트를 해당 메소드에게 전달한다. 해당 dispatcher가 사용하는 시스니처가 호출하는 시그니처와 종종 같은 경우가 있다. 그럴더라도 dispatcher는 각 테스크로 여러 메소드들이 수행할 수 있도록 하는 기능을 제공해 준다.

예를 들고 웹 서버가 HTTP request를 받았을때 dispatcher를 들어온 request에 URL을 검사하기 위해서 호출한다. 그것을 통해 해당 리퀘스트를 처리하는 특정 메소드를 찾기 위해서이다.

같은 시그니처를 가지고 있는 메소드라도 각자 다른 독자적인 기능을 제공하는 경우 괜찮다. 다른 예로는 하나의 인터페이스에 여러 구현이 있는 경우이다. Like disk driver in an operating system. 여러 디스크에 대해서 똑같은 인터페이스를 제공하지만 그들은 모두 다른 disk에 대해서 지원하게 해준다. 이것을 통해서 인지 부하를 줄일 수 있고 새로운 인터페이스를 배우는데 있어서 일을 더 쉽게 할 수가 있다.

7.3 Decorators

decorator design pattern는 레이어 간에 API 중복을 만들어냅니다. decorator는 존재하는 기능에 확장하여 기능을 붙입니다. 예를 들면 Java I/O에서 BufferedInputStreamdecorator입니다. InputStream에 실제 내부는 BufferedInputStream는 인 경우에 InputStream를 통해서 read를 하는 경우 실제로는 하나의 캐릭터가 아닌 많은 캐릭터를 저장하게 됩니다.

decorator design pattern의 동기는 특별한 핵심 기능 클래스에 대한 특별한 기능을 분리하는 것입니다. 하지만 decorator를 위해서 작은 새로운 기능을 위해서 많은 양의 보일러플레이트를 만들어야합니다. 뿐만 아니라 decorator 클래스는 pass-through methoud를 얻게 됩니다.

그러기 때문에 한번 decorator 클래스를 만들기 전에 해당 내용을 생각해보세요.

  • 직접 내부(원본) 클래스에 새로운 기능을 추가하는 것은 어떤가요? 이것은 이해를 쉽게 할 것이예요. 만약 해당 기능이 일반적인 목적과 연관이 있거나 로직적으로 내부 클래스와 연관이 있거나 대부분 해당 기능을 내부에서 사용한다면 말이다.
  • 만약 특별한 유즈 케이스에 대해서 새로운 기능이 특별해졌다면 해당 기능을 유즈 케이스와 합치는 것은 어떤가요?
  • 기존에 있는 decorator와 합치는 것이 아닌 새로운 decorator를 만드는 것은 어떤가요?
  • 해당 구현을 정말해 해야되겠다면 해당 클래스는 기존(base) 클래스와 완전히 독립적으로 구현할 수 있습니까?

7.4 Interface vs implementation

Another application of the “different layer, different abstraction” rule is that the interface of a class should normally be different from its implementation: the representations used internally should be different from the abstractions that appear in the interface. If the two have similar abstractions, then the class probably isn’t very deep.

7.5 Pass-through variables

또 다른 형태에 여러 레이어를 걸쳐서 존재하는 API 중복은 pass-through variable이다. 간단하게 말을 하면 지속적으로 해당 메소드가 다른 밑에 있는 클래스까지 사용하게 되는 경우 메소드나 여러 경우를 통해서 다른 lower에 있는 메소드까지 전달되어야 한다. 하지만 해당 이슈는 전혀 사용되지 않은 메소드들도 해당 변수를 지속적으로 넘겨야되는 문제가 있다.

  1. 지속적으로 메소드 아큐먼트로 넘긴다.
    • 말처럼 해당 변수를 지속적으로 아큐먼트로 넘긴다.
    • 꼴뵈기 싫고 만약 변경되는 경우 모든 메소드들이 모두 변경되어야되는 큰 이슈다.
  2. shared object를 사용해서 넘긴다.
    • 기존에 해당 main -> m3 까지 넘겨졌던 object에 해당 변수를 추가해서 넘긴다.
    • 하지만 이것 또한 문제인게 그렇다는 것은 해당 object는 벌써 pass-through variable이 아닌가?
  3. global variable을 사용해서 넘긴다.
    • 1번과 2번에서 말했던 방식과 다르게 아큐먼트로 넘기지 않아도 된다.
    • 하지만 글로벌 변수를 사용하는데 있어서 문제는 존재한다. 바로 똑같은 시스템해서 해당 변수를 또 만들지 못한다는 것이다.
  4. context object를 시용해서 넘긴다.
    • application의 글로벌 상태를 아는 곳에 해당 변수를 추가한다.
    • 예를 들면 configuration option, shared subsystem information과 같은 것들이다.
    • 하지만 이것 또한 글로벌 변수를 사용하는 것과 비슷한 이슈가 있다.
    • 해당 변수가 왜 존재하는지나 어디서 사용하지는 명확하지 않은 문제, 동기화 문제, 잘못 사용하게 되는 경우 거대한 의존성 덩어리가 됨.

7.6 Conclusion

개발자들은 새로운 인프라 요소들에 대해서 배워야 됩니다. 만약 특정 요소가 복잡성이 존재한다면 그것에 대한 복잡성을 제거하십시오. 혹은 특정 요소 없이도 구현할 수 있도록 만드십시오. 예를 들어 캡슐화를 통해서 해당 내부를 알지 못해도 구현할 수 있는 것처럼.

different layer, different abstraction 룰은 해당 아이디어 이다. 만약 다른 레이어에 같은 추상화가 있다면 pass-through methods나 decorators 와 같이 그들은 충분한 혜택을 주지 못할 가능성이 있다. 비슷하게 pass-through arguments는 특별히 도움이 되지도 않으면 여러 메소드에서 그들의 존재를 알아야 되도록 만든다.

느낀 점

사실 조금 더 레이어나 추상화에 대해서 설명을 해주고 무엇이 조금 더 명확하게 다른 레이어에 느낌인지 말을 해줬으면 좋았을 것 같은데… 그런 부분이 없어서 아쉽다. 요즘 개발을 진행하면서 같은 레이어에 대해서 충분히 생각하지만 어려운 문제라서 그런 부분에 대해서 이야기 없어서 아쉬웠다.

pass-through methods와 관련해서 나도 경험한 적이 있다. 단순히 레이어를 만들기 위해서. 미래에 사용될 가능성에 대한 클래스를 만들었는데 해당 부분은 나중에 분명히 인지 부하를 줄 수 있을 것이라는 생각을 들게 했다.

Leave a comment