프로그램은 현실세계의 추상화
고난이도의 알고리즘에 기반한 프로그램이거나 혹은 무수한 쿼리문을 날려 그 결과값을 화면에 뿌리기만 하는 관리 프로그램이라도 그 근본 구조는 다르지 않다. 객체지향 프로그래밍에선 결국 프로그램은 현실세계의 추상화이기 때문이다. 사용자의 요구사항을 추상화된 모델을 만들어 처리하고 응답하는 것이다.
회사의 자재관리 프로그램을 짠다고 생각해 보자. MVC 패턴에 입각하면 UI는 단순히 모델의 반영이며, 모델에 값을 전달하는 것에 불과하다. 물론 요즘 프로그램의 UI는 복잡하고 화려해지면서 그 자체가 하나의 프레임웍으로 구현된다.
UI프레임웍은 이미 충분히 일반화 되어 있다 . MFC, WPF, WinForm등 그저 갖다 쓰기만 하면 된다.
하지만 모델은 어떤가. 모든 프로젝트는 결코 동일하지 않듯이 프로그램도 동일한 비지니스 로직을 표현하는 것은 없다. 불가능한 것은 아니나 비지니스 로직을 프레임웍으로 만들기는 대단히 어렵다. 처음부터 비지니스 프레임웍을 구성하도록 하기보다는 프로그램에 맞도록 모델들을 잘 연계한 구조 (아키텍처, 골격)를 가져야 확장성과 유지보수성을 극대화할 수 있다.
- UI 결합 코드의 일반적인 사례
여러 회사에서 여러 프로그램을 접해보면서 놀라운점은 대부분의 프로그램이 UI에 밀접하게 연계되어 있다는 것이다. UI를 분리할경우 어떠한 작업도 진행할 수 없도록 짜여져 있다. 더욱 놀라운 것은 그게 문제인지조차 모르는 경우가 허다하다. 너무나 많은 프로그래머들이 그런식으로 작성해왔기 때문이다.
모델과 뷰의분리가 잘이해가 가지않는다면 역설적으로모델과 UI가결합된 것을 보면된다. 다음 화면은 간단한 곱셈을 연산한 후 값을 출력하는 프로그램이다.
이 단순한프로그램을 어떻게구현할까? 초보자들은 흔히 다음과 같이 짜곤 한다.
voidCBadCalculatorDlg::OnBnClickedBtnResult()
{
CStringsLValue, sRValue;
m_editLValue.GetWindowText(sLValue);
m_editRValue.GetWindowText(sRValue);
double rLValue = _tstof(sLValue);
double rRValue = _tstof(sRValue);
double rRet = rLValue * rRValue;
CStringsRetValue;
sRetValue.Format(_T("%.4f"), rRet);
m_editRetValue.SetWindowText(sRetValue);
}
즉 계산버튼 누르는이벤트 안에서값을 입력하는두 에디터박스 안의값을 가져와서곱하기를 한후 다시결과 값을에디터 박스에넣어 표현한다. 뭐가 문제일까? UI만 있을 뿐 모델 객체가 없다. 모델객체가 없다면곱하기 천번 테스트같은 것이불가능 해지고, 또한 다른 프로그램에서 계산기를 재사용하는 것이 어려워진다. 물론 복사신공(?)으로 가능하지만, 그건 객체지향 세계에선 가장 큰 범죄 행위(?)중 하나이다.
자 그럼모델객체를 만들어보자. 혹자는 단순한 곱하기인데 굳이 클래스를 만들 필요가 있느냐고 생각할지 모르겠다. 또 현재의 상황만 보자면 사실 충분하기도 하다. 하지만 프로그램은 생물과 매우 유사한 진화 과정을 거치게 됨을 생각해야 한다.
#pragma once
namespace Indy
{
class Calculator
{
public:
Calculator(void);
virtual ~Calculator(void);
double GetResult() { returnm_rResult; }
double Multiply(doublelValue, double rValue);
double Multiply(doubleRValue);
protected:
double m_rResult;
};
};
Calculator 구현
#include "StdAfx.h"
#include "IndyCalculator.h"
using namespace Indy;
Calculator::Calculator(void)
{
m_rResult = 0;
}
doubleCalculator::Multiply(double lValue, double rValue)
{
m_rResult =lValue * rValue;
return m_rResult;
}
doubleCalculator::Multiply(double RValue)
{
m_rResult *=RValue;
return m_rResult;
}
위의 선언부를보면 두개의 값을곱하는 함수가있고, 인자가 하나뿐인 곱하기 함수도 있다. 우리가계산기를 사용할때 이전의연산 결과에다시 곱하는경우가 있는데, 인자가 하나뿐이 함수의 구현부를 보면 이전의 결과값을 누적함을 확인할 수 있다. 또 곱하기를 몇 번 수행했는지를 알고 싶다면? 곱하기를 한 히스토리를 30단계까지 보고 싶다면? 곱하기의 결과를 세 자리씩 끊어서 “,” 를 붙여 회계 형으로 만들고 싶다면 어덯게 할까?
이제슬슬 머리가 아파오기시작할 것이다. 만약모델이 없고 UI에이런 기능을 추가하려면조금씩 UI와 기능양쪽과 서로 연관되면서그 복잡도는 더욱더커지게 된다.
나는 골격을 프레임워크의 전단계라고 본다. 만약 여러분이 유사한 프로그램을 세 번 정도 만든다고 하면 여러분이 만든 골격이 차츰 프레임워크로 발전할 것이지만 그렇지 않은 경우에는 골격이 아키텍처 차원의 재 활용성을 가지기는 매우 어렵다. 그러나 잘 짜인 아키텍처는 품질을 유지하고, 개발의 속도를 높이는 데는 막강한 역할을 담당하게 될 것이다.
- 골격이란 프로그램의 핵심기능이 구현된 중요한 구조(아키텍처)의 구현물
프로젝트의 초창기엔주변의 자잘한기능이 아닌핵심 기능에집중해야 한다. 우리는 흔히 개발 공정을 작성할 때 프로그램의 전체 기능을 순서대로 개발하는 오류를 범하기도 한다.
예를 들어 XML로 값을 읽어 들여 연산을 한 뒤 결과를 3차원 그래프로 출력하는프로그램을 개발할경우 XML파서기 개발부터 시작하는 식이다. 계산기를 만들경우 계산하는기능에 우선집중해야 한다. 결과를 3차원 그래프로 표현하거나 XML입 출력하는 기능은 핵심 기능이 아니다. 프로젝트의전체를 관통하는주요 흐름만을최대한 빠른시간 내에구현해야 한다.
전체 개발기간의 3-40% 이내의 기간 내에 골격이 완성 되고, 핵심 기능이 구현되지 못한다면 프로젝트는 실패를 예고하고 있는 것이다. XML파서기를 아무리잘 만들든 무슨소용이 있겠는가? 계산 성능에서심각한 문제가 후반에 발견된다면 말이다.
바로이러한 문제를 조기에확인하기 위해 골격을구현하고 테스트하는 것은 중요한 작업이다. 이 작업이 잘 마무리되면 프로젝트 중, 후반의 작업은즐거운 살붙이기 작업이될 것이다.
프로그램이 자꾸 버그가 꼬리를 물때, UI의 작은 변경에도 많은 클래스들을 변경해야 할때 자신의 프로그램을 자세히 들여다 보라. 골격이 있는지, 또 있다면 UI와 완전히 분리되어 있는지 살펴볼 일이다.
'Successfull Project > Architecture Design' 카테고리의 다른 글
객체지향 코드 - 캡슐화 (0) | 2018.12.28 |
---|