Skip to content

Posts 의존의 자기 유사성 #
Find similar titles

의존성이란 #

시스템 설계에서 아주 중요한 개념 중 하나로 의존(dependency)이라는 것이 있다. 다양한 정의가 있지만 이 글에서는 간단하게

A는 B가 있어야만 올바르게 작동할 수 있을 때 A가 B에 의존한다(A depends on B)

라고 정의하도록 하겠다. 그림으로는 보통 #!dot/s;A->B 이렇게 그린다.

의존성이 낮은지를 평가할 수 있는 휴리스틱 중 하나는 변경 비용(change cost)이 얼마나 낮은가, 혹은 변경에 따른 여파(ripple effect)가 얼마나 좁은가를 보는 것이다. 쉽게 말해서 코드를 수정할 때 노력이 적게 들면 의존성이 낮다고 짐작할 수 있다.

다양한 종류의 의존성 #

재미있는 점은 의존성에도 다양한 스케일(scale)이 있고, 각 스케일에서 유사한 형태로 반복되며 나타난다는 점이다. 이를 자기유사성(Self-similarity)이라고 한다. Koch curve 같은 Fractal을 떠올리면 좋겠다:

Kuch curve

객체와 패키지 #

요즘은 모두들 "객체, 객체"하는 시대라, 의존성에 이야기를 할 때에도 보통 객체 간의 의존 혹은 좀 더 크게 봐서 패키지 간의 의존을 많이 얘기한다. 의존성 주입(dependency injection), 의존성 역전(Dependency inversion principle 혹은 inversion of control), 안전한 패키지에 의존하기(Stable dependencies principle) 등등.

이 부분은 뻔한 얘기니까 넘어가자.

함수 #

약간 스케일을 낮추면 함수 간의 의존성에 대한 이야기를 해볼 수 있겠다. 함수 사이에 의존성을 만드는 주범은 전역 변수(global variable) 혹은 전역 상태(global state)이다. 함수 외부에 어떠한 상태(variable, state)가 있고, 그 상태는 다른 어떠한 함수에서나 접근이 가능하다(global). 결과적으로 시간이 흐름에 따라, 혹은 초기 설계에서부터 이 전역 변수를 중심으로 여러 함수들이 엮이게 된다. 의존성이라는 단어로 다시 표현하면, 여러 함수들이 전역 변수를 매개로 서로 영향을 주고 받는 상태가 되면서 서로가 서로에 대해 의존성을 갖게 된다.

메서드 #

함수와 전역 변수 사이의 의존관계는 하나의 인스턴스 내부에서도 작은 스케일로 또 출현한다. 여러 인스턴스 메서드들이 인스턴스 변수를 매개로 서로 영향을 주고 받는 상황은 그 스케일만 다를 뿐 의존성이 존재하는 것이다.

서두에서 변경 비용을 기준으로 의존성을 평가할 수 있다고 했는데, 이 경우엔 메서드 이동하기(move method) 리팩토링을 할 때의 비용을 생각해볼 수 있겠다. 메서드가 인스턴스 변수에 의존하고 있지 않으면 그대로 메서드를 빼서 다른 클래스로 옮기면 된다. 변경 비용이 낮다. 하지만 인스턴스 변수에 의존하고 있으면 해당 인스턴스 변수 및 그 인스턴스 변수에 의존하고 있는 다른 메서드들을 면밀히 살펴야 대상 메서드를 옮길 수 있다.

매개변수 #

함수나 메서드 내부로 들어가도 봐도 마찬가지입니다. 아래 의사코드를 보자:

class Foo {
   function bar(a, b) {
      this.func1(a);
      this.func2(b);
      this.func3();
      this.func4();
      this.func5(a,b);
   }
}

bar 메서드의 다섯줄 중 세 줄은 매개변수를 사용하고 있다. 매개변수와 해당 문장들 사이에 의존성이 있는 것이다. 그 결과 변경 비용이 높아진다. 메서드 추출하기(extract method) 리팩토링을 할 때의 비용을 생각해보자. 매개 변수에 의존하고 있지 않은 두 줄을 뽑아낼 때에는 아무 생각 없이 두 줄을 잘라서 새 메서드 본문에 넣고, 원래 위치에 새 메서드 호출 코드를 집어넣으면 된다. 하지만 매개 변수에 의존하고 있는 줄은 그렇지 않다.

지역변수 #

지역변수도 마찬가지다. 지역변수를 중심으로하여 해당 지역변수를 사용하고 있는 다른 문장들 사이에 의존성이 생긴다. 그러한 문장은 쉽게 독립적으로 추출(extract method)될 수 없다.

의존성 낮추기 #

하나의 원칙으로 다양한 스케일의 문제를 설명할 수 있으면 그건 좋은거죠. 왜냐하면 한 놈만 잘 신경써서 잡아도 많은 문제를 해결할 수 있으니까.

객체지향 프로그래밍을 하건 구조적 프로그래밍을 하건 함수형 프로그래밍을 하건 상관없이 한 놈만 패야한다면 주저말고 "의존성"을 잡아보길 권한다. 그러고 보면 온갖 좋은 설계 원칙이라는 것들이 패러다임과 무관하게 일관적으로 추구하는 것이 바로 의존성을 낮추는 것이다.

기왕에 리팩토링 얘기를 꺼냈으니, 리팩토링 책에서 말하는 냄새(code smells) 몇 가지를 의존성이라는 측면에서 다시 검토해보자:

  • 긴 파라메터 목록 - 파라메터가 많으면 메서드 내부의 문장 간 의존성이 높아진다.
  • 거대한 클래스 - 클래스가 거대하면 인스턴스 변수도 많아진다. 반대로, (유틸리티 클래스가 아닌 이상) 인스턴스 변수가 적으면서 클래스가 거대하기는 쉽지 않다.
  • 긴 메서드 - 메서드가 길면 지역 변수도 많아진다. 반대로, 지역변수가 적으면서 메서드가 길기도 쉽지 않다.
  • 임시 필드 - 이름에서 그대로 드러나므로 생략.

의존성을 최대한 낮추는 것은 좋은 설계를 얻기 위한 훌륭한 원칙 중 하나이다.

의존성과 중복 #

그럼 의존성을 어떻게 낮추나? 위에서 설명한 의존성의 다양한 모습들을 살펴서 사살하는 방법도 있겠지만, 다른 방법도 있다. Test Driven Development: By Example에서 켄트 백은 이런 말을 한다:

If dependency is the problem, duplication is the symptom. ... Unlike most problems in life, where eliminating the symptoms only makes the problem pop up elsewhere in worse form, eliminating duplication in programs eliminates dependency.

의존성이 문제라면 중복은 드러나는 징후다. ... 문제 자체는 남겨둔 채로 징후만을 제거하면 다른 어딘가에서 최악의 형태로 문제가 드러나곤 하는 현실 세계의 일반적인 양상과는 달리, 프로그램에서는 중복만 제거해주면 의존성도 제거된다.

즉, 문제(의존성)를 직접 공략하는 법 말고 문제의 징후(중복)을 공략하는 것도 방법이라는 것이다. TDD의 두 가지 설계 원칙 - 중복 제거, 의도 드러내기 - 은 여기에서 기인하는 것이다.

여담 #

요 몇 년 사이 기획일을 주로 하다보니 기획자의 관점에서도 가끔 생각을 해보는데, 의존성의 자기유사성은 클래스와 패키지 범위를 넘어서 기획 단계까지도 이어질 수 있다고 생각한다. 확장성 있는 서비스, 변화에 대처할 수 있는 서비스를 기획하려면 이 개념을 잘 활용할 필요가 있겠다고 생각한다.

시스템 분석/설계 능력은 기획자가 지녀야 할 지식의 본체(body of knowledge)를 이루는 커다란 한 축이라고 생각한다. 또 다른 중요한 축으로는 시스템이 아닌 "인간에 대한 이해" 같은 것이 있을 수 있겠다. 이 사이에 겹치는 부분도 있을 것이고.

Suggested Pages #

Other Posts #

0.0.1_20140628_0