객체지향 이야기 – 1.상속설계(1)

대한민국 개발자와 객체지향 이야기 – 1.상속설계(1)

필자의 한 후배가 술자리에서 자신이 설계한 클래스 다이어그램을 보여주며 정상적인 클래스 계층도를 형성하고 있는지 물어본 적이 있다. 얼핏 보기에 다형성을 적용하기에 무리가 없고, 상속관계도 간결했지만 확신이 서질 않았다. 이러한 클래스 계층도를 생성하는데 있어 상속 설계(Inheritance Design)는 매우 중요한 역할을 한다. 설계에 대한 별도의 기준이 없는 만큼 어떠한 계층도를 설계 하더라도 그것이 자신의 시스템을 만족 한다고 착각 할 수 있다. 이번 시간에는 상속에 대한 설계에 대해 알아보는 시간으로 상속의 중요성과 평가 항목에 대해 알아본다.

정명수 ㅣmsguns.jung@samsung.com ㅣ 삼성전자 메모리사업부 플래시 소프트웨어 개발 관련 연구원

국내외를 막론하고 10년 이상의 라이프 사이클을 가지는 솔루션이나 프로그램이 얼마나 될까? 대부분의 프로그램이 1년 미만의 프로젝트로 생명을 마감 함에도 불구하고 기존 개발자가 만든 프로그램이나 오래전에 자신이 만든 프로그램을 다시 제작하는 사례가 빈번하다. 제작하는 데 있어 다소 과정의 차이가 있지만 신규 개발자는 기존 개발자가 작성한 코드가 마음에 들지 않거나 재사용성이 낮다는 이유(이전 프로그램이 모듈화가 잘 돼있더라도)로 다시 제작하는 경우가 있다. 또 어떤 이는 개인의 업무 비중을 높이기 위한 방편으로 기존 코드 전체를 자신만 알 수 있는 방식으로 제작한다. 개발자 마다 차이는 있지만 뭔가를 새롭게 제작할 때 기존에 만들어진 오픈소스나 개발 샘플들에서 사용된 모듈을 코드 수준에서 재사용 하는 경우도 흔히 볼 수 있는 광경이다.

무슨 이유에서든지 이렇게 코드를 새롭게 만들고 나면, 개발자의 수준여하에 상관없이 이전 코드에서 더 이상 특정 기능을 찾아 헤매는 일은 사라진다. 모듈 간의 관계도 신규 개발자에게 명확하게 전달되어 프로젝트를 제어하기도 편해진다. 물론 그러기까지 시간에 대한 투자가 필요하겠지만, 모든 코드가 자신만의 네이밍 규칙과 추상화를 가진 프로젝트로 다시 태어날 때의 뿌듯함도 덤으로 얻게 된다.

하지만 필자나 독자들이 다시 구현한 코드는 언제라도 또 다른 개발자에 의해 다시 작성될 수 있다. 신규 개발자가 편입되고 프로젝트가 이관 되었다면 독자들의 노력으로 만든 프로젝트는 다시 사라질 가능성이 높다. 왜 대부분의 신규 개발자는 아키텍처를 모두 수정하고 깨끗하게 정화해 놓은 기존 프로젝트를 다시 제작 하는 것일까? 아마도 앞서 설명할 이유와 유사할 것이다. 신규 개발자의 업무 욕심에 기존 프로그램이 사라질 운명이라면 그 정도의 장렬한 희생은 용서가 된다. 하지만 그 이외에 확장성을 고려하지 않고 주먹구구식으로 제작되어 기존 프로젝트를 재사용 하지 못할 상황이기 때문에 다시 제작하는 거라면 전적으로 기존 개발자의 잘못이다.

기본 기능들의 구현이 서로 얽히고 복잡해서 내부 코드가 무엇을 의미하는지 모르거나 가독성이라고는 찾아볼 수 없는 코드와는 친해지기 힘들다. 친해지려해도 클래스의 용도를 알지 못하고 내부 코드를 보지 않고서는 도저히 쓰기 어려운 추상화 메소드를 만들었다면 재사용은 엄두도 못 낸다. 이와 같이 기존 프로그램의 일부 모듈이 너무 많은 모듈과 관계를 갖고, 무엇을 하는지 직관적으로 파악되지 않는다면 굳이 기존의 코드를 사용할 필요가 없다. 필자와 같이 새로운 것에 금방 적응하지 못하는 개발자라면 아마도 새로운 프로그램을 짜기 위해 몇 년 전의 요구 명세서를 다시 펼칠 것이다.

더 최악의 경우는 디자인은 잘 됐지만 확장성을 갖추지 못한 경우다. 신규 개발자가 기존 개발자가 만든 클래스를 사용하기 위해 마음을 비우고 3일 밤낮을 훑어보았지만 기존 클래스의 코드가 수정하지 않고서는 도저히 확장 할 수 없게 디자인 됐다면 기존 개발자는 진심으로 신규 개발자에게 사과해야 한다. 잘못된 디자인으로 인해 상속이나 오버로딩, 오버라이딩 등으로 클래스의 기능을 확장해도 새로 추가된 요구사항을 만족 시키는 변경이 불가능하거나 변경은 되지만 새로운 요구사항을 잘못된 디자인의 기존 클래스에 무리하게 끼워 맞춰 프로젝트가 본래의 기능성을 상실할 수 있기 때문이다.

C언어로 제작된 경우 프로그램의 재사용성은 현저히 떨어진다. 물론 확장성이라는 단어가 어느 정도까지 고려돼야 하는지 정해진 건 없다. 책임 영역도 범위에 따라 조금씩 달라진다. 그럼에도 불구하고 C언어는 제작된 함수의 기능이 일부 변경되더라도 단지 코드 수준의 Copy & Paste가 전부이다. 코드 수준에서 Copy & Paste로 사용 가능한 함수나 매크로는 그나마 다행이다. 기존 코드를 바탕으로 프로그램이 다시 작성된다는 말은 그에 알맞은 새로운 루틴이나 자료구조가 추가된다는 말이다. 이러한 경우 대부분 라인 단위로 자료구조와 루틴을 하나하나 추적하며 함수 자체를 이해하고 기존 코드 자체를 변경하는 수고가 따르게 된다.

이런 관점에서 C++의 상속은 재사용성의 입장에서만 보더라도(재사용성 뿐만 아니라 아키텍트 입장에서도 많은 이점을 가진다) C로 구조화된 프로그램보다 많은 이점을 갖고 있다. 지금부터 코드 재사용성의 관점에서 상속과 다중 상속, 인터페이스(Interface)등의 이슈를 살펴본다.

상속

상속(Inheritance)은 객체지향 적 언어의 장점을 이야기 할 때 가장 흔하게 부각 되는 장점이다. 다른 개발자가 작성한 클래스들을 상속 받아 그와 비슷한 기능을 수행하는 다른 클래스로 만들 수 있으므로 어느 절차적 언어보다 재사용성이 뛰어나다. 상속은 클래스 계층 구조에서 공통적인 속성과 행동들을 공유하게 하는 기능이다. 대부분 상속을 받는 하위의 클래스들은 상위 클래스에서 상속받은 속성과 행동들을 통해서 새로운 추상화 과정을 거치게 된다. 즉 어떤 행동들은 은닉해서 제거하고 또 다른 행동들은 이전 행동에 뭔가 더해진 행동을 하도록 구현한다. 아니면 전혀 다른 기능을 수행 하게 구현 할 수도 있다. 상속을 할 때는 크게 두 가지 측면만 고려하면 무난한 클래스 계층도를 생성 할 수 있다.

  • IS-A 관계
  • HAS-A 관계

이 두 가지의 상속 판단 동사는 생성하게 될 클래스가 결국 하나의 오브젝트로 명사처럼 쓰일 수 있기 때문에 상속할 클래스와 상속 받을 클래스간의 관계가 자연스러운지를 파악 할 수 있다. 다시 말해 상속 받게 될 클래스의 이름과 상속하게 될 클래스의 이름 사이에 IS-A와 HAS-A라는 동사를 넣어봄으로써 이 클래스 간의 상속 관계가 직관적으로 정상인가를 판단하는 척도가 된다. 클래스와 클래스 사이에 IS-A, HAS-A가 들어갔을 때 문장이 어색하지 않으면 서로 상속관계를 가져도 좋은 것으로 보면 된다.

익숙한 실례로 IS-A 관계와 HAS-A 관계를 적용시켜 보자. 한 IT업체가 내부적으로 사람들을 관리하기 위해 시스템을 개발한다고 가정해 보자. 각 프로젝트 단위로 개발자들을 분류하여 이를 클래스로 추상화한다면 흔히 개발자와 프로젝트 리더의 클래스를 떠올리게 된다. 요구명세가 간단하긴 해도 사람을 관리하기 위한 시스템에서 사람이라는 클래스를 생각해볼 수 있다. 사람이라는 클래스는 프로젝트 리더, 개발자 두 클래스가 공통적으로 가져야 할 속성이다. 즉 이름과 주민등록 번호, 성별 등과 같은 멤버들을 포함하게 된다. 물론 다른 방식으로 클래스들을 추상화 할 수도 있지만 지금의 사례에서는 무난한 상속 관계를 보여주기 위한 조건을 갖추는 것이므로 <그림 1>의 계층도를 통해서 앞에서 언급된 IS-A관계와 HAS-A관계가 적당한지 판단해 보자.

<그림1> 이름으로 판단 해보는 상속 관계

첫 번째 개발자(CDeveloper)와 사람(CPeople)간의 관계를 살펴보도록 하자. 직관적으로 사람이 개발자보다 상위 개념이다. IS-A와 HAS-A 통해서 만들어지는 문장은 두 가지로 ‘개발자는 사람이다(Developer is a people)’와 ‘개발자가 사람을 가지고 있다(Developer has a people)’정도 이다. 두 문장 중에 전자가 정상적인 문장이라는 것을 쉽게 알 수 있을 것이다. 같은 이유로 프로젝트 리더는 개발자의 클래스로부터 상속하게 된다. 개발관련 부서의 프로젝트 리더라고 한정해 두는 것이 더 정확하다. 개발관련 부서의 프로젝트 리더는 개발자 과정을 거쳤기 때문에 소속 부서와 개발 경력 등을 상속 받을 수 있다. 상속 받은 속성 이외에 프로젝트 리더는 개발자가 가지고 있지 않은 행동이나 속성을 정의해야 한다. 다시 말해 일반적인 프로젝트 리더들이 부서원들을 관리하고 평가하는 일과 특정 프로젝트에 대하여 보고해야 하는 위치에서 해야 하는 행동을 정의한다. 이는 개발자로부터 상속받은 후 그에 대하여 속성이나 행동들을 추가 하면 된다.

여기서 CPeople은 상위 클래스(Super class)나 부모 클래스(Parent class), 또는 기반 클래스(Base class)라고 부르는데 이 세 가지는 모두 같은 의미로 쓰인다. 중간의 개발자 위치는 프로젝트 리더에 대하여 상위 클래스이자 부모 클래스, 기반 클래스이지만 CPoeple에 관해서는 그 아래인 하위 클래스(Sub class) 또는 자식 클래스(Child class), 파생 클래스(Derived class)로 불린다. 프로젝트의 리더인 경우에는 단말 클래스에 해당 될 것이다. 객체지향적 언어의 클래스 간의 용어에는 같은 의미에 대하여 다른 용어들을 정의하여 사용자나 학습자를 혼란스럽게 하는 경향이 있다. 이럴 때는 용어에 대한 집착 보다 문맥에서 그 용어가 차지하고 있는 실제 의미를 파악하는 것이 우선이다.

유연성과 딱딱함, 그 핵심에 있는 상속 스킴

객체지향에 있어서 상속은 아주 중요한 부분을 차지한다. 앞서 반복해서 이야기 했던 폴리모피즘(Polymorphism), 동적 바인딩(Dynamic binding), 그리고 앞으로 이야기 할 추상화 클래스(Abstraction Class), 인터페이스(Interface)의 모든 근간이 된다. CBSE(Component base software engineering)의 혹자들은 간혹 OOP는 재사용성 관점에서 클래스라는 개념이 실패했다는 주장을 한다. 재사용성을 다루는 덩어리, 즉 컴포넌트 크기가 작고 사용자의 코드 접근이 가능하므로 완벽하게 정보 은닉이 불가능 하다고 반박한다. 또한 시스템 엔지니어들은 구현하고자 하는 기능을 포함한 클래스가 기존에 개발된 클래스를 사용하지 못하고, 각 클래스 간의 소프트웨어 엔트로피가 높아 자신들의 코드가 삽입될 수 없는 경우가 많다고 주장한다. 즉 모두 다시 구현하기 때문에 C++와 같은 객체지향 언어에 전혀 재사용성이 없다고 주장 한다. 이는 C++와 같은 객체지향적 언어의 재사용성에 대한 실패라기보다는 클래스 설계를 잘못한 언어의 사용자에게 그 책임이 있다고 본다.

다른 절차적 언어와 달리 상속 관계는 시스템 전체의 골격을 형성하므로 이에 대한 설계가 잘못되면 재사용성이 현저히 떨어지는 것은 물론, 지극히 소프트(soft)하지 않은 소프트웨어가 된다. 잘 구조화된 계층도를 가지는 객체지향이 적용된 시스템에서 유지 보수는 많은 유연성과 편리함을 제공하지만 클래스들의 계층 구조가 잘못된 요구분석 및 모델링 분석에 의해 설계될 경우 프로젝트의 중간을 넘어간 이후 다시 정상적으로 변경하는 것은 엄청난 비용과 노력이 따른다. 따라서 초보 개발자의 부정확한 모델링으로 인한 시스템의 구조적 결함을 객체지향 패러다임이나 언어의 자체의 문제로 해석하는 오류를 범할 수 있다.

또한 기존에 만들어진 클래스들을 통해 상속을 구현하는 사용자 입장에서도 상속 관계를 올바르게 유지해야 한다. 상속 구조를 바람직하게 유지 하는 것은 차후 변경 및 추가의 가능성을 고려했을 때 최소한의 매너이다. 즉 사용자가 정상적이지 않은 방법을 이용해 상속을 받아 시스템을 구현 한다면 나중에 다른 사용자나 개발자가 자신의 클래스를 상속 받게 될 경우에 소프트웨어 복잡도와 엔트로피가 증가한다. 결국 시스템을 못 쓰는 지경에 이른다. 위의 예에서 성별과 이름을 가지고 개인적인 관리 번호를 가지는 회사 내부의 보안견과 같은 클래스를 시스템에 추가 할 일이 생겼다고 가정하자. 개발자가 코드 상에서 볼 때 CPeople이 가지는 속성을 그대로 가진다고 해서 사람으로부터 상속받아 구현하면 당장은 별다른 문제가 없겠지만 반드시 누군가에 의해 다시 작성해야 한다.

상속 시 오류가 발생할 경우 사용자의 잘못인지, 설계자의 잘못인지에 대한 책임 여부는 경우에 따라 애매해질 수 있다. 예를 들어 상위 클래스 다이어그램에서 CPeople이라는 클래스가 없다고 가정해 보자(원래 구현 사항에서는 개발자와 프로젝트 리더를 관리하는 것이 목적이다). 이렇게 개발된 시스템을 임직원들을 모두 관리 할 수 있도록 변경하면, 관리직(CManager) 직군의 상속 문제에 봉착하게 된다. 관리직은 개발자도 아니고 프로젝트 리더도 아니다. 하지만 성별과 이름, 주민번호 등은 모두 가지고 있으므로 개발자로부터 상속을 받아 자신만의 속성들을 오버로딩시켜 시스템을 구축해도 문제가 되진 않는다. 이렇게 사용자가 관리직을 개발자로부터 상속 받게 된다면 사용자의 잘못인가 설계자의 잘못인가? 설계자는 요구사항에 대해서 올바르게 구현했고 사용자는 기존 시스템을 잘 이용 해보려고 한 것 이외에는 서로 간의 차이가 없기 때문이다. 사실 이러한 경우 설계자가 확장성을 고려하지 않은 채 시스템을 구현한 것이 잘못으로 여겨질 수도 있다. 하지만 설계자가 시스템이 어디까지 확장될 것인지는 모두 예측할 수 없다. 그렇다고 요구된 시스템 보다 과하게(?) 디자인 하는 것은 타임투마켓(time-to-market)에서 살아가는 소프트웨어의 운명을 간과한 설계이다. 상속 설계를 통해 이후 개발자나 사용자에게 최소한의 예의를 갖추는 방법을 모색해 보자.

상속 설계

자료 추상화는 모든 프로그램에서 존재한다. 개발자가 이해하는 단위의 자료 추상화로 C언어는 자료구조, C++는 클래스로 이루어진다. 하지만 상속은 자료 추상화와는 조금 다른 설계관점이 필요하다. 사실 상속은 자료 추상화 관점 보다는 여러 개의 클래스 클러스터를 설계부터 시작해 전체 시스템의 골격을 생성한다. 클래스를 통한 자료 추상화는 하나의 클래스 개체에만 적용되는 것으로 그 클래스가 잘못 되더라도 시스템 전체에 영향을 미치지 않지만, 상속을 통해 클래스가 계층도를 갖게 되는 경우, 상속 트리를 잘 못 설계하면 프로젝트 전체가 엉망이 된다. 상속 트리는 프로젝트가 진행 될수록 계속 자라나 그 복잡도가 증가 하는 방향으로 흘러가기 때문이다.

C++를 사용하는 대부분의 개발자가 상속을 사용하지 못하거나 상속이 두 단계 이상 내려가지 못하는 이유도 하나의 클래스에 영향을 미치는 추상화 도구로써 사용하기 때문이다. 이는 C언어의 구조체를 사용하는 것과 다를 바가 없다(대부분 이러한 경우 프로젝트 내에서 Copy & paste에 의해 반복되는 코드들을 쉽게 볼 수 있다).

이러한 관점에서 상속에 대한 설계는 프로젝트 성패의 중요한 키워드가 된다. 자료 추상화가 스케치와 채색으로 구분되기는 하지만 이와 달리 상속을 적용하는 절차는 다른 것과 비유하기 쉽지 않다. 하지만 상속을 적용하기 위해서는 반드시 자료 추상화가 수반 돼야 한다.

클래스 클러스터 설계

IT업체의 내부 전산망 시스템을 사례로 상속을 살펴봤다. 여기서 가장 고려했던 점은 그 시스템이 관리해야 하는 대상이다. 이 대상에서 각 성격을 아우르는 클래스 클러스터를 생각해야 한다. 앞서 살폈던 예제에서는 프로젝트 리더와 개발자라는 한정된 정의 때문에 별도의 클러스터가 존재하지 않았지만, 개발자와 프로젝트 리더를 포함하는 클러스터, 즉 사람과 개발자라는 클래스 클러스터를 어느 정도 인식했다. 상속 설계에 있어 이런 클래스 클러스터 인식이 가장 선행돼야 할 내용이다.

클러스터를 인식 하기 위해 해야 할 것들을 무한정 적어보자. 일종의 추상 명세서와 같은 것이다. 참고로 추상 명세서는 아주 구체적일수록 좋다. 클라이언트가 원하는 것을 모두 알아야 이후 서비스들을 정의 할 때 좀 더 구체적이고 탄탄한 설계를 할 수 있다.

“프로젝트에서 지원해야 하는 것은 회사에 출입하게 되는 모든 사람들의 정보를 저장해두고 회사에서 유용한 정보로 가공하는 것이다. 여기서 모든 사람이라는 것에는 고객, 직원, 투자자 등을 들 수 있을 것이고 유용한 정보라는 것은 고객들 중 회사에 많은 도움이 되는 사람부터 나열 하거나 투자자들이 가지고 있는 재력을 측정하고 그들의 관심 분야를 통해 우리 회사에 가장 유력한 투자자를 뽑는다는 행위를 포함한다. 관리를 위해서는 직원들 정보가 필요한데 이들의 인사이동, 승진, 급여 등의 모든 회사 제반 활동의 기반 하는 행위들을 계산 할 수 있어야 한다. 개발자들은 2년 이상의 경력을 가지고 있어야 하고 그들이 갈 수 있는 부서는 정해져 있으며 기획자들은 외국 경험을 필수로 가지고 있어야 하고 이를 보조하는 지원업무의 직군 들은 경력에 무관하나 사무 일에 포함 되는 제반 업무를 처리 할 수 있어야 한다. 각각의 직원들은 다른 봉급 제도를 가지고 있으며… (중략)”

이러한 추상 명세서에서 클러스터를 인식하는 방법으로 크게 두 가지를 생각해볼 수 있다. 하나는 명사 중심의 클러스터 인식이고 다른 하나는 서비스 중심의 클러스터 인식이다(여기서 말하는 명사 중심, 서비스 중심은 특별히 객체지향적 언어에서 정의하는 내용은 아니다. 필자가 클래스 클러스터를 인식할 때 사용 하는 방식으로 간단하게 명명하였다). 우선 요구사항을 정의에 대하여 필요한 클래스들을 줄줄이 나열하여 시스템의 서비스 중심의 클러스터를 찾아보자. 서비스를 달리 말하면 개발자 입장에서 필요한 함수나 메소드로 이해하면 쉽다. 어떠한 특징을 찾아서 클러스터를 형성하기 위해서는 이러한 함수나 메소드들을 모두 나열해야 한다. 일종의 브레인스토밍으로 보면 된다. 위의 추상 명세서에서 봤듯이 고객들 중에 회사에 많이 도움이 되는 사람이라는 것의 서비스 명칭은 GetClientOfVIP()정도의 이름을 갖게 될 것이다. 이러한 식으로 위의 서비스들을 크게 나열 하면 <그림 2>와 같다.

<그림2> 서비스에 대한 브레인스토밍

<그림 2>에 나열된 서비스들의 상세 메소드 이름은 실제 서비스가 가능한 객체가 되는 클래스들이 필요하다. 명사에 대한(클래스에 대한) 클러스터는 분류 하지 않았으니 이를 구분 짓고 나서 각 클래스에 바인딩 시킬 것이다. 또한 이 서비스들의 행동에 대한 정의가 되는 입력과 출력의 시그니쳐는 아직 정의되지 않았다. 이는 모든 서비스를 인식하거나 클러스터 설계가 끝난 후에 정의 될 수 있다. 이러한 서비스가 영향을 미치는 객체에 대한 정의가 되지 않았기 때문이다.

물론 설계자에 따라 이러한 시그니쳐를 먼저 정의하는 경우도 있지만 하나의 설계 단계에서 다른 단계로 진행됨에 있어 이전 설계단계가 완벽히 이뤄지지 않아도 무방하다. 이는 나선형 구조의 설계방식으로 객체지향적 패러다임에서 일반적으로 사용된다. 사실 전공에서 배우는 폭포수 개발 모델과 같은 전형적인 설계 방식보다 훨씬 인간적(?)이다. 시스템의 요구사항이 시시각각 변하여 마치 요구사항 자체도 살아있는 시스템을 형성하므로 이전 단계를 마무리 하면 그걸로 끝인 폭포수 모델은 이전 단계로의 롤백을 일으켜 오히려 설계나 개발의 비용을 증가 시킨다. 또한 개발자나 설계자가 처음 구현하는 시스템에 대해서 어떤 특징을 갖는지에 대한 클러스터를 인식하기도 전에 마치 구현할 시스템을 전부 알 고 있는 신이라도 된 듯이 오류나 실수에 의해 새로 설계되지 않고 순차적으로 설계, 개발, 검증을 하는 단 방향 개발 모델은 예상치 못한 결과를 가져온다.

대동여지도가 특별한 측량기법 없이도 현재의 지도와 아주 유사한 까닭은 각 지방마다 분리해서 그린 후 조합했기 때문이다. 그 당시, 우리나라 전체 모양을 생각하고 각 지방들을 그리진 않았을 것이다. 다시 서비스를 클래스에 바인딩 하는 이야기로 돌아가자. 사실 이러한 서비스가 누구에게 또는 어떤 객체에게 바인딩 되는지 추상화 된 명세서를 통해 어렴풋하게 알고 있다. 이러한 명사 클러스터에 원소들은 각각 고객(Client), 투자자(Supporter), 개발자(Developer), 관리자(Manager), 기획자(Planner)로 나열되는데 이러한 단어들은 클래스 명이 될 수 있다. 이제 서비스와 클래스를 묶어 단말 클래스 설계를 일단락 하자. 클러스터를 설계하기 위해 임시로 각자에게 필요한 서비스들은 모두 포함 시킨다. 상위 유추들을 통해 <그림 3>과 같은 클래스를 얻을 수 있다.

<그림3> 각 서비스에 의해 유추된 클래스들

사실 현재 정의한 내용보다 더 많은 서비스가 필요 할 수 있다. 여기서는 실제 시스템을 구현하지 않으니 일부 예제가 될 만한 서비스들만 정의해 보자. 클래스를 설계함에 있어서 설계상의 오류가 있을 수 있다. 하지만 객체지향 적 언어는 앞서 말했든 나선형 개발 구조로 변형이 가능하며 개별 노드들로부터 전체 시스템을 구성하게 되는 상향식 프로세스를 가지고 있다. 지금 정의되는 클래스들은 일종의 단말 노드들로서 이를 묶어 주는 새로운 클래스가 존재 할 수 있다. 이는 상위 클래스들을 정의해가면서 유사한 특징들을 가지는 클래스를 추상화하여 트리에 삽입하고 설계를 확장 할 수 있을 것이다.

각 클래스의 속성과 유사점 인출

지금까지 상위 서비스들을 정의하고 각 객체들을 정의했다. 다음으로 할 일은 각 클래스의 속성들을 정의하는 것이다. 속성들은 서비스를 구현 하거나 코드 작성 시 모르고 있던 더 많은 데이터 멤버들을 정의 할 수 있게 된다. 필자는 속성(Attri bute)라는 이름과 데이터 멤버라는 것을 구분하는데 속성은 실제 명세로부터 생성되고 그 클래스의 고유한 특성을 대변하는 데이터 멤버를 말한다. 즉 Client의 경우 우리와 비즈니스를 하기 위해 필요한 자본을 보유하고 있어야 한다. 그 고객의 경험과 마인드 등이 속성으로 분류 될 수 있다. 이러한 데이터 멤버들은 실제 그 클래스의 본질적인 것으로 속성(Attribute)으로 부른다. 이에 반해, 검색이나 특정 정책이 반영된 알고리즘을 통해서 이 Client가 VIP인지를 찾는 것에 필요한 멤버 변수는 앞에서 말한 속성과는 본질적으로 다르기 때문에 데이터 멤버로 호칭한다. 이러한 데이터 멤버들을 알고리즘 표현뿐만 아니라 메소드 간의 정보전달, 메소드의 중간 결과를 잠시 보관하기 위해서도 사용 한다.

속성들은 관례적으로 Get이나 Set이라는 이름의 메소드로 외부 클래스나 서비스로 Expose시키게 된다. 이는 첫 회(2007년 3월호)에서 언급했으니 참고하기 바란다. 요즘의 추세로는 Get이나Set이라는 접두사를 사용하지 않고 실제 멤버들의 이름을 그대로 써서 좀 더 보기가 편리하다. 예를 들면 GetPay의 경우 Get을 제외 하고 int Pay()와 void Pay(int money)와 같이 쓸 수 있다. 이런 한 명명 법칙이 편리한 것은 속성의 정의된 이름과 일치하기 때문에 좀 더 직관적인 메소드로 구성이 가능하고 이에 따라 가독성이 증가 한다. Get은 리턴 타입이 반드시 필요 할 것이고 Set의 경우 반드시 속성에 대한 정보를 인자로 받을 것이니 이를 제외하고 같은 이름을 두고 이 인자를 통해서 오버로딩이 가능하다(TO MATO사의 최근 버전의 Visual assist는 리팩토링(refactoring) 기능에서 후자의 생략 방식대로 속성들을 expose하고 있다).

다시 본론으로 돌아가 이러한 속성들을 전부 정의하고 나면, 속성 중에서 공통적인 점을 찾아야 한다. 공통적인 특징으로 멤버들을 묶음으로써 상속 관계의 가장 기본적인 틀을 만들 수 있다. 눈에 보일 듯 말듯 한 존재의 공통점을 인출하는 것은 생각보다 쉽지 않은 작업이다. 직관적으로 각각 공통되는 속성을 묶고 유사한 클래스들을 묶어 본다. 이때 벤-다이어그램이 유용하게 쓰인다. 벤-다이어그램에는 클래스 내의 멤버를 먼저 생각하여 그 멤버들 간의 포함관계를 고려해 분류해야 하지만 너무 복잡해지는 것을 방지하기 위해 클래스 이름들만 나열 하였다. <그림 4>는 정말 유사점을 가지는 것들을 하나하나 묶어서 기계적으로 분류 한 것이다.

<그림4> 포함관계를 기계적으로 분류한 벤-다이어그램

<그림 4>의 벤-다이어그램에서 각각 Planner, Manager, Dev eloper, Client, Supporter들은 공통적으로 주민등록 번호와 이름, 성별이라는 속성을 갖고 있다. 이 속성은 People 클래스에 포함됐으므로 People이 다른 클래스의 Super set으로 그렸다. 그리고 각 단말 노드가 서로 중복되지 않는 부분이 있다. 이는 고객인가 임직원인가에 따른 속성의 차이로 Client와 Employee와 같은 중간 단계의 클래스를 삽입하여 분류를 좀 더 명확히 하였다. 그 중 Supporter의 일부 속성은 Client에 모두 있으므로 Client는 Supporter의 Super set으로 정의 하였다. Planner, Manager 그리고 Developer 클래스들은 Employee라는 중간 클래스를 하나 더 두어 임직원과 고객의 속성 차이를 확실히 하고 이러한 클러스터링은 차후 두 클래스를 이용해 좀 더 진화된 클래스들을 정의 할 수 있는 여지를 남겨 놓는다.

예를 들어 Client는 다른 고객 군이 생성될 때 확장 할 수 있다. 마찬가지로 Employee 클래스는 외부 직원이나 신규 직군까지 커버 할 수 있을 것이다. 일정부분 확장성도 고려됐으므로 기계적인 분류에 의해 생성된 클래스 다이어그램은 어느 정도 디자인 이슈를 만족 하게 된다. 이러한 기계적 분류는 현 시스템 설계에서 좋은 것으로 분류 될 수도 있고 최악의 계층도가 될 수도 있다. 위에서 제시된 벤-다이어그램 외에도 다음과 같은 벤-다이어그램을 더 생각 해 볼 수 있다(<그림 5> 참조).

<그림 5>와 같은 벤-다이어그램은 필자에 의해 세 가지로 그려졌다. 실제로는 더욱 많은 벤-다이어그램이 있을 수 있다. 다시 말해 동일한 시스템을 구현함에 있어서 간과하지 말아야 할 것은 이중 어느 벤-다이어그램을 사용하더라도 정상적으로 동작하는 시스템을 구현 할 수 있다는 점이다. 그래서 각기 제작이 가능한 벤 다이어그램을 가지고 시스템에서 미칠 영향력을 평가해야 한다. 아울러 평가에 있어 이러한 분류들의 문제점과 장점을 도출해서 가장 적절한 클래스 계층도를 유도해내고 구현해야 할 것이다. 이 세 가지 모델을 가지고 각기 시스템에서 어떤 것이 좋을 것인지 평가 해보도록 하자.

<그림5> 같은 시스템에 대한 다른 벤-다이어그램

클래스 설계 시 고려사항

우리는 세 가지 사례를 갖고 클래스 계층도를 평가했다. 다음의 내용은 클래스 계층도를 설계할 때 고려할 사항이다.

  • 상속 관계는 자연스러워야 한다.
  • 속성과 메소드는 중복되지 않는 것이 좋다.
  • 확장성을 고려해야 한다.

첫 번째 상속 관계가 자연스러운지에 대한 평가는 어떠한 평가보다 선행돼야 한다. 이는 시스템이 어떤 확장성을 가질 수 있는 가에 대해서도 영향력을 미친다. 첫 번째 벤-다이어그램으로부터 <그림 5>와 같은 클래스 다이어그램을 생성할 수 있다. 첫 번째 벤-다이어그램은 각 클래스 간의 상속관계가 명확하다. 즉 첫 레벨에서 ‘임직원과 고객은 사람이다’라는 조건을 만족하고 각각 ‘개발자와 관리자는 임직원이며, 후원자는 고객이다’라는 조건을 만족하므로 이 클래스 다이어그램만큼 상속 관계가 명확한 것은 없다. 여기서 Poeple이라는 클래스는 추상 기본 클래스(ABC : Abstraction Base Class)로 다형성의 사용시 모든 클래스의 인터페이스가 될 수 있기 때문에 자료 구조 운영에서도 적당한 계층도를 가지고 있다.
대부분 의 개발자가 생각하는 클래스 다이어그램은 <그림 6>과 같은 형태를 보인다. 하지만 이 클래스 다이어그램은 차후 코드가 반복적으로 사용되는 중복된 클래스 계층도(두 번째 벤-다이어그램에서 파생된)보다 낮게 평가 받게 될 것이다. 그 이유는 다음 호에서 확인하도록 하자. 아울러 다음 호에서 코드 반복성에 대한 평가와 확장성에 대하여 다른 벤-다이어그램을 모두 같이 평가해보고 어떠한 클래스 계층도가 이중에서 좋은 것인지 생각해 보기로 하자.

상속에 대한 이슈는 개발자마다 중요시 하는 관점이 다르고 자신의 가치관에 따라 다른 계층도를 생성할 수 있다. 또한 코드나 언어에 대한 기술적 지식보다는 시스템 전체를 디자인하는 작업이므로 상속에 대해 이야기할 때, 잘못하면 너무 높은 추상화 수준에서 기술될 수 있다. 때문에 상속 관계를 정의하고 시스템을 형성할 때는 가급적 UML을 이용해 코딩 전에 정확히 설계하는 것이 중요하다. UML로 설계했다고 해서 무조건 올바른 클래스 계층도를 형성해주는 것은 아니다. UML은 클래스 간의 관계를 가시적으로 표현하기 때문에 개발자가 클래스 설계 시 범하는 오류들을 사전에 방지해 주는 정도다. 무엇보다 중요한 건 개발자의 소프트웨어적인 마인드다. UML의 사용 여부를 떠나 반드시 기억할 것은 단지 클래스간의 재사용성만을 가지고 계층도를 형성하면 안 된다는 것이다. 또한 확장성만 가지고 계층도를 형성해도 안 될 것이다. 더불어 설계자도 언어적 테크닉을 이용해 보기 좋은 코드를 만들기보다는 프로젝트의 생명 주기를 길게 해주는 클래스 설계를 고려하기 바란다.

제공 : DB포탈사이트 DBguide.net
출처 : 마이크로소프트웨어 2007년 8월

답글 남기기