반응형

좋은 언어와 좋은 프래임워크란 ?

언어와 framework의 발전사가 이렇다고 하고, 그러면 제목에서 적은 좋은 언어와 좋은 프래임워크란 무엇일까요?

 

가장 기본적인 기준이 되는 것이 2가지 일것 입니다.

첫번째로 프래임워크에서 제공하는 사용성과 정책이 프로그래밍 언어에서 제공하는 것과 일관되어야 합니다.

두번째는 일반적일 것 같은 동작은 그렇게 동작하도록 해야 합니다.

 

위에서 예기한 것들을 좀 살펴봅시다.

우선 첫 번째 "프래임워크와 언어의 일관성"에 대한  예를 들면, 

프로그래밍 언어에서  var A =  a; 라는 코드가 A에 a를 copy하는 것이라고 정의하고 있다면,  이 언어로 작성된 프래임워크의 MyObject obj = OldObj; 의 코드 역시 obj 에 OldObj의 내용들을 copy하는 것이어야 합니다. 그런데 만약 여기서, obj에 copy 하는 것이 아니라 OldObj의 내용을 가리키도록 동작하도록 작성되었다고 한다면,

obj의 내용을 변경했을때 왜 OldObj의 내용이 바뀌는지등에 대한 의문을 만들게 됩니다.

또는 실제 obj가 쓰일때 copy를 한다거나, obj.sync() 를 불렀을때 실제 복사가 일어난다거나 한다면, 사용자 입장에서 '=' 가 불렸을때 copy가 될 것을 기대하고 퍼포먼스를 높이기 위해 튜닝을 했는데, 실제로는 다른 timing에서 data가 copy 되기 때문에 제대로 된 튜닝을 못하게 되는 상황이 발생합니다.

이는 사용자에게  framework에서 제공하는 "=" 동작에 대해 혼란을 주는 결과가 됩니다.

이는 실수를 유발하거나 직관성을 떨어뜨리게 됩니다.

 

두번째 "일반적일것 같은 동작" 대한 예를 들어보자면,

ImageViewer class 가 있고, ImageViewer에는 Draw()라는 함수가 있습니다. 이 Draw() 라는 함수는 ImageViewer의 image를 화면에 그려주는 기능을 하는 함수입니다.

Draw()기능을 좀더 세밀하게 정의 해보자면, 

1. Draw()는 ImageViewer가 들고있는 display용 context에 image를 drawing하는 함수라고 합시다.

2. Draw()가 불렸을때 화면에 보이지는 않고 framework의 rendering 시점에 이 display context의 내용이 화면에 출력되는 방식입니다.

그런데 이 class 개발자가 Draw()가 불렸을때 image를 직접 display context에 drawing하는 것보다, rendering 시점에 display context에 drawing 하도록 하는것이 성능상 이득이라고 판단하여, Draw()함수가 나중에 drawing이 필요하다는 flag만 설정하고 나오는 것으로 정의했습니다.

Draw() 함수에 대한 정의가 좋은 방향으로 결정 된것 일까요? 옳다 그르다의 표현은 좋지 못하고, API의 직관성을 흐리는 방향으로 간것 같다는 표현이 맞죠.

 

만약 위와 같은 경우에 사용자는 ImagViewer의 Draw()호출한 다음에 Display context에서 draw된 image를 꺼내와서, 그위에 text를 더 그리려는 작업을 하려 했다면, 전혀 엉뚱한 결과를 얻게 될것입니다.

여기에서 과연 Draw()라는 함수이름에서 보여주는 직관적인 동작은 무엇인가요? 바로 drawing입니다.

즉, 만약 ImageViewer의 성능을 높이기 위해서 flag 설정을 하여 Lazy update을 구현 할 생각이었다면, 다른 이름을 사용하는게 옳습니다.

많은 방법이 있죠. requestDraw나, Invalidate(windows에서 많이 사용하는 용어죠), SetRenderingTimeDrawEnabled() 등 API이름에서 해당 목적이 명확하게 나오도록 하는 것이 더 낳았을것 입니다.

 

framework은 직관성을 유지 하면서, 일반적인으로 예상할 수 있는 동작을 기본 동작으로 하고, 다양한 옵션을 제공하는 방향으로 되어야 한다고 생각합니다.

 

다음과 같은 항목들이 대표적인 사항들일 것입니다.

- Sync call와 Async call의 구분 

- lazy update와 immediate update 

- resource 소유권에 대한 규칙 

 

이와 같은 부분들이 아마도 platform에서 일관되게 동작하도록 정의하고 정리하기 어려운 부분일 것입니다.

 

특히 Resource 라고 표현한 객체의 lifecycle 관리 즉, 객체를 생성한 주체와 삭제하는 주체에 대한 것은 아주 자주 접하는 케이스이기 때문에, 명확히 하고 일반적으로 방법(개발자들이 흔히 예상할 수 있는 )으로 제공하는 것이 맞습니다.

 

 

아주 특별하고 획기적인 기능이라 하더라도, 그게 일반적인 상식이나 생각에서 벗어난다면, 좋지 못하겠죠.

 

몇가지 예를 들어보겠습니다.

 

DDD::CreateObj()

{

   ABC* obj = new ABC;

   Dock.Set(obj);

}

 

라는 아주 간단한 code가 있다고 합시다. 이 경우 obj는 누가 관리하는 것이 맞을까요?

1. DDD class에서 해야한다.

2. Dock에서 하는 것이 맞다.

3. 가비지 컬랙터(Garbage Collector)에 맡긴다.

 

 

또 다른 예를 들어보자면, array와 list가 있다고 합시다.

C++로  SoCool이라는 프래임워크 를 개발하고 있는 탐 이라는 사람이 있다고 합시다.

탐은 Array 와 List 를 만들때, "왜 다른 언어들은 index를 0 부터 시작하지? 그래서 개수가 10개 이면 0~9까지 카운트 되게 했지? 이건 일반인들은 상상도 할 수 없는 일이야" 라고 생각했습니다. 그래서 array의 index를 0이 아닌 1부터 시작하도록 했습니다.

어떻게 생각하시나요?

 

이 생각을 바탕으로 하나씩 짚어봅시다.

 

Array의 index는 프로그래밍 세계에서는  일반적으로 0부터 시작하는 반 열린 집합 형태로 표기 됩니다.

즉 개수가 10개인 array는 0<= index<10 으로 표현하는 것이 일반적인 표현 방식이죠. 이유는 memory와 관련 있는데, array 의 변수명은 그 변수가  잡고있는 memory의 시작 위치를 표시합니다. 따라서 Array[0]의 표시는 address + 0의 위치 시작 위치로 표시되는 것이죠.

이를 실생활에서 처럼 첫번째 item이니까 Array[1] 로 표현 한다면, address + 1 -1 과 보정을 하거나 아니면, 메모리상 address +1  위치 부터 data를 사용하도록 해야 합니다. 

이 두가지 모두 Computer 구조랑 잘 맞지 않는 표현 방법이 되죠.

(생각하기 나름이긴 하지만 시스템 이나 메모리구조 등을 이해하고 있는 많은 프로그래머들의 일반적인 생각은 이렇다는 것입니다.)

 

이러한 이유에 의해서 인지는 모르지만, 결과적으로 보면, 아래와 같은 의미가 되었습니다.

개념 상 뭐 어떻게 생각하느냐에 따라 다를 수 있습니다. 하지만 수학의 집합 형태로 생각 해봅시다.

우선 수학에서 어떤 집합을 표현 할때 열린 집합, 닫힌 집합, 반 열린 집합 등의 명칭으로 분류를 하기도 합니다.

무슨 의미냐 하면, 1 부터 9까지의 숫자들의 모음(1과 9을 포함하는)을 위에서 예기한 집합으로 표현하자면 다음과 같습니다.

1. [열린집합] 0<집합<10   즉, 경계인 0을 포함하지 않고, 10을 포함하지 않는 집합.

2. [닫힌집합] 1<=집합<=9  즉, 경계인 1을 포함하고 , 9는 포함하는 집합.

3. [반 열린 집합] 1<=집합<10  즉, 경계인 1을 포함하고 , 10을 포함하지 않는 집합.

4. [반 열린 집합] 0<집합<=9   즉, 경계인 0을 포함하지 않고 , 9는 포함하는 집합.

 

Computer programming 에서는 memory addressing 방식 때문에 자연스럽게 3번 형태를 띄게 되었습니다.

Computer에서는 시작을 1부터가 아닌 0부터 시작하고 있기 때문에, 10개 라는 의미가 0~9까지를 의미하는 것이 되었기 때문이죠.

 
탐은 array에 대해서는 이해를 했고, 리스트는 고치고 싶었습니다. "그래 . array는 메모리 상에 존재하는 특정 블럭을 표현 한것이니가 그렇다 치고, 그럼 list는 논리적으로 head 와 tail을 따로 구성할 수 있고, 가변적인 데이타 구조이니까, 앞으로 우리는 list에서는 index가 1부터 시작하도록 할거야."

라고 생각하고 list의 index를 1부터 시작하는 것으로 제공하기로 하였습니다.

 

만약 list 의 index를 1부터 시작하겠다고 하는 것은, "2번 닫힌 집합으로 표현 해라." 라는 의미가 되는 것입니다. 즉, 10개가 있다는 의미가 1~10까지 닫힌 집합으로 표현해야 한다가 되는것이고,  code로는   1<= index <=10 이라는 의미가 되는 것이죠.

array에서는 0<=index<10 이고, list에서는 1<=index<=10 이라고 표현 하면, 결국 data의 집합을 표현 하는 자료 구조인데 한 언어나 프래임워크 내에서 서로 다른 규격을 제시 하는 것이 됩니다. 

 

이런 비슷한 기능을 제공하는 class들이 각각 다른 기준의 동작을 유도하는 것은, 어플리케이션 개발에 수많은 혼선을 줄 가능성이 있습니다.

단순히 list와 array의 차이가 아니라, 어떤 API들도 직관적으로 접근 할 수가 없고, 숨은 의도들을 파악해야 하는 상황이 발생하죠. 

위의 경우, 어플리케이션 개발자는  "아 array는 memory 블럭을 잡는 것이니까 index는 0 부터 사용해야 하고, list는 동적이기 때문에 메모리 블럭과 관련이 없어 그러니까 프로그래머가 아닌 일반적인 사람 입장에서 생각해서  index가  1부터 인거야, 이건 꼭 외워야 해. 나중에 index array와 list간의 index변환도 고려 해야 하니까. 이런게 또 뭐가 있을까? Stack ? Stack은 어떻게 동작하지?" 라는 생각을 하게 할겁니다.

 

 

전반적인 S/W 도메인 별로 보면 여러 주제들 예를 들면 Synchronous와 Asynchronous, mutable, thread, lock, ipc, data binding, runtime, reflection 등등, 이러한 요소들을 프레임워크와 언어에 대해서 일관성, 과 일반적이고 상식적인 기본 동작과 컨셉을 가져가야 할 것인지를 잘 정리하고 제공되어야 좋은 플랫폼 또는 프래임워크 라고 할 수 있을 것입니다.

 

이런 이유에서 규모가 큰 S/W 회사들은 , 프래임워크 구축하려 할때, 기존의 언어로 새로운 패러다임을 반영하기 힘들거나 , 프래임워크에서 제공하려는 기능이 기존의 언어로 표현이 힘들 경우, 새로운 언어들을 만들기도 하는 것입니다. (C#, Java가 대표적이랄까요?)

 

 

'개발 Note > UI Framework 개발하기' 카테고리의 다른 글

Core Image Concepts  (0) 2011.09.20
stdafx.h 를 없애보자!  (0) 2011.08.18
CoreAnimation in iOS  (0) 2011.07.13
key - value 관리  (0) 2011.06.08
[애자일] 릴리즈 계획 과정에서 가장 중요한 것들  (0) 2010.10.29
반응형

프로그래밍 역사에서 빼놓을 수 없는 C와 C++ 언어에 대해서 얘기해 보고자 합니다.

1. C에서 C++



C 언어는 벨 연구소에 일하던 데니스 리치가 B언어(Bell 연구소에서 따와서 B라고 명명함)를 개선하여 만들었습니다.

이당시 상황은 Unix에서 사용할 프로그래밍 언어가 필요한 상황이었고 이 언어가 개발됨에 따라 Unix의 바탕이 되는 프로그램들은 거의 모두 코드가 C언어로 만들어지게 됩니다.

리눅스 역시 Unix를 모델로 만들어지면서, 리눅스의 바탕 역시 C언어로 만들어지게 됩니다.

(리눅스는 리누스 토발즈가 헬싱키 대학원생 시절 교수가 교육용으로 만든 미닉스의 기능에 만족하지 못하고 새로 개발하게 된 OS입니다.)

때문에 C언어가 크게 확산되고 발전된 것은 Unix나 Linux같은 OS의 개발과 확산에 의한 영향이 크다고 봅니다.

C 언어는 이처럼 System programming에 적합한 언어로 자리 잡았고, 현재까지도 Linux나 Unix는 사용되고 있는 OS이고 그 외의 여러가지 Kernel 들이나, OS들 역시 System level의 code들은 거의 C언어로 이뤄졌다고 볼 수 있습니다.


그 후 S/W OS의 발전은 system S/W에서 점점 Ui, Network, Service, Game, Graphics, Application 등 다양한 영역 들로 넓혀가게 됩니다.

그중 대표적인 것이 GUI 환경이죠. 

이 GUI 환경은 Zerox 연구소에서 시작이 되어, Macintoch, MS window, OS/2, Nextstep  등등 수많은 OS들이 GUI 환경을 제공하게되고, 사용자 입력 역시 keyboard에서 Keyboard+mouse 로 바뀌게 되었죠.

이를 우리는 보통 Window System이라고 부릅니다. 아무튼 이로서 프로그램들은 더 복잡하고, 인터랙티브 한 기능들을 제공해야 했습니다.


기존의 어떤 특정 기능을 처리하는 방식의 절차적인 프로그래밍 방식으로는 이런 환경에서 다양한 사용자 요구사항과 여러가지 입력들을 체계적으로 처리하기에는 무리가 있었습니다.

이때부터 나오기 시작한 개념들이, 객체지향 설계, 이벤트 기반 방식(Event driven), 등등의 개념이 나오게 됩니다.

그러면서 실제로 객체지향 설계를 받아들일 수 있는 시뮬라 67이라는 언어가 만들어졌고 , 이 언어는 다른 언어들에 영향을 미치게 됩니다.

C 언어 역시 객체지향 패러다임을 적용하기 위해서 C++이라는 C언어 트랜슬레이터가 개발됩니다. 향후 C++은 단순 트랜슬레이터가 아니라 독립된 언어로 그 과정에서 C언어와의 Code 호환성을 유지하면서 멀티 페러다임 언어로 자리잡게되죠.


즉 C++은 C언어가 복잡하고 고도화 되어가는 S/W 환경에서 객체 지향 설계라는 획기적인 S/W설계방식을 받아들이 위해 만들어졌다고 보시면 되겠습니다.


 !!!!:  C++ 언어는 초창기 C언어의 트랜슬레이터로 구현되었었고, 때문에 C++로 작성된 코드는 C로변환된뒤 컴파일이 되었습니다.

(참고 wikipedia : ko.wikipedia.org/wiki/C++) 

그래서 지금도 C->C++로의 환성이 유지되고 있고 C언어의 문법을 그대로 받아들이고 있습니다. 즉 C++은 객체지향 언어라기 보다는 절차지향 언어에 객체지향 패러다임이 추가된 멀티 패러다임 언어라고 하는게 좀더 잘 표현한 것이라 할 수 있겠네요.(여러 자료들에서도 이런 표현을 사용하고 있죠.)

C에서C++로 확장된 같은 지류의 언어라고 인식하는 것도 이런 이유일 것입니다.


2. "C++이 느리다." 


사람들이 C 언어를 다른 언어와 비교할때 주로 C++과 비교를 많이 하는 반면 Java를 같은 선상에 놓고 비교하지는 않습니다. 

이유는 언어의 성격 상 C는 system programming을 하기 위한 것, Java는 application 개발 또는 application programming을 하기 편한 것으로 인식하고 있기 때문에 이에 대한 비교는 별로 하지 않는 반면, C / C++에 대해서는 같은 선상에 놓고 비교할 때가 많습니다.


이런 비교의 바탕은 사실상 System level의 programming을 하는 쪽에서 가 많이 나오는 편입니다.

(C++로 System software를 개발할 필요성이 있는가를 염두해 둔것 이라고 봐야 할까요?, 아니면 C에 익숙해있는 System S/W개발자들은 C++언어가 못마땅한걸까요?) 

비교의 주된 내용이 모두 Performance에 맞춰져 있습니다. 즉, C++은 C보다 느리다는 것입니다.


이런 성능 를 하게 되면, 그리고 왜 일부 사람들은 C가  C++ 보다 성능이 좋다고 생각하는 것일까요?

C++언어는 객체지향 언어가 아니라 멀티 패러다임 언어입니다. 즉, 용도에 맞춰 코드를 작성 할 수 있기 때문에, 퍼포먼스를 해치는 그런 코드는 굳이 객체화 하거나 할 필요가 없다는 것인데 C++은 무조건 객체로 만들어야 한다는 선입견이 적용된 경우가 아닌가 싶습니다.


일단 성능 얘기가 나왔으니 이야기를 이어가 보겠습니다. 성능 의 주요 주제는 virtual function에 대한 얘기를 합니다.

자, 그럼 아래의 code를 테스트 해보면 어떤 결과를 얻을까요?
loop 1000만번 을 돌면서 tempbuf에 hello라는 단어를 copy 하는 코드입니다.
조금이라도 공정하게 테스트 하기 위해서, test_tool()이라는 동일한 함수를 부르도록 하였습니다.
virtual funtion 테스트는 class 는 inherit 해서 virtual 함수를 상속받아 구현한 코드이고,
function pointer 테스트는 일반적으로 function pointer를 사용할 때 null 채크를 하기 때문에 이 코드를 포함하여 넣었습니다.
  - 이 부분에서 논란이 있을 수 있는데 제 생각은 이렇습니다.

C++ compiler는 compile time에 virtual function을 syntax를 검사하는 형태로  pure virtual 함수를 사용하는 경우를 필터링 해줍니다.

하지만 C의 경우 function pointer의 안정성 검사가 이뤄지지 않죠. 따라서 실제 사용될때는 사용자에 의해 어디선가 해야 합니다.

때문에 아래와 같이 가장 기본적인 null 채크를 추가하였고 일반적으로 실사용에서 함수 포인터를 사용한다면  꼭 해야하는 기본적인 로직이라 생각해서 넣은 구문입니다.

 (사실 C의 function pointer 임의의 function으로 대처될 수도 있어서 c++ virtual function에 비해 risk가 더 있기 때문에 실제 현업에서는 더 많은 컨디션 체크를 하고 있다고 생각됩니다.)


돌려보셨나요? 결과는 어떤가요?

#include <stdio.h>
#include <sys/time.h>
#include <iostream>

using namespace std;

class _ElapsedTime
{
public:
    _ElapsedTime(const char* tag = NULL)
    {
        struct timeval tv;

        tagName = tag;

        gettimeofday(&tv, NULL);
        start_time = (long long)tv.tv_sec * 1000000LL + (long long)tv.tv_usec / 1LL;
    }

    ~_ElapsedTime(void)
    {
        long long end_time;
        struct timeval tv;
        gettimeofday(&tv, NULL);
        end_time = (long long)tv.tv_sec * 1000000LL + (long long)tv.tv_usec / 1LL;

        fprintf(stderr, "%s: elapsed time: %lld us\n",tagName,  end_time - start_time);
    }


public:
    long long start_time;
    const char* tagName;
};



char tembuf[2048];

void test_tool()
{
  sprintf(tembuf,"%s","hello");
}


// test for virtual function

class test
{
public:
  virtual void test_func()=0;
};



class test_inherit : public test
{
public:
  virtual void test_func();

};
void test_inherit::test_func()
{
  test_tool();
}


// test for function pointer

typedef void (*test_func)();

void test_try_func()
{
  test_tool();
}



int main()
{

  test_inherit test;
  test_func func_pointer = &test_try_func;

  int i = 0;
  const int loop_end = 10000000;

  sleep(1);

  {
    _ElapsedTime try1("function pointer");
  for(i=0;i<loop_end;i++) // test function pointer
    {
      if(func_pointer)func_pointer();
    }
  }

  {
    _ElapsedTime try2("virtual function");
    for(i=0;i<loop_end;i++)  // test virtual function
    {
      test.test_func();
    }
  }

  return 0;
}



여러번 돌려봤을때 아래와 같은 결과를 얻었습니다. 위의 if 문이 큰 영향을 주더군요.

if 문이 빠졌을때는 비슷합니다.


&"warning: GDB: Failed to set controlling terminal: \353\266\200\354\240\201\354\240\210\355\225\234 \354\235\270\354\210\230\n"

function pointer: elapsed time: 41011 us

virtual function: elapsed time: 31175 us

Debugging has finished


Debugging starts

&"warning: GDB: Failed to set controlling terminal: \353\266\200\354\240\201\354\240\210\355\225\234 \354\235\270\354\210\230\n"

function pointer: elapsed time: 39163 us

virtual function: elapsed time: 31143 us

Debugging has finished


Debugging starts

&"warning: GDB: Failed to set controlling terminal: \353\266\200\354\240\201\354\240\210\355\225\234 \354\235\270\354\210\230\n"

function pointer: elapsed time: 39903 us

virtual function: elapsed time: 31362 us

Debugging has finished


Debugging starts

&"warning: GDB: Failed to set controlling terminal: \353\266\200\354\240\201\354\240\210\355\225\234 \354\235\270\354\210\230\n"

function pointer: elapsed time: 35646 us

virtual function: elapsed time: 31281 us

Debugging has finished


두번째 성능 이슈는 객체의 생성 소멸에 따른 비용 문제입니다.
C++ 로 class를 작성하게 되면, 생성과 소멸시 constructor 와 destructor 가 불리게 되고, 함수에서 객체를 return 하거나 operator에 의해 임시객체사 생성, 임시객체 복사, 삭제가 발생합니다.

C++11 에서는 r-value reference가 지원되어 move semantic 등을 통해 임시객체에 대한 overhead는 약간은 해결했지만, 기본적으로 객체의 생성 삭제 부분의 오버해드는 존재하게 됩니다. 이는 퍼포먼스에 영향을 주기 마련입니다.


특히 Integer, Float, Char과 같은 basic type들을  class로 정의해서 사용한다면, 여러가지 operator들을 제정의 해서 사용하게 되고, 객체를 return하게 될때 객체 copy가 일어나고, 만약 임시객체를 만들게 되면,임시객체 생성, 소멸, 복사가 일어나게 오버해드가 발생합니다.

c++ 가 그동안 이런 측면에서 어택을 받아왔던 부분들 일것입니다. 


기본 타입(primitive type)에 대해서는 언어의 차이에 대해서 오버해드는 없겠지만, 객체를 만들어서 다루는 부분에 대해서는 분명 오버헤드라고 볼 수 있습니다.

이 부분은 객체 라는 설계요소가 포함되어 발생하는 부분으로 같은 설계를 놓고 본다면, C나 C++이나 아마 크게 차이나지 않을 것 입니다.

예를 들면 이런거죠.


// A를 다루는 List 형태의 manager 설계

struct A{...};

struct Node{struct A a; struct B* pNext;};

struct Manager{ struct A* pcurrent; struct Node* pList; } ;


void clear_A(A* a);


Manager* create_manager();

//destroy 함수는 모든 node를 살펴보면 삭제하는 코드.

void destroy_manager(Manager* p){

   if(p == NULL) return;

   p->pcurrent = NULL;

   struct Node*pNode = pList;

   while (pNode){

      struct Node* pCur = pNode->pNext; 

      pNode= pNode->pNext;

      clear_A(&pCur->a);

      free(pCur);

   }

}


class A{...  ; A(); ~A();};

class Node{

public:

    A a;   Node* pNext; 

    Node(); 

    ~Node(); 

    Node* getNext();

};

class Manager{ 

    A* pcurrent;     Node* pList; 

    Manager(); 

    ~Manager();

}; 


Manager::~Manager(){

  pcurrent =  nullptr;

  Node* pNode = pList;

  while(pNode){

    pCur = pNode;

    pNode = pNode->getNext();

   delete pCur;  <-- 이 부분이 왼쪽의 clear_A() free() 를 호출한 부분과 같은 부분

  }

}




이 다음 주제가 설계를 반영하는 부분에 대한 얘기 입니다.



3."C로도 OO를 구현할 수 있다."


C와 C++의 비교 내용은 주로 Performance 대한 이슈 또 하나가 C로도 객체지향을 구현할수 있다는 의견이 주 내용 일 것입니다.

이를 단순히 표현하면, "C 는 C++보다 빠르고 C로도 객체지향적으로 구현할 수 있다" 가 되죠.

그럼 아키텍쳐링 측면에서 좀 살펴봐야 할것 같습니다.


요즘은 어플리케이션이든 아니면 서비스 모듈이든 상당히 복잡해졌고 규모가 큰 편입니다.
그래서 소프트웨어의 구조적인 측면을 중요하게 생각합니다. 그리고 S/W를 소개할때에도 이런 부분들이 잘 반영되도록 해서 구조 설명을 해야되어있습니다. 때문에 설계는 S/W에서 빠질 수 없는 부분입니다.

객체지향 프로그래밍이라는 개념이 나오고 나서 S/W의 설계 분야는 OO 기반으로 상당한 발전들이 있었습니다.
Component, OOAD, Design pattern 등이 대표적인 예인데, 이러한 개념들은 UML 과 더불어 강력한 프로그래밍 설계 툴이 되었습니다.

C, C++ 은 모두 절차적 프로그래밍 언어이긴 하지만 C++ 객체지향 페러다임을 담고 있는 언어라고 얘기 합니다.
그래서 C++는 UML이나 객체지향 설계의 내용을 코드로 옮기기가 쉽습니다.
그렇기 때문에 설계와 코드를 동일한 퀄리티로 유지 할 수 있고, S/W의 구조적인 문제점들을 파악하거나 개선 방향을 잡는데 매우 편리합니다.


이런 얘기가 나오면, "C로도 OO 개념을 적용할 수 있다" 라고 합니다.

하지만 언어적인 한계가 있기 때문에 아무리 구조적으로 잘 작성한다 하더라도, OO를 지원 하는 언어와 동일한 편의성을 제공 할 수 없습니다. 안타까운 현실이죠.

예를 들면, 아래와 같이 구조를 잡아나가면 c 나 c++이나 다를게 없다고 생각 할 수도 있습니다.
그렇지만 자세히 뜯어보면 그렇지 못하다는 것을 알 것입니다.

<test.c>

typedef struct
{
  int (*getter)();
  int (*setter)();

  int value;
}CStructPrototype;


void test_struct()
{
  CStructPrototype st;
  st.getter();  <- 올바른 동작일까요?
  st.setter();

}



실제 정상적인 코드를 작성하려면 아래와 같이 되어야 할 것입니다.


<test.c>


typedef struct _CSTRUCT
{
  int (*getter)(struct _CSTRUCT* self);
  void (*setter)(struct _CSTRUCT* self, int value);

  int value;
}CStructPrototype;

int CStructPrototype_Getter(struct _CSTRUCT* self)
{
  return self->value;
}

void CStructPrototype_Setter(struct _CSTRUCT* self, int value)
{
  self->value = value;
}

void CStructPrototype_Construct(CStructPrototype* st)
{
  st->getter  = &CStructPrototype_Getter;
  st->setter  = &CStructPrototype_Setter;

  st->value = 0;
}

void test_struct()
{
  CStructPrototype st;
  CStructPrototype_Construct(st);
  st.getter(&st); 
  st.setter(&st, 10);

}



자, 이러면 된걸 까요?

멤버 함수 처럼 보이는 getter와 setter에 인자로 자기자신(st&)이 들어가도록 되어있습니다.


여전히 문제는 있습니다. 다음 코드를 봅시다.



void test2_struct()

{
  CStructPrototype st;
  CStructPrototype st2;
  CStructPrototype_Construct(st);
  st.getter(&st);
  st.setter(&st, 10);
  st.setter(&st2, 10); // (A)<- st2의 data가 변경됨. 
}


(A)를 보면 st의 member함수처럼 보이는 setter에 st2가 들어갑니다. 그래서 st2의 내부 변수이 value가 변경되게 됩니다.

setter의 구현체인, CStructPrototype_Setter 의 내부에서도 인자로 받은 st가 this 인지를 확인 할 방법도 없습니다.

즉 function pointer는 member함수가 될수 없는 이유입니다.



또, 만약 상속을 생각한다면,
 getter와 setter의 prototype이 좀 바뀌어야 합니다.

  int (*getter)(void* self);
  void (*setter)(void* self, int value);

그리고 구현부에서 void* 를 casting해서 사용해야 합니다.

함수 overriding 구현을 위해서는 더욱 더 복잡해지기 마련이고, 이를 표기할때 단순화 시키기 위해 macro를 작성해 사용한다면, 코드의 가독성이 떨어지게 되고,  함수 pointer와 연결된 함수를 직관적 알아보기 힘들게 됩니다.



그외 ,

operator overloading 과 function overloading 등은 방법이 없습니다.

또 exception handling 역시 지원되지 않기 때문에 error handling 방법이 유일하게 return값을 확인 하는 방법과, 모듈 내부에서 last error 와 같은 로직을 추가로 해결 할 수 밖에 없습니다.

(exception에 대한 안좋은 선입견을 가지신 분들도 있을 수 있는데요. exception은 결함 내성(fault-torelant) 소프트웨어를  쉽게 작성할 수 있도록 도와주는 것입니다. - 참고 : 왜 예외를 쓰는 게 좋을까요? 

 : 논점에서 좀 벗어난 얘기라 서 읽는데 혼란을 줄 수도 있는 내용이라 gray 로 덮었습니다.


오히려, 이런 코드는 

CStructPrototype_Getter(&st);

CStructPrototype_Setter(&st, 10);

함수 자체를 호출하도록 하는 것이 훨씬 C 스럽고 안정성을 높이고 시인성을 높이는 것입니다.


그런데 종종 위와 같이 struct과 function pointer를 이용해서 OO 구조를 잡는 코드들을 볼수 있습니다.

왜 C programming을 할때 위와 같이 struct과 function pointer를 가지고 class 와 비슷한 구조를 갖추려고 할까요?
제 생각에는 이런 프로그래밍을 하는 사람은 아마도 OOP를 알고 있는 사람일 것입니다.

그래서 C언어 밖에 사용할 수 없는 환경에서 OO를 흉내내고 싶어 하는 것 입니다.

(물론 그렇지 않은 경우도 있습니다 만, 때때로 과도하게 이런 흉내를 내는 코드를 볼때가 있습니다.)


만약 그러시다면, C++을 사용하십시오.


<test.cpp>


class CppClsss
{
public:
   int getter(){return value;}
   void setter(int v){ value = v;}
private:
int value;
}

void test2_struct()
{
  CppClass st;
  st.getter();
  st.setter(10);
}


더 이상 struct를 class 처럼 꾸밀 필요도 없고, 아래와 같이 OO로 작성된 module 들은 객체의 member function을 사용하면 되고,
C function 제공하는 것들에 대해선 C function을 사용하면 되기 때문에, C 개발자 입장에서는 C 사용성이 확대되었다 정도로 인식만 하면 되지, 기존의 로직 구조를 변경할 필요는 없습니다.




자 그럼 다음 질문, 왜 C++로 작성하지 않을까요?
이 질문이 C 언어를 주로 다루는 개발자 분들은  C로 작성된 모듈을 모두 OO 기반으로 바꾸지 않는가? 라고 오해의 소지가 있습니다.

Refactoring 관점이나 Architecturing 관점에서 접근하더라도 C로 코딩 되어 있는 코드들을 객체 기반으로 코드로 모두 바꾼다는 것은 잘못된 설계가 될 가능성이 있고 모듈의 동작 변화도 발생할 가능성이 높습니다. 그만큼 리스크가 있는 작업입니다.

이런 리스크를 안고 굳이 기존의 코드를 OO 기반의 코드로 변경할 필요는 없다고 봅니다.


잠시 프로그래밍 언어와 용도에 대해서 잠시 고민해 봅시다.
C로 되어있는 system call을 처리하는 모듈이 있는데, 이 모듈은 system call에 의한 interrupt 루틴이 함수 하나로 작성되어있고,  runtime 에 system service 에 등록 되도록 되어있습니다.

그럼 C++ compiler로 교체 함으로 해서 이런 간결하고, 뛰어난 코드를 객체로 재 정의 하고 객체에 interface를 만들고, service call에 등록하기 전에 객체 instance를 생성하고, 연결해주는 static 함수를 service에 등록해야 할까요? 이런 코드들은 비록 객체로 바뀌었다고 하지만, 여전히 C로 구현했던 내용과 큰 차이가 없을 것이고 크게 개선될 것도 없을 것입니다.

C++ compiler로 변경 했다고 해서 기존의 코드를 모두 새로운 객체지향 페러다임으로 변경할 필요는 없다고 생각합니다.
대신 C 효율적으로 대처하지 못했던 부분들에 대해서 C++ 이 보완해 줄 수 있다는 것이 가장 중요한 핵심인것 같습니다.


4.Framework


가끔 "이 프래임워크는 Java로 되어있어", "Facebook 은 Social service framework 이다" 이런 표현들을 들어봤을 것입니다.


framework 비슷한 용어로 우리는 platform이란 단어를 사용합니다.

Platform은 뭐고 Framework은 무엇일까요? 이것들은 비슷하게 들리는데 어떤 차이가 있는 것일까요?


Platform이라는 용어는 Hardware architecture ,Operating system, runtime library를 포함한 용어로 많이 사용되고 있습니다. 따라서 어떤 디바이스가 동작하기 위한 모든 요소를 합쳐서 platform이라고 부릅니다.


Framework은 특정 platform위에서 어플리케이션이나 서비스를 개발할 수 있도록 제공되는 logic을 기능화하여 제공하는 것을 의미합니다.  library와의 차이가 뭐냐? 라고 한다면,

Library는 흔히 API들의 집합을 의미하는 것으로 그 이상의 의미는 별로 없습니다. 그러나 framework은 이런 library와 platform의 기능 들을 장 정리해서 특정 기능이나 서비스를 구현하는데 필요한 business logic을 제공하는 것을 입니다.


즉, 단순 API set이 아니라, logic을 포함한다고 보시면 됩니다. 잘 이해가 안 가나요?


쉽게 비교 표현 하자면, Windows platform, Graphics library, Ui framework 이렇게 봅시다.

Windows 는 desktop 기기나 mobile 기기를 구동하기 위한 architecture와 OS 를 담고 있기 때문에 platform이라고 부릅니다.

그 Windows 위 에는 수많은 library 들이 있습니다. socket, network library , openGL, graphics, GUI library등이 있죠.


그리고 Net framework 이나, MFC 와 같은 framework은 이런 graphics library와 open gl, socket, http등을 이용해서 application을 쉽게 구현 할 수 있도록 해주는 것입니다.

또, Facebook에서 자신들의 서비스를 쉽게 접근해서 사용할 수 있도록 제공하는 facebook framework 등과 같은 서드 파티에서 제공하는 framework이 있습니다.


즉 Framework은 어떤 기능을 구현하기 위한 low level의 API를 제공하는 library(socket,gl 과 같은) 가 아니라, network connection manager 나 game 개발을 쉽게 개발할 수 있도록 socket을 이용하여 http protocol을 쉽게 접근 할 수 있는 http class 를 제공하는 것이고, http class들을 이용해서 facebook에 쉽게 접근 할 수 있도록 facebook class나 facebook web api set을 제공하는 package를 말하는 것입니다.


OS 제조사(MS, Apple, Google등)에서 제공하는 application development framework(SDK)은 다양한 sub framework이나 module 들을 포함하고 있습니다.


또 .Net 이나 android의 framework들이 대표적이라고 할 수 있는데요. 크게 Application life cycle 을 handling 할 수 있는 runtime, Ui framework, content management, media, image , graphics , network , censor 등의 sub module들을 포함하고 있습니다.


이런 sub module( 또는 sub framework)등을 architecture 로 표현 할 때 흔히 package, class, namespace 등으로 표현하고, UML과 같은 디자인 툴로 각 class 및 package 들의 관계를 표현 합니다.


이정도로 정리해서 platform과 framework의 정의를 나눠봤고, 과연 C언어로 framework을 제공하는 것에 대해서 좀더 심도 있게 생각해보고 싶습니다.

이 의견은 객체지향 패러다임을 적용할 수 없는 언어가 과연 application 개발을 위한 framework으로 적합한가에 대한 물음으로 보시면 될것 같은데요. 제 의견은 만들 수는 있지만 좋은 사용성을 제공하기는 힘들 것이라 생각됩니다.

여러가지 framework들은 범용적인 형태가 아닙니다. 즉 어떤 목적에 맞는 ,그 목적에 잘 어울리는 방식으로 기능을 제공합니다.

하나의 programming 언어로만 제공되는 경우도 있지만, script, XML, Editor tool, 심지어는 programming 언어까지 새로 만들어서 제공되기도 합니다. 

이러한 다양한 형태까지는 아니더라도, 적어도 OO design을 적용한 framework은 적어도 OO 패러다임을 담고 표현할 수 있는 언어로 제공되는 것이 맞겠죠.



5. 맺음말

처음에 C와 C++언어에 대한 이야기로 시작 했는데 사실 framework과 language를 같이 얘기 하고 싶었습니다.
제가 프로그래밍 언어에 대해 전공을 한 것이 아니라 제 경험과 주관 그리고 블로그나 커뮤니티에서 활동하고 있는 고수분들이 적어놓은 자료들을 많이 읽어보고 나름 의견을 정리해봤는데요.하고자 했던 얘기는 프로그래밍 언어는 환경과 목적에 따라 잘 선택하는 것이 중요하다는 것입니다
System 하부의 device driver와 같은 코드를 개발하는데, java나 C# 보다는 C가 훨씬 더 효율적이고, system의 동작에 대해 예측이 쉽기 때문에 잘 어울린다고 볼 수 있습니다.
반면, Application framework이나 service framework을 개발하여 사용자에게 interface를 제공하는데 C 로 작성된 API들을 제공한다는 것은, OO 기반의 설계를 다시 구조적, 절차적인 code로 구현하고 Framework API를 C function으로 제공한다는 의미가 됩니다
이는 설계와 동작이 불일치 할 가능성이 있고, 편의성 측면에서도 문제가 될 소지가 있습니다. 
OO 기반의 S/W 설계가 20여년 이상 자리잡고 현재는 대세가 되었는데, 복잡한 framework구성이나 탄탄한 설계가 필요한 부분에서 OO를 지원하지 않는 언어로 framework 을 구현하고 API를 제공한다는 것은 잘못된 방향이 아닌가 싶습니다.  구현, 디버깅, 안정성, 사용성 어느 하나 더 낳을 것이 없으니까요.
저는 OO 관점에서 봤을 때 C++ 역시 pointer를 지원하기 때문에  framework interface로는 좋지 못하다고 생각합니다. pointer는 객체에 대한 접근 표현이 아니라 메모리에 직접적인 접근을 의미하기 때문에 값의 변경이나, type 이 모호해질 가능성이 매우 큽니다. C와 비교했을때 C++이 더 낳다 인것이지, 다른 프로그래밍 언어들과 비교 했을때, C++ 역시 상당히 조심해서 사용을 해야 하는 언어임은 맞습니다. 복잡하구요.
(대안이 없는 경우라면 ㅠ_ㅠ 어쩔 수 없겠죠.)

최근에 다시 C++ 언어가 재조명 되고 있다는 기사를 본적이 있는것 같습니다. 모던 C++이라는 이름을 쓰기도 하는것 같던데..
다양한 프로젝트들에서 C++로 framework을 작성하고 있고, C++ 표준화도 C++11, C++14와 같은 많은 변화들이 진행되고 있습니다.
최신 C++에서는 기존의 C++이 어택받아오던 임시객체 생성과 복제 등에 대한 대안들을 제공하고 있어서 framework을 어떻게 설계하는가에 따라 퍼포먼스에 대해서도 상당한 향상을 가져 올 수 있을것 으로 보입니다.
글쓰는데 소질이 없는 건지 마무리 맨트가 좀 그렇네요.  ㅎ. 나중에 시간되면 좀더 정리해보도록 하겠습니다.
이상 마칩니다.

반응형

Animation Framework 개발하기.

UI Framework에 Animation을 접목시키면 그 파급효과는 무시할 수 없다는 결론은 이미 iOS 나 Andriod를 통해서 입증이 되었다고본다.

그런 의미에서 animation framework을 설계/구현 해보는 경험은 매우 중요하리라 보인다.

목표는 Core Animation 과 같은 rendering task가 분리된 형태의 framework 을 구현하는 것이다.

컨셉 모델 구상




구성요소

Engine
   Renderer
   Object Manager
           Object ( Image, Rectangle, Text...)
           Controller
   Animator
           Animation Object
                       ( Transform, Rotation...)

Client
   Object
   Animation
   Controller

Base
   Data type 

  2011.09.19
- Animator 완성
       : Animation - Transform, Value
       : TimeLine - Bezier, Linear Easy, FadeIn, FadeOut
- Face Tree 구축
       : Face property : Bounds , Fg color,bgColor, opacity , visible
       : RootFace, ImageFace ,BoxFace,TextFace
- Communication
       : Command, FaceCommand
       : CommandQueue, Executor
       : CommandParser
- Property
       : Value , Property ,PropertyT(template),PropertyReceiver
      



반응형

글을 읽는 도중 다음과 같은 문구를 보게 되었습니다.
Framework 없이 어플리케이션을 만드는 것은 프로그래머의 역량과 컨디션에 따라 어플리케이션의 구조가 만들어지게 된다. 라고 적혀있었는데 생각해보면 맞는 예기인것 같습니다.

Framework이라는 것이 사실상 생산성을 높이고 제공하고자 하는 기능을 구조화 하여, 개발자들에게 기능 사용구현에 대한 Guide 역할하고, 기능 동작을 보장 해주는 것이라 볼수 있습니다.

때문에 Framework없이 S/W를 개발한다면 사실상 개발자의 역량에 맡길수 밖에 없게 됩니다.

이전에 이와 같은 기능을 구현해 봤는가, 아닌가에 따라 S/W의 질이 달라지게 되겠죠.

Framework이란 무엇인가? 

 Ralph Johnson 이라는 사람은 추상클레스( abstract class) 들의 집합과 상호 협조하는 클래스들의 인스턴스 동작 방법으로 이루어진 재사용 가능한 디자인이라 정의했습니다.

프로젝트에 어울리는 잘만들어진 프레임워크를 이용한 어플리케이션은 비록 거대해 지더라도 프레임워크의 의도에 맞게 어플리케이션의 설계가 이루어집니다. 때문에 어플리케이션에 의해 System의 안정성을 해치거나, 터무니없이 낮은 퍼포먼스를 내는 일은 거의 없을 것입니다.

라이브러리와 프레임워크의 차이!
라이브러리는 개별적인 기능들을 집단형태로 묶음을 의미합니다.
때문에 라이브러리를 이용해 문제를 해결하고자 할때는 메소드의 기능을 정확히 이해하고 사용해야 합니다..

프레임워크는 원하는 기능을 제공하는 모듈의 인스턴스와 이에 수반되는 여러가지 장치 들의 상호작용까지의 묶어서 정의 하게 됩니다. 

때문에 프레임워크에서 제공되는 기능을 사용하게 되면 이와 수반된 이미 검증된 작업의 플로우까지 함께 사용하게 되는 것입니다..


이렇게 함으로 해서 어플리케이션의 코드를 줄일 수 있고, 안정성을 높이며 개발 속도를  빠르게 할 수 있습니다.



좋은 프레임워크
좋은 프레임워크를 사용하면 소프트웨어 개발 비용이 줄어들지만, 아이러니하게 좋은 프레임워크를 개발하기 위한 비용이 많이 들게 됩니다.

좋은 framework을 만들기 위해 설계에 들어가는 시간, 퍼포먼스 향상을 위해 투자하는 시간, 그리고 안정성 확보를 위해 테스트에 들어가는 시간 이 모든 것들이 비용이 되기 때문에 많이 들 수 밖에 없습니다.


Framework 설계에 들어가는 원칙들.

의존관계 역전 원칙( The Dependency Inversion principle)
인터페이스 분리 원칙(The Interface Segregation Principle)
리스코프 치환 원칙(The Liskov Substraction Principle)
단일 책임 원칙(The Single Responsiblity Principle)
개방 폐쇄 원칙(The Open-closed Principle)


프레임워크는 많은 도메인에서 여러가지 문제를 해결할 수 있도록 만들어지기 때문에 특정 도메인의 간단한 문제를 해결하기 위해 기능과 구현이 복잡해지고 코드의 크기나 객체의 크기가 커질 수도 있다.



'개발 Note > UI Framework 개발하기' 카테고리의 다른 글

STL : List  (0) 2009.01.04
Coding  (0) 2009.01.04
아키텍쳐를 잡아 나가는 일  (0) 2009.01.04
회사 일? 내 일? 그리고 블로그  (0) 2009.01.04
새로운 시작!!! framework  (0) 2008.12.29

+ Recent posts