레이블이 Top-down인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Top-down인 게시물을 표시합니다. 모든 게시물 표시

2010년 4월 8일 목요일

Bottom-up/Top-down 설계

 

'신의 한줄'을 위한 생각 - Bottom-up/Top-down

 

소프트웨어 개발자의 세계에 있어서,

설계와 코딩은 서로 떼놓을수 없는 정말 중요한 작업들입니다.

좋은 설계는 코딩을 편하게 하고, 좋은 코딩은 설계가 유연해지도록 돕습니다.

하지만 다들 코딩은 어떻게든 하겠는데, 설계는 어떻게 해야 할지 몰라서 헤매는 경우가 많습니다.

시중에 나온 책들도 코딩을 잘하는 방법(테크닉)을 가르쳐 줄뿐이지, "설계는 이것이다" 라고

딱히 이야기해주지 않습니다. 그나마 전산학과에선 설계를 가르친다고는 하나, 이들 학교에서

배우는 내용도 매우 두리뭉실할 뿐, 현실에서는 거의 적용하기 힘든때가 많습니다.

 

그렇다면 우리는 설계를 어떻게 시작해야 좋을까요?

저뿐만 아니라 많은 개발자들이 여기에 대해서 수없이 고민해 왔습니다.

그리고 각자 자신만의 결론에 도달하게 되죠, 그것이 옳던 아니던 간에.

(사실 맞고 틀린것은 없습니다, 자기가 좋아하는 방법이 최선인 것입니다.)

 

제가 생각하는 설계는 2 가지가 있습니다.

(여기선 이전단계인 요구사항 분석이 끝났다고 칩시다.)

 

하나는 정말 설계다운 Top-Down방식

다른 하나는 부지런한 리팩토링에 따른 Bottom-Up 방식

 

참고로 아래 예시를 설명함에 있어,

저는 구현과 설계를 병행한다는 것을 고려하시기 바랍니다.

 

 

Top-Down방식

 

Top-Down방식은 두리뭉실한 것을 구체적으로 만들어 나가는 것입니다.

이전에 완료된 요구사항중 공통되는 핵심 기능만을 우선적으로 객체로 모델링을 합니다.

여기서는 개발자라면 많이 사용하는 버튼, 콤보박스, 리스트 등등의 UI Control이

어떻게 설계될수 있는가에 대해서 이야기하도록 하겠습니다.

 

우선 "화면에 보여야 한다"라는 요구사항을 만족시키기 위해서 Visual 이라는 객체를 하나 만들었습니다.

이 Visual은 Draw() 라는 메소드가 있어, 스스로 화면에 그려질수 있습니다.

 

 

 

그리고 여기서 "마우스로 조작할수 있어야 한다." 라는 요구사항을 만족시키기 위해서 아래와 같이

Control 이라는 객체를 만들었습니다.

 

마우스로 조작하려면, 화면에 보여야 하기 때문에 이 Control은 Visual의 특성을 가져야 하므로,

상속을 이용하여 다음과 같이 표현합니다.

 

여기서 좀더 구체적인 요구사항 "마우스로 클릭하면 실행되어야 한다." 를 만족시키기 위해서

Button을 만들었습니다.

 

 

Button은 역시 마우스로 조작되어야 한다는 앞의 요구사항을 만족해야 하기 때문에 Control의

특성을 지녀야 하므로, Control을 상속 받습니다.

그런데 Button으로 만족되지 않는 다른 요구사항 : "클릭하면 다시 클릭될 때까지 눌린 상태(체크)를

유지해야 한다." 가 있습니다. 버튼은 클릭하면 뭘 수행하는 기능만 있기 때문에, 이때쯤 새로운

객체를 하나 더 디자인 합니다. 이를 CheckBox 라 하겠습니다. 역시 화면에 보이고, 마우스로

조작되어야 하기 때문에 Control을 상속 받습니다.

 

여기에 또 다른 요구사항 : "스크롤 해서 항목을 선택할수 있어야 한다." 가 있습니다.

이는 Button, CheckBox 모두와 서로 안맞는 요구사항이므로, Button이나 CheckBox를 상속받는

행위를 하면 안됩니다.(서로 맞는지 아닌지 두리뭉실해서 확신이 안서면 문제에 대한 이해나

요구사항 분석이 잘못된것입니다. 좀더 자세히 해놓을 필요가 있습니다.) 보통 초반에 잘 설계해놓고

마구 상속하고 이게 감당이 안되서 overriding 하여 설계를 망치는 경우가 많습니다. 조심해야 합니다.

 

이러한 실수를 막고자 상속은 3단계 이상 하지말것,

다중 상속 금지 등의 "경험적인 Rule"이 생겨난 것입니다.

 

결국, Button과 CheckBox 모두와도 서로 요구사항이 맞지 않지만, Control이 만족하는 요구사항

"화면에 그려지고, 마우스로 조작될수 있다."라는 조건을 맞추기 위해서 ListBox라는 객체를 만들어

Control을 상속받습니다.

 

이렇게 하여 하나의 Tree 모양의 설계가 일단 완료되었습니다.

더 복잡하게 하면 머리아프고 쓰기도 힘드니, 여기서 더 자세히 나가지는 않겠습니다.

 

이 방식의 경우 추상적인 곳에서 구체적인 곳으로 나아가면서 설계가 이루어지기 때문에

Top-Down = Bottom까지 내려가야 실제 사용할수 있는 구체적인 컴포넌트가 나타나게 됩니다.

 

개발자 입장에서는 차근차근 뭔가 두리뭉실한게 명확해진다, 뭔가 이루어진다는 느낌이지만,

빨리 눈에 보이거나 뭔가 나오는게 없어 성격급한 관리자들이 싫어하는? 방법이기도 합니다.

 

 

 

Bottom-up방식

 

이번엔 Bottom-up 방식의 설계 기법을 보여 드리겠습니다.

역시 위와 마찬가지로 UI Framework 구조를 만들어 나가는 과정입니다.

 

일단 Bottom-up에서는 가장 구체적인 요구사항을 모두 만족하는 객체를 먼저 만듧니다.

(일단 닥치고 만듭니다.)

 

- Button의 경우    

  • 화면에보이고,
  • 마우스로 조작 가능하며
  • 클릭할수 있다

    는 요구사항을 모두 구현합니다.

 

- CheckBox의 경우

  • 화면에보이고,
  • 마우스로 조작 가능하며,
  • 클릭하면 다시 클릭될 때까지 눌린 상태(체크)를 유지해야 한다.

    는 요구사항을 모두 구현합니다.

 

- ListBox의 경우

  • 화면에보이고,
  • 마우스로 조작 가능하며,
  • 스크롤 해서 항목을 선택할수 있어야 한다.

    는 요구사항을 모두 구현합니다.

 

여기서 주의할 것은, 각 기능을 구현하는데 객체 내에 위 요구사항을 만족하는 기능들의 응집도가

높으면 안된다는 것입니다. 응집도가 높으면 리팩토링하기가 너무 힘들고, 한다고 해도 제대로

부작용(Side-Effect)없이 동작한다고 보장하기 힘듭니다.

 디미터 룰(실용주의 프로그래머(인사이트) 참고)을 철저하게 지키던가 아니면 내부 구현을 최대한

간단하게 한다던지 각자의 방법으로 잘 정리해 합니다.

 

 

어쨌든 구현이 끝나고 각 객체의 리팩토링이 끝나면, 위 요구사항중에서 공통되는 요구사항

"마우스로 조작 가능하다" 를 뽑아내어 Control이라는 위 3개 요소를 대표할수 있는 좀더

두리뭉실한 이름의 객체로 만든후, 이 요구사항과 관계된 Code중 공통되는 부분을 뽑아

이 Control 안에 넣습니다. 그리고 3개 컴포넌트가 이 Control을 상속받게 합니다.

 

 

역시 마찬가지로 "화면에 보이다" 라는 공통되는 요구사항을 뽑아 Visual이라는 객체로 표현후

3개 객체에서 화면그리는데 공통되는 Code를 뽑아 내어 집어 넣습니다.

그리고 3개 Control이 이 Visual을 상속받게 합니다.

 

 

여기서 C++등의 언어에서는 이렇게 한 객체가 2개의 상위 객체를 상속받게 허용합니다.

이를 "다중상속"이라고 부릅니다. 하지만 앞에서도 이야기 했듯이, 이 다중상속 때문에

설계 및 디버깅 전체가 점점 더 어려워지는 문제가 있어 C#등 최신 언어들에서는 다중상속을

지원하지 않습니다.

 

(다중상속의 폐해에 대해서는 Scott Myers의 Effective C++ 등의 저서를 참고하세요)

 

저는 C#으로 먹고살기 때문에 다중상속이 가능하지 않습니다.

따라서 Visual과 Control이 같은 Level 선상에 놓여야 하는지 고민해야 합니다.

결국, Control도 화면에 보여야 하기 때문에, 좀더 근본적인 요구사항을 만족시키는 Visual을

최상위 객체로 둡니다. Control은 Visual을 상속받게 됩니다.

 

 

어떻습니까? 앞과 마찬가지로, 결국 하나의 Tree 모양이 완성됩니다.

내부의 구현은 Top-Down과 다를수 있지만 외부에서 보았을때엔 동일한 구조입니다.

 

여기서 "부지런한" 이라는 수식어를 붙인 이유는, 이러한 Bottom-up 설계가

계속 중복된 요소를 잡아내고, 이를 추상화 시키며 정리해 나가는 리팩토링 과정과 함께 하기 때문에

Top-Down보다 매우 바쁘게 움직여야 한다는 것입니다. 마치 정원사가 계속 정원을 다듬듯이

그렇게 다듬어 주어야 차츰차츰 좋은 설계가 나타나는 것입니다.

 

하지만, 이렇게 힘이 많이 드는 대신에 Top-Down 방식에서 일어나기 쉬운

"불필요한 추상화/구체화"가 덜하다는 장점이 있습니다.

 

 

 

실전에선 어떻게 하나?

 

 위에서는 예로 설명하고자 두 방법을 분명히 구분지으려 노력했지만,

실전에서는 두 방법이 서로 보완하면서 번갈아 이용됩니다. 서로 장단점을 보완해 줄수 있기 때문이죠.

예를들어, Top-Down으로 만든 구조에서 다음과 같은 새로운 요구사항이 들어왔다고 합시다.

 

"클릭할수 있지만 클릭하면 푹 들어가 다시 클릭될 때까지 나오지 않는다."

 

뭔가 애매합니다. 클릭도 되고, 푹 들어가서 또 누를때까지 나오지 않는다....

위에서 이야기한 Button과 CheckBox의 성질을 모두 가지고 있지만, 어느것 하나의 성질도

가지고 있지 않은 상황입니다. 아래와 같이 교집합적인 성질을 지니고 있는 것처럼 보입니다.

 

그럼 Top-Down적으로 생각해서, Control을 상속밭는, 완전히 새로운 것을 하나 더 만들어야 할까요?

당장은 이게 가장 쉬운 방법처럼 보입니다. 하지만 이런 식으로 자꾸자꾸 새로운 것을 만들면

아래처럼 잔가지가 수십개나 달리는 = 중복되는 코드가 수십개나 되는 지저분한 구조가 되어

같은 기능을 수정할 때마다 여러 클래스에 있는 기능을 다 수정해줘야 하는 문제가 생깁니다.

경험상 제일 귀찮으며, 손이 많이가고, 제일 야근을 심하게 하게 만드는 요소이죠.

 

 

 

여기서, Bottom-up적인 생각이 적용될수 있습니다.

Bottom-up적인 생각이란, 앞에서 말했듯이 공통되는 부분을 집어내어

이를 묶는 좀더 대표적인 객체를 만드는 = 한단계 추상화 시키는 것입니다.

 

여기서 공통되는 부분은

 

1 클릭(Click)

2 토글(Toggle) - 클릭하면 푹 들어가 다시 클릭될 때까지 나오지 않는다.

 

입니다.

 

앞에서 말했듯이 "다중상속"은 안 쓴다고 했습니다.

따라서 공통되는 Toggle 기능(또는 특성)만을 위한 객체를 따로 만들어서 Control과 함께 상속받게

할수는 없습니다. 하지만 이 공통되는 특성을 두 객체에 강조하고 싶습니다.

이럴때 쓰는게 Interface 입니다. 공통되는 성질을 이용하기 위한 Interface : IToggle을 만들어

CheckBox와 새로 만들 객체 (여기선 요구사항대로 위 1, 2 성질을 모두 표현하는 ToogleButton

이라고 이름 짓습니다.)가 상속받게 합니다. 그리고 IToggle이 요구하는 구현사항을 모두 구현합니다.

 

 만일 ToggleButton의 IToggle구현과 CheckBox의 IToggle 구현이 동일하고, 여기서도 중복을

없애고 싶다면 IToggle을 구현하는 별도의 상위 객체 (예를들어, ToggledControl 등)을 CheckBox와

ToggleButton의 상위 클래스로 두면 되겠지만, 이럴경우 Button의 Click 특성까지 별도 상위 클래스를

두면 ToggledButton은 다중상속이 되어 C# 에선 구현이 안됩니다. 결국 Click과 Toggle 둘중 더

중요한 특성을 상위 클래스로 뽑고, 다른것은 인터페이스로 뽑아 유형만 구현하도록 합니다.

이 예시에서는 Click이 Toggle보다 좀더 중요하며, 근본적인 특성이라고 보고 설계하였습니다.

 

 

따라서 ToggleButton은 Button의 공통 특성인 Click 기능을 가진 ButtonBase 라는

상위 추상 클래스를 하나 만들어, 이를 상속받게 합니다.

ButtonBase가 추상 클래스인 이유는 클래스 자체로는 인스턴스화할 필요가 없이

Click 에 대한 공통 구현만 제공하며,

하위 클래스에서 재정의 하여 사용하기 용이하도록 하기 위함입니다.

(ButtonBase가 추상 클래스이기 때문에, 이 위의 상위 클래스 모두 추상 클래스여야 합니다.)

 

 

이제 Click의 특성을 가진 ButtonBase를 Button 과 ToggleButton이 모두 상속받음으로서

아래와 같이 Tree 형태를 유지하는 깔끔한? 전체적인 구조가 완성되었습니다.

(그리고 이것이 바로 WPF의 UI Control Framework 설계 모습이기도 합니다.)

 

이렇게 Top-Down적인 설계가 한계에 다다랐을때,

아래에서 Bottom-up하는 설계/생각을 도입하여

좀더 Elegant 하게 설계의 문제를 해결할수 있습니다.

 

 

 

결론

 

 

하지만 앞에서 살펴본 설계에는 한가지 꼭 필요한 요구사항이 있습니다.

바로 "전체를 보면서 설계하라" 라는 의미입니다.

전체 클래스들의 배치와 구조 그뿐만 아니라 각 클래스의 중복점을 알지 못하면

객체지향의 요소들의 존재이유와 특징을 음미하지 못하는,

제대로 안된 설계만이 나올 뿐이고, 개발자는 더욱 고생하게 됩니다.

 

그래서 전체를 보는데 도움이 되는 팁을 몇개 정리했습니다:

 

객체지향 설계의 원칙 = 되도록 기능의 책임을 잘게 나눠라 잘라라

전체 설계 구조의 형태 = Tree 모양을 유지하라

끊임없는 문서화와 리팩토링 = 가지치기를 많이 할수록 정원이 좋아진다.