이 블로그 검색

2014년 4월 13일 일요일

내가 본 거지같은 소스들 - 잘못된 추상화

지금까지 일한곳들에 존재하는, 기존 거지 발싸개 같은 소스들을 유지, 보수하면서 느낀점들을 정리한다.  

잘못된 추상화

추상화란 프로그램이 사용할 객체를 정의하기 위해, 필요한 정보들을 뽑아내는 작업이라고 할수있다. 잘못된 추상화란 객체를 잘못 정의하는것이다. 예를 들어 데이터베이스 테이블을 위한 객체를 정의한다고 해보자. 어떤  객체를 정의해야 할까? 
일단 각각의 테이블들을 각각의 객체로 정의한다고 가정해보자. 

자.. 여기서 내가 실제 겪고 있는 거지소스를 예로 한번 들어본다. 

누군가 DataFormat  테이블을 위해 CDataFormat 이라는 객체를 정의했다. 그런데 이 객체가 하는일이 무엇이라고 생각되는가? 난 이름만 보고 DataFormat 테이블에서 select 를 통해서 가져오는 데이터 집합을 관리하는 역활을 수행한다고 생각했다 (왜냐면 클래스명이 테이블명과 동일하고, 알다시피 데이터베이스 테이블은 집합의 개념 아닌가?). 그런데 코드를 분석할수록 점점 이해가 힘든 부분이 많아졌다. 그리고 결국 이 소스가 읽기 어려운 원인은 이 클래스가 DataFormat table의 하나의 데이터(1 row)  를 위한 객체였다는것을 알게 되었다.


class CDataFormat //이게 하나의row를 위함인가 아니면 전체 테이블을 위한것인가???
{ 
    public:
        ItemDataFormat Items; //이 구조체가 결국 1 row를 위한 정보이다. - - ;
        //그런데 items 라는 복수형으로 정의가 되있다. 
    ...
}

typedef struct __ItemDataFormat //1 row를 위한 정보
{
    char    strPackageId     [LEN_PACKAGE_ID    +1];
    char    strServiceName   [LEN_SERVICE_NAME  +1];
    char    strGroupName     [LEN_PROCESS_NAME+1];
    char    strComponentType [LEN_COMPONENT_TYPE +1];
    char    strDataName      [LEN_FORMAT_NAME  +1];
    char    strStructureCd   [LEN_STRUCTURE_CD +1];
    ....
}ItemDataFormat;

원래 내가 생각했던 테이블의 데이터 집합관리는  CDataFormatVect 이라는 클래스명으로 존재했다.  약간만 생각을 해볼수만 있다면 대부분의 개발자는 먼저 하나의 row 를 위한 DataFormatTableRow 클래스를 정의하고, 이 row들의 집합을 위한 DataFormatTable객체를 정의했을 것이다. 이소스에서의 문제를 단순한 네이밍의 문제라고 볼수도 있겠으나 소스를 계속 분석하면서 느낀점은, 결국 잘못된 추상화 였다. 하나의 테이블에 대한 추상화로 객체를 정의하려면 결국 그 테이블의 특징을 객체로 표현해야 한다. 그리고  테이블의 특징은 당연히 개별 데이터들의 집합이다. 하지만 이를 객체 정의에 반영하지 못한 것이다. 

이렇듯 추상화가 잘못되면 잘못된 클래스를 만들게 되고, 이는 결국  처음 봐서 바로 이해가 안되는  네이밍의 결과로 나타난다.  그리고 이것이 단순히 네이밍의 문제가 아니다. 리팩토링한다고 클래스명을 일괄 변경 해도 여전히 이해가 어려운 코드가 남는다. 왜냐면 처음 프로그램 작성 시 부터 객체에 대한 개념과 정의 (즉 추상화) 가 잘못된것이 그대로 나머지 코드에 계속 영향을 준 상태이기 때문에, 이게 단순한 네이밍의 문제로만 남지않게 되는 것이다. 
예를 들어보자. 거지소스는 다음처럼(피곤하게) 계속되고 있다. 

class CDataFormatVect : public CDataFormat  
{
    //자.. CDataFormatVect 이 클래스가  CDataFormat  데이터 집합을 의미한다.
    ....
    vector<CDataFormat>    vectDataFormat;
}

class CStructDataFormat 
{
    // 테이블 데이터중에 strStructureCd 라는 항목별 데이터집합(rows) 정의??
    ....
    char    strStructCode[LEN_STRUCTURE_CD+1];
    CDataFormatVect clsDataVect;
};

class CStructDataFormatVect 
{
    // 어떤 strStructureCd 로 CDataFormat  데이터 집합을 찾는다?
    ....
    map<string, CStructDataFormat> mapStructVect;
    ....
}  
이러한 객체 정의를 가지고 기존 소스를 보려면, 머리속에서는 부자연스럽고 짜증나는 맵핑을 계속 염두에 두어야 한다.
이 모든 문제의 근본에는 애초부터 잘못된 추상화가 존재하는것이다. 


객체를 다음처럼 정의했다면 좀더 이해하기 쉽고 유지하기도 쉬운 코드가 되지 않았을까?

1. 먼저 class CDataFormat , ItemDataFormat 은  DataFormatTableRow 라는 클래스로 통합. 이는 테이블내의 1 row 의 데이터를 의미한다.

class DataFormatTableRow 
{
    char    strPackageId     [LEN_PACKAGE_ID    +1];
    char    strServiceName   [LEN_SERVICE_NAME  +1];
    char    strGroupName     [LEN_PROCESS_NAME  +1];
    char    strComponentType [LEN_COMPONENT_TYPE+1];
    char    strDataName      [LEN_FORMAT_NAME   +1];
    char    strStructureCd   [LEN_STRUCTURE_CD  +1];
    ....
};

2. CDataFormatVect 클래스는 DataFormatTable 이름으로 재정의.

class DataFormatTable
{ 
    ....
    vector<DataFormatTableRow>  vecDataFormatTableRow;
};

3. CStructDataFormat , CStructDataFormatVect 이것들이 정말 필요한 객체들인가? 둘을 제거하고 대신, DataFormatTable.hpp 에 다음을 정의한다.
 
typedef map<string, DataFormatTable>  MAP_DATA_FORMAT ; //key is structure_cd


거지같은 소스들을 유지/보수하는일은 정말 힘들다.

댓글 없음:

댓글 쓰기