'프로그래밍'에 해당되는 글 31건

  1. 2007.03.14 아놔 진짜 이거 참
  2. 2006.11.04 구현은 변경된다
  3. 2006.10.09 프로그래밍에 대한 나의 생각
  4. 2006.09.19 디자인 개선
  5. 2006.09.16 디자인의 퇴보
  6. 2006.04.14 감동의 순간
  7. 2006.03.03 두 능력
  8. 2005.12.21 삽질의 추억 - 사운드편
  9. 2005.10.04 엔초 페라리와 MC12
  10. 2004.01.30 스테이트
dev.log2007. 3. 14. 21:11

지금까지 했던 모든 삽질의 원흉은.. 언리얼 엔진의 appSleep은, 밀리초 단위가 아닌, 그냥 '초' 단위였다는 것이란 걸 어제 발견했다. (털썩)

2차 CBT까지 한 R모 게임을 플레이하면서 "30분 기다렸는데 캐릭터가 안나와요" 하신 분들, 죄송합니다. (꾸벅) "30분동안 해도 그림자만 보여요" 하신 분들, 역시 죄송합니다. (꾸벅) "캐릭터 머리가 계속 뻥 뚫려보여요" 하신 분들께도, 역시 죄송합니다 (꾸벅)

이제 단위가 밀리초단위가 아니라 그냥 '초'단위란 걸 알았으니, 아마 캐릭터가 나타나기까지 걸리는 시간이 1/1000로 단축될 겁니다.. (아마도) 다음부턴 잘 될 겁니다 ( __)

 

Posted by uhm
dev.log2006. 11. 4. 23:37

그렇다, 구현은 늘상 변경되기마련이다. "foo.bar"파일을 fopen으로 파일을 열었다가도 어느 순간인가는 CreateFile로 바뀔수도 있고, 한참 지나다보면 또 std::fstream 객체를 만들어야 할 때도 오는 법이다.

얼마전 회사에서 있었던 일 하나.

거대한 테이블을 파일에서 참조해야 하는 상황이다. 대략 평균 60메가 정도의 파일이 80개 가량. 결국 4.8기가의 테이블을 참조해야 하는 것인데, 이만한 크기의 테이블을 메모리에 올릴 수는 없는 노릇이다. 아무리 요새 메모리가 썪어 난다지만, 단일 테이블을 4.8기가나 메모리에 올리는 것은 미친짓이라고 할수밖에;

그래서 내 손에 들어온 애초의 구현은 윈도우의 경우는 MapViewOfFile 함수로 메모리에 파일포인터를 매핑하여 접근하는 것이었고, 리눅스에서는 mmap 함수로 동일한 일을 하고 있었다. 대략적으로, 다음과 같은 식이다.

 

void Table::init( ... )
{
    short* table = MapViewOfFile( ... );
    // short* table = mmap( ... ); // in case of linux
    // ...
}

void certain_function()
{
    short value = table[index];
    // ...
}

void another_function( ... )
{
    short another_value = table[another_index];
    // ...
}

void yet_another_function( ... )
{
    short yet_another_value = table[yet_another_index];
    // ....
}

// goes on and on .....

코드를 보면 뭔가 불안하다. 일단, table 포인터를 얻어오는 과정이 플랫폼마다 다른데, 이걸 이렇게 흩어진 코드에서 마구잡이로 접근한다면 문제가 생길 것이 뻔하지 않은가. 그래서 코드를 받자마자 테이블을 참조하는 작업을 별도의 인터페이스로 빼서 구현을 따로 하였다. 조금 안심이 된다. 구현과 인터페이스를 분리한 것이다.

void Table::init(  ... )
{
   buffer = MapViewOfFile( ... );
}

short Table::lookup(short index)
{
    return buffer[index];
}

void certain_function()
{
    short value = table.lookup(index);
    // ...
}

void another_function( ... )
{
    short another_value = table.lookup(another_index);
    // ...
}

void yet_another_function( ... )
{
    short yet_another_value = table.lookup(yet_another_index);
    // ....
}

 

문제는 윈도우에서 터졌는데, 이놈의 MapViewOfFile 함수가, 파일을 주소공간에 매핑하고 포인터를 얻어와야 하는데, 메모리가 부족하여 매핑을 못하는 사태가 벌어진 것이다. (사용메모리양 2기가) 유력한 용의자는 당연히 MapViewOfFile 함수. 이 함수가 파일을 통째로 메모리에 올릴 것이라는 혐의가 잡혔다. 리눅스에서는 문제가 안 터졌는데, 아마도 64bit 커널을 쓰고 있어서 주소공간이 넉넉하여 별 문제가 발생하지 않은듯 싶다.

 

그래서 메모리맵을 쓰는 대신, 테이블의 개별 레코드를 일정개수씩 캐쉬로 유지하면서 참조하는 전략을 택하기로 했다. 그 전의 구현이었으면 수십개의 함수에 흩어져있는, 테이블 참조 코드를 일일이 고쳐야 했겠지만, 다행히도 그 전에 구현과 인터페이스를 분리해 두었으므로, 참조 인터페이스는 그대로 두고 구현만 고치면 되는 문제였다. Table::init과 Table::lookup 함수, 단 두개.

 

결과는?

"메모리 뻑 고치는데 한 3일이면 될까요?"

"네.. 해보죠 뭐."

구현만 뚝딱뚝딱 고친다. 30분 뒤....

"다됐음. 받으셈"

"브라보! 3일걸린다면서요."

"훗훗훗"

 

구현은 항상 변경된다. 그러므로 구현과 인터페이스는 분리되어야 하는 것이다.

그럼 당신도 인정받는 프로그래머.

 

PS : 혹은, 3일동안 할일을 30분만에 끝내놓고 이틀동안 빈둥빈둥 노는 것도 추천;

Posted by uhm
misc.log2006. 10. 9. 08:45

"지정문답 XXX"놀이. 나에게는 당연한 귀결인지는 몰라도 '프로그래밍'이란 주제가 떨어졌다.

 

최근 생각하는 『프로그래밍』
 화가는 그림으로 자신의 세계에 대한 생각을 표현하고, 소설가는 소설을 씀으로써 자신의 세계에 대한 생각을 표현한다. 마찬가지로, 프로그래머는 프로그래밍을 함으로써 자신의 세계에 대한 생각을 프로그램으로 표현한다.

 화가가 처음 그림을 배울 때는 직선 긋는 훈련, 원을 그리는 훈련, 색을 보는 훈련을 거치고, 음악가가 처음 음악을 배울 때도 음을 구별하는 법, 악기를 다루는 법, 악보를 읽는 법등의 기본기를 거친다. 그 후에는 기술의 유무보다는 저작자 자신의 아이디어와 개성이 최종 저작물의 질을 결정하게 된다. 프로그래밍도 마찬가지여서, 언어의 문법, 개발툴의 사용법, 기본라이브러리, 몇가지 기초 학문의 지식만 있다면, 그 뒤의 작업은 프로그래머 자신에게 달린 문제다. 지금 하고 있는 일만 해도, 5년전에 익힌 기술 이상의 기술을 필요로 하지를 않는다. (하긴, 엔진이 5년전 엔진이니) 새기술의 습득보다는 내 스타일의 확립이 중요하다고나 할까.

 

이 『프로그래밍』에는 감동

"프로그래밍"은 '프로그램을 짜는 작업'을 지칭하는 것이므로, 나로서는 다른 사람이 어떻게 작업을 하는지 알 수 없다. 어떻게 한 소설가가 다른 소설가가 타자기를 놀려 소설을 완성해 나가는 작업을 평가할 수 있겠는가.

다만, "프로그램"이라면 평가의 대상이 될 수 있다. 내가 지금까지 접해본 것중 가장 훌륭한 프로그램을 말하자면, MS엑셀이다. 효율성부터 설계의 일관성까지 어느 것 하나 놓치지 않은 정말 훌륭한 프로그램이다. 나중에 '조엘 온 소프트웨어'를 읽고 나서 속으로 "이런 훌륭한 사람이 참여해서 만든 프로그램이라 훌륭했구나"라는 생각을 했을 정도.

* 효율성 : 엑셀은, 조엘의 말을 빌자면 "번개같이 빠른" 프로그램이다. 방대한 양의 계산을 순식간에 처리해 낸다. 단순히 컴퓨터니까 계산을 빨리 한다는 의미가 아니다. 같은 일을 하는 다른 프로그램에 비했을 때 속도가 월등히 빠르다. 계산 뿐만 아니라, 사용자 인터페이스도 번개같이 빠르다. 아래한글이나 MS워드에서 표를 만들고 표 구분선을 마우스로 드래그해서 움직일 때의 반응성과, 엑셀의 셀 구분선을 드래그해서 움직일 때의 반응성을 비교해보라. 엑셀은 정말 끔찍히도 효율적인 프로그램이다.

* 설계의 일관성 : 엑셀 매크로를 써 본 적이 있는가. 사실 매크로의 기본은 작업 내용을 기록해서 나중에 다시 반복하게 해 주는 기능이다. 그런데 MS에서는 이러한 기본 기능을 확장하고 추상화하여 '작업 내용'을 객체로 간주하고 이를 비주얼 베이직의 문법으로 접근할 방법을 마련했다. 이것이 VBA, visual basic for application 이고, 엑셀에는 당연히 엑셀VBA가 매크로로 탑재되어 있다. 엑셀의 모든 구성요소가 객체 단위로 일관성 있게 구성되어 있기 때문에 가능한 기능이다. 그러기에 엑셀VBA를 이용하면 엑셀을 거의 완전히 내 손에 맞는 전용 툴로 개조할 수 있다. 더군다나 어떤 매크로가 포함된 문서를 여느냐에 따라 외양과 기능이 완전히 달라지는 유연한 전용 툴이 되는 것이다.

보통 프로그램은, 유연하면 속도가 느리게 마련이고, 속도를 빠르게 하려면 유연성이 떨어지게 마련인데, 엑셀은 그 둘을 다 충족시켜 버렸다. 감동적이다.

 

직감적 『프로그래밍』, 좋아하는 『프로그래밍』, 그리고 싫어하는 『프로그래밍』
기획안에서 머릿속에 프로젝트의 모든 구성요소의 설계가 이루어질 때가 있다. 일관성 있게 잘 짜여진 기획안이라면, 코드의 설계도 일관성 있게 잘 짜여지게 된다. 심지어는, 어서 설계를 그려놓고 이 설계에 맞는 코드를 써 넣고 싶어서 안달이 날 지경.

그러한 설계를 구현할 때에는 손가락이 키보드 위를 달리는 느낌, 혹은 손가락과 키보드가 일체가 되는 듯한 느낌, 혹은 손가락이 키보드에게 말하고 있는 듯한 느낌, 혹은 코드가, 전체 설계가, 전체 시스템이, 하나의 세계가 손가락 끝에서 뿜어져 나오고 있는 듯한 느낌.

분명, 이 세계를 신이란 존재가 만들었다면, 분명 신도 그러한 일관성있는 짜임새를 목표로 하고 세계를 만들면서 즐거워 했을 거라고 생각한다.

 

반면, 일관성이 떨어지거나 전혀 종잡을 수 없는 설계로 작업할 수 밖에 없는 상황도 분명 존재한다. 그럴 때는 코드 한줄 한줄 을 쓸 때마다 하품이 나오고 따분하다. 말할 것도 없이 최악의 상황.

 

세계에 『프로그래밍』이 없었다면

프로그래밍이 존재하지 않는다는 말은, 프로그래머나 프로그램 역시 존재하지 않는다는 것이고, 이는 곧 컴퓨터가 존재하지 않는 세상을 암시한다. 그럼 인류는 달에도 가지 못했을 것이고, 원자력 발전소도 수시로 폭발했을 테고, 인간 게놈도 밝혀내지 못했을 테지. 그리고 아마 사람들은 컴퓨터를 붙잡고 있는 대신 친근한 사람들과 조금이라도 더 많은 시간을 보냈을 지도 모른다. (하지만 아마 컴퓨터 대신 TV를 붙잡고 있을 공산이 크다고 생각한다)

나? 컴퓨터가 없는 세상이었다면, 아마 어렸을 적, 컴퓨터를 몰랐던 때의 장래 희망처럼 이론물리학을 하거나... 도서관 사서, 그런 것을 하게 되지 않았을 까 싶다. 더 나은 것인지 아닌지는 모르겠다. 지금도 그리 나쁘진 않으니까.

 

바톤을 받는 5명 (지정과 함께)

- 징 : 무협

- 미러 : 하루키

(더이상 이 블로그에 오는 사람이 없음. 생략)

 

Posted by uhm
dev.log2006. 9. 19. 09:47

설계의 가장 기본은 추상화이다. 이놈과 저놈이 다른게 무엇이고 공통점이 무엇인지를 파악해서 공통점을 묶어서 추상개념으로 승화시켜야 하는 법이다.

앞에서 든 음악 재생의 예는, 추상화를 제대로 하지 못해서 실패한 디자인이다. '재생'과 '정지'를 음악 재생의 한 과정으로서가 아니라, 추상적 실체로 바라보았다면 훨씬 나은 디자인이 되었을 것이다.

단지 음악을 '재생'하고 '정지'하는 기능만 필요한 상황에서는 애초의 설계가 그리 나쁜것은 아니다. 간단한 기능에는 간단한 설계. 이런 간단한 상황에서 Overkill은 금물이다.

 

class music
{
public:
    music();
    ~music();

    void update();
    void play( char* name );
    void stop();

protected:
    void* data;
    bool playing;
};

 

void music::update()
{
    if ( playing )
        continue_playing( data );
}


이제 여기에 '일시정지'와 '계속재생'이 추가된다. 프로젝트에서 이 부분을 맡은 개발자는 다음과 같이 기능을 추가했다.

class music
{
public:
    music();
    ~music();

    void update();
    void play( char* name );
    void stop();
    void pause();
    void resume();

protected:
    void* data;
    bool playing;
    bool paused;
};

void music::update()
{
    if ( playing )
        if ( !paused )
            continue_playing( data );
}

이 순간이 결정적 순간이다. 이 개발자는 이 기능을 추가하는 순간에 한번 고민을 해 봤어야 한다. data는 항상 쓰는 자원이니까 별도로 치면, 이 객체가 처할 수 있는 상태는 4가지이다.

A : playing is true, paused is false

B : playing is true, paused is true

C : playing is false, paused is false

D : playing is false, paused is true

여기에서, 상태A는, 재생이 시작되었고, 일시정지되지 않은 상황이다. 그냥 '재생'되고 있는 상황. 상태 B는, 일단 재생이 시작되었다가 일시정지된 경우이다. 우리가 일상적으로 생각하는 '일시정지'의 개념과 매우 잘 부합한다. 상태C는 재생되고 있지도 않고, 일시정지 되지도 않은 상황이다. 즉, 그냥 '정지'.

문제는 상태 D이다. 객체가 이 상태에 있을 때는 정의되지도 않았고, 우리의 일상적인 개념과 잘 부합하지도 않는다. 어떻게, 음악이, 재생하지도 않았는데, 일시정지될수 있단 말인가.

 

여기에서의 적합한 추상화는, 음악을 상태기계로 보고, '재생', '일시정지', '정지'의 3가지 상태를 만들어 주는 것이다.

class music
{
public:
    music();
    ~music();

    void update();
    void play( char* name );
    void stop();
    void pause();
    void resume();

protected:
    void* data;
    int state;
};

void music::update()
{
    switch ( state )
    {
    case STATE_STOPPED:  do_nothing(); break;
    case STATE_PLAYING:   continue_playing(); break;
    case STATE_PAUSED:    do_nothing(); break;
    default: stop();
    }
}

 

일단 지금은, 보기 흉한 switch 같은 것은 무시하기로 하자. 좀 더 명확해 진 것으로 만족해 보자. 이제 play(), stop(), pause(), resume(), update() ('시간의 경과'역시 입력의 하나로 취급할 수 있다) 등의 함수는 사실은 music이라는 객체의 상태를 바꿔주는 상태전이 입력으로 파악할 수 있는 것이다. 또한 객체가 알 수 없는 상태에 빠졌을 때에도 명시적으로 검증할 수 있는 상태로 편리하게 되돌릴 수도 있는 것이다. (위의 default 케이스)

 

이제 여기에 페이드-인/아웃을 추가해 보자. 이제 간단히 FI/FO상태를 추가하기만 하면 된다.

void music::update()
{
    switch ( state )
    {
    case STATE_STOPPED:  do_nothing(); break;
    case STATE_PLAYING:   continue_playing(); break;
    case STATE_PAUSED:    do_nothing(); break;
    case STATE_FADEIN:      increase_volume(); break;
    case STATE_FADEOUT:   decrease_volume(); break;
    default: stop();
    }
}

이걸로 끝일까? 위에서 update()역시 '시간의 경과'를 나타내는 상태전이 입력의 하나로 볼수 있다고 했다. 따라서 상태가 하나 추가된다면, 다른 모든 입력에 대해서 이 상태가 어떻게 행동해야 하나를 고려해야 한다. 따라서 다음과 같이 고려해야 한다.

void music::play( char* name )
{
    switch ( state )
    {
    case STATE_STOPPED:  read_data(); state = STATE_PLAYING; break;
    case STATE_PLAYING:   break;
    case STATE_PAUSED:    state = STATE_PLAYING; break;
    case STATE_FADEIN:      break;
    case STATE_FADEOUT:   restore_volume(); state = STATE_PLAYIN; break;
    default: stop();
    }
}

void music::stop()
{ ............ }

void music::pause()
{ ............ }

......................

 

'상태'라는 추상 개념을 도입함으로써, 기능이 추가될 때 무엇을 해야 하는지가 명확해 졌다.

이제 문제는 도처에 널부러져 있는 보기 흉한 switch-case이다. 상태가 하나 추가될 때마다 모든 가능한 입력에 대해 행동을 정의해 줘야 하는데, 하나의 상태가 하는 행동이 여러군데에 흩어져 있는 것이다. "Fade-in 할때 이러저러할 걸 바꿔주세요"라는 요청이 있을 때마다, update()에서 한줄 고치고, play()에서 한줄 고치고, pause()에서 또 한줄 고치는 일을 마냥 하다보면, 마우스 휠이 닳아서 못쓰게 될 지경이 될 것이다.

이때에 생각할 수 있는 것은 여러가지이지만 내가 적합하다고 생각하는 쪽은 State 패턴(GoF, p305)이다. 트랙백의 덧글에서처럼 MVC패턴을 적용할 수도 있지만, MVC패턴은 원래 다중 View를 생각해야 할 때 적합한 패턴이고, 음악 재생의 예는 View보다는 객체 자체의 행동을 캡슐화해야 하는 상황인데, 이때에는 State패턴이 보다 적합하리라는 것이 나의 생각이다.

State 패턴을 적용하면, 대략 다음과 같아진다.

class music
{
public:
    music();
    ~music();

    void update();
    void play( char* name );
    void stop();
    void pause();
    void resume();

protected:
    void* data;
    struct state* cur_state;
    struct state
    {
        music* context;
        state( music* m ) : context( m ) {}
        virtual void handle_play( char* name );
        virtual void handle_stop();
        virtual void handle_pause();
        virtual void handle_resume();
        virtual void handle_fadein();
        virtual void handle_fadeout();
        virtual void handle_update();
    };

    struct state_playing : public state
    { .......... };

    struct state_stopped : public state
    { .......... };

    struct state_paused : public state
    { .......... };

    struct state_fadein : public state
    { .......... };

    struct state_fadeout : public state
    { .......... };

};

 

그럼 update()와 다른 함수들은 대략 다음과 같이, 현재 상태로 입력을 포워딩하는 구조로 바뀐다.

void music::update()
{
    cur_state->handle_update();
}

 

void music::play( char* name )
{
    cur_state->handle_play();
}

 

이제부터는 기능이 추가되어야 한다면, 적절한 상태로 추상화 한 후, 상태 객체를 만들어서 끼워 넣는 식으로 디자인 변경이 쉬워진다. 또한 앞으로의 변경사항에 어떻게 대처해야 하는지, 현재 구조를 바꾸지 않고도 기능을 추가할 수 있는 방향을 제시할 수 있다는 점에서 더 나아진 디자인이다.

잘된 디자인이란, 모든 상상 가능한 기능을 구현할 수 있는 '틀'이 아니다. (그러한 것은 존재하지 않는다) 오히려 전혀 예상치 못한 요구가 발생했을 때에, 그 요구를 해결하기 위해 디자인이 어떻게 바뀌어야 하는가에 대한 방향을 제시할 수 있는 디자인이어야 한다고, 나는 생각한다.

 

Posted by uhm
dev.log2006. 9. 16. 05:18

생물이 시간에 따라 진화한다고는 하지만, 코드 역시 시간에 따라 진화한다. 물론, '진화'가 '진보'를 의미하지는 않으므로, 코드 역시 '퇴보'할 수 있다. 특히 잠깐 신경을 쓰지 않고 코드를 쓰다 보면, 디자인이 퇴보하는 것은 어렵지 않게 볼 수 있다. (열역학제2법칙을 떠올려 보자)

어떤 개발자가 음악을 플레이해볼 수 있는 간단한 툴을 만들고자 했다. 필요한 기능은 그냥 음악을 '틀어보고' '꺼보는' 기능이다. 그래서 다음과 같이 디자인했다.

class music
{
public:
    music();
    ~music();

    vod update();
    void play( char* name );
    void stop();

protected:
    void* data;
    bool playing;
};

 

동작은 간단하다.

1) play()멤버를 호출하면 name으로 data를 만들어내고, playing 멤버를 true로 바꾼다,

2) stop()멤버는 playing멤버를 false로 바꾸고 data를 지운다. 

3) update()은 playing이 true일 때만 data를 읽어서 재생을 계속한다.정말 간단하다.

 

잘 써먹고 있다가 보니 '일시정지'와 '계속재생'이 필요해졌다. 그래서 '일시정지' 기능을 다음과 같이 추가했다.

class music
{
public:
    music();
    ~music();

    vod update();
    void play();
    void stop();
    void resume();
    void pause();

protected:
    void* data;
    bool playing;
    bool paused;
};

동작은 아까와 크게 다름없다.

1) play(), stop()은 똑같다

2) pause()를 부르면, paused멤버를 true로 세팅한다.

3) resume()을 부르면, paused멤버를 false로 세팅한다.

4) update()에서는, paused가 false, playing 멤버가 true일 때만 재생을 계속한다.

 

이렇게 잘 써먹고 있다가 어느 기획자가 오더니 "페이드-인, 페이드-아웃이 반드시 있어야 되는데요"라고 말하고 가버린다. 그래서 이 개발자는 다음과 같이 페이드-인/아웃을 추가했다.

class music
{
public:
    music();
    ~music();

    vod update();
    void play();
    void stop();
    void resume();
    void pause();
    void fadein( float duration );
    void fadeout( float duration );

protected:
    void* data;
    bool playing;
    bool paused;

    int fade_mode;
    float fade_timer;
    float fade_duration;
};

이제 슬슬 골치가 아파오기 시작한다.

update()에서는, playing이 true, paused가 false일 때만 업데이트를 계속하되, fade_mode를 보면서 시간에 따라 볼륨을 조절해야 한다. 대략 다음과 같이 된다.

void music::update()
{
    if ( playing )
    {
         if ( !paused )
         {
             switch ( fade_mode )
             {
             case FADE_IN:    increase_volume(); break;
             case FADE_OUT: decrease_volume(); break;
             default: continue_playing();
             }
         }
    }
}

여기가 끝이 아니다. 어느날 어떤 기획자가 와서 말한다; "페이드-아웃할 때 음악을 끄지 말고 그냥 일시정지상태로도 할수 있게 해주세요"라고. 또 다른 기획자가 와서는 "3초간 대기한 후에 페이드-인 하면서 시작할 수 있게 해주세요. 아, 대기시간3초는 바뀔 수도 있어요"라고 말하고 가버린다. 그리고 기타 등등등.. 결과는? 걸레가 된 코드다.

 

제발 이 디자인에 문제가 없다고 생각하는 사람이 없기를 빈다. 구멍이 숭숭 뚫려있는 것이 눈에 보인다.

1) 이 객체가 처할 수 있는 모든 가능한 상태에 대한 고려가 없다 : playing이 false이거나, paused가 true일 때의 처리가 명시적이지 않다. 물론 지금이야 코드량이 워낙 적으니 명확하게 알아볼 수 있지만, 코드가 길어지면 명시적이지 않다는 것은, 객체가 알 수 없는 행동을 할 것이라고 봐야 한다. 두더지 게임기의 두더지처럼 버그를 하나 고치면 또 다른 버그가 끝도 없이 튀어나올 것이다 -_-

2) 이 객체에 다른 기능이 추가될 경우엔 구조가 바뀌어야 한다 : 만약 여기에 또 다른 기능, 이를테면 '2배속재생'이라든지, '거꾸로재생' 같은 기능이 추가되어야 한다면 또다시 if의 끝없는 중첩으로 해결할 속셈인 것이다, 이 개발자는. 프로그램의 구조는, 앞으로 발생 가능한 모든 사태를 포용할 수 있는 범용성을 갖추어야 한다. 기능이 추가될때 구조가 바뀌어야 하는 디자인은, 결코 디자인이라고 할 수 없다. 집안에 에어콘을 놓겠다고 벽을 허무는 셈이다. -_-

 

이렇게 짜는 사람이 있을 거라고 생각되지 않을지도 모르지만, 놀랍게도, 이 디자인은 회사에서 쓰고 있는 엔진에 그대로 들어있다. 수십만달러를 받아먹고 (내 어렴풋한 기억에는 50만달러정도였던거 같다) 파는 엔진에도 이런 디자인이 있는 걸 보면, 프로그래머가 밥벌어먹고 살 구석은 아직도 많은 거 같기도 하다 -_-;;


Posted by uhm
misc.log2006. 4. 14. 06:06

http://scifian.egloos.com/2352590 에서 트랙백.

 

사람마다 모두 감동을 느끼는 순간이 다르다. 트랙백의 원문에서는, 자연 풍광을 보면서 감동을 느끼는 사람이 있다고 하고, 내친구 징군은, 게임의 기막힌 연출을 보면서 감동을 느꼈다고 한다. 물론, 여기서 말하는 감동은, 감정적인 감동이 아니라, 경외감이랄까, 어떤 새로운 경지를 알았음이 느껴지는 순간을 일컫는 것이겠다.

 

내가 감동을 받았던 순간은, 어떤 '벽'을 통과했다는 느낌이 드는 순간이었다. GW베이직으로 제일 처음 만든 16-퍼즐이 돌아가던 순간. C로 (그 유명한) Hello, world 를 출력해본 순간. 내손으로 DPMI를 써서 SVGA모드에서 돌아가는 게임을 만들어본 순간.

 

내가 어떤 '벽'을 통과했으며, 그 앞에 있는 벽도 뚫을 수 있을것 같은 방법을 희미하게 알게 되는 순간들이었다.

 

그중에서 제일은 역시, 객체지향의 본질을 (내 나름대로) 깨달았던 순간이었다. 객체에서 객체로 이어지는 그 일관성의 흐름. 그리고 그 일관성의 흐름을 내 손으로 만들어 낼 수 있는 무한한 가능성을 발견한 그 순간. 내 손으로 어떠한 세상이라도 만들어낼 수 있을 것 같은 착각이 들게 되는 그 순간.

 

뭐랄까. 비유하자면, 산속 계곡에서 수풀이 우거진 가운데 좁다란 하늘만 올려다보면서 산을 오르다가 정상에 오르니 내 발아래 세상이 이렇게 넓게 펼쳐져 있다는 것을 깨달은 것과 비슷한 느낌이랄까. 그리고 정상에 오르니 그동안 지나쳐왔던 풀잎도, 나뭇가지도, 돌맹이도, 물방울도, 모두 산을 이루고 세상을 이루던 것들이었다는 통찰감. 그리고 정상에서 주위를 둘러보니 더 큰 산이 옆에 있는 것을 발견하는 좌절감과 두려움, 그리고 기쁨까지.

 

역시나, 이러한 느낌을 전달하는 것은 어려운 일이다. 하지만 나를 포함한, 대부분의 긱geek들은 바로 저런 느낌을 느낀 적이 있었기에, 그렇게 한 분야에 골몰하는 것이라고, 나는 생각한다.

그 느낌을 전달하고 공감하기 어렵기에, 그래서 긱이 왜 그렇게 긱한 행동을 하는지 이해하는 것 역시 어려운 일이 아닐까.

 

 

Posted by uhm
misc.log2006. 3. 3. 08:16

잘난척좀 해보자.

버텍스리스트로 구현한 모델데이터 파일을 인덱스리스트로 재구성하면서 느낀거다.

엔지니어로써 가장 뿌듯하면서도 우울할 때는, 반년 전에 만든 코드를 보수하면서 그때 내가 올바른 결정을 했다는 것을 발견했던 때이다. 아니, 정확히는 그때 나는 올바르다고 생각하는 방향을 주장했지만, 다른 사람의 의견에 밀려서 다른 방향으로 작성된 코드를, 다시금 그때 내가 주장하던 방향으로 고쳐야 할 때이다.

돌려서 말했는데, 직접적으로 말하자면, 다른 사람 말 믿고 했다가 결국 내말이 옳음을 발견했을 때를 말한다.

뭐라할지, 내 엔지니어로써의 식견이 그다지 쓸모 없지는 않다는 것과, 아직은 내 사회적 설득력이 부족하다는, 상반된 두 종류의 능력의 유/무를 느끼게 되는 뿌듯하면서도 씁쓸한 감정이다.

 

 

Posted by uhm
dev.log2005. 12. 21. 03:27

다음엔 삽질하지 않고자

삽질의 추억을 기록함 -_-

=======================================================================================

Posted by uhm
geek.log2005. 10. 4. 23:06
뉴스 : 25억짜리 '엔초 페라리'와 17억 짜리 'MC12' 동시 공개

엔초 페라리와 MC12다들 새끈한 차다. 보기만 해도 흐뭇해진다고나 할까.
하지만 내가 이 기사를 보고 경악한 것은 다름이 아니라...
문득 다음과 같은 생각이 떠올랐기 때문이다.
보통사람에게는 충격으로 다가올 수 있으므로..

Posted by uhm
dev.log2004. 1. 30. 02:44
스테이트 머신은 복잡한 계의 행동을 모델링하는 간단한 기법으로 널리 애용되곤 한다. 스테이트 머신에 대한 자세한 설명은 여기서는 하지 않겠지만, 상태에 따라 외부의 자극에 다르게 반응하는 계를 모델링하는 기법이라고 설명하면 될듯 하다. 스테이트 머신에는 여러 종류가 있지만, 여기서는 가장 간단한 Deterministic Finite State Automata만을 고려하기로 한다.
오늘 누군가가 스테이트 머신 클래스를 만든 코드라고 한 것을 본 적이 있다. 대략적으로 다음과 같은 형상이었다.
void state_machine::handler()
{
    int state = INITIAL_STATE;
    while ( !is_final( state ) )
    {
        int s = fetch_stimuli();
        switch ( state )
        {
        case STATE0:
            state = on_state0( s );
            break;
        case STATE1:
            state = on_state1( s );
            break;
        // 다른 스테이트를 처리하는 케이스
        // ....
        default:
            state = INVALID_STATE;
        }
    }
}
int state_machine::on_state0( int s )
{
    switch ( s )
    {
    case STIMULI0:
        // 스테이트0에서 입력0를 처리하는 코드
        // ....
        return SOME_STATE;
    case STIMULI1:
        // 스테이트1에서 입력1을 처리하는 코드
        // ....
        return ANOTHER_STATE:
    // 다른 입력을 처리하는 케이스
    // ....
    default:
        return STATE0;
    }
}


이건 클래스만 썼을 뿐이지 C코드 그대로의 설계방식이다. 전혀 객체지향적이지 않다. 문제점이 보이는가?
어떤 스테이트 머신이 있을때, 그 머신의 스테이트0와 스테이트1에서의 행동은 많든 적든 다른 점이 있기 마련이다. 객체지향의 세계에서는 다른 일을 하는 개체는 다른 클래스로 만들어야 한다. 구현하고자 하는 기능을 그냥 <클래스에 때려박는다>고 해서 객체지향이 되는 것이 아니라는 것이다.

혹시 디자인 패턴 쪽을 공부해 보지 않은 분이 있다면 객체지향 설계 방법론에서 디자인 패턴은 한번 봐둘만한 가치는 있음을 말하고 싶다. 여기에서 나타나는 것이 state pattern, 혹은 strategy pattern이다. 스테이트 패턴은 어떤 객체의 스테이트를 하나의 객체로 모델링하는 기법이다.
위 스테이트 머신을 스테이트 패턴을 써서 고친다면 다음과 같이 변할 것이다.

우선, 객체의 스테이트를 나타내는 state 추상 클래스를 선언한다.

class state
{
public:
    virtual state* handle_stimuli( int s ) = 0;
};


이제 각 스테이트를 표현하는 클래스를 선언한다.
class state0 : public state
{
protected:
    state* handle_stimuli( int s )
    {
        switch ( s )
        {
        case STIMULI0:
            return handle_stimuli0();
        case STIMULI1:
            return handle_stimuli1();
        // 다른 입력을 처리하는 케이스
        // ....
        default:
            return new state0();
        }
    }
};


이제 메인 루프에서는 선언된 추상클래스의 인터페이스만 호출하면 된다.

void state_machine::handler()
{
    state* cur_state = new initial_state();
    while ( !is_final( state ) )
    {
        int s = fetch_stimuli();
        state* new_state = cur_state->handle_stimuli( s );
        delete cur_state;
        cur_state = new_state;
    }
    delete cur_state;
}


혹 동적 메모리의 할당과 해제가 빈번히 일어나는 것이 신경쓰인다면, 각 상태 객체를 미리 스테이트 머신 객체에 등록시켜 놓고 빠르게 접근하는 방법을 생각해도 되겠다. 이런 경우에는 Prototype패턴을 응용해 보면 좋을 것이다. 혹은, placement new 연산자를 오버로딩하여 사용하는 방법을 써도 좋다.

구현상의 개선이야 그렇다 치고, 이제는 설계상의 개선을 한번 찾아보자. 위 설계에 개선의 여지가 없는가? 당연히 있다. 문제는 스티뮬리를 처리하는 handle_stimuli()메소드의 구현 방식에 있다. 여기서는 스티뮬리의 값이 따라서 객체의 행동을 선택해주는 방식을 취하고 있다. 그렇다면 행동을 취하는 정보는 state객체에 있는 것이 아니라 스티뮬리에 있는 것이 된다. 그렇다면 행동의 선택은 이미 스티뮬리에 따라 주어진 것인데, 구태여 state객체가 스티뮬리에 따르는 행동의 선택에 관여할 필요는 없다는 결론이 나온다.
즉, 스테이트 객체가 스티뮬리를 처리하는 방식은 스티뮬리에 따라 결정되므로, 스티뮬리가 자기 자신을 처리하는 방식을 알고 있는 것이 합리적인 설계이다. 즉, 스티뮬리를 객체로 모델링하는 것이 정답이다. 여기에는 Command패턴을 쓰는 것이 적절하다.

우선 스티뮬리를 나타낼 추상클래스 stimuli를 선언한다.
class stimuli
{
public:
    virtual state* handle( state* s ) = 0;
};


이제 각 스티뮬리를 표현하는 클래스를 작성한다.
class stimuli0 : public stimuli
{
protected:
    state* handle( state* s )
    {
        return s->handle_stimuli0();
    }
};


그럼 이제 state객체의 선언이 바뀌게 된다.

class state0 : public state
{
protected:
    state* handle_stimuli( stimuli& s )
    {
        return s.handle( this );
    }
};


그러면 메인루프는 다음과 같아진다.

void state_machine::handler()
{
    state* cur_state = new initial_state();
    while ( !is_final( state ) )
    {
        stimuli& s = fetch_stimuli();
        state* new_state = cur_state->handle_stimuli(s);
        delete cur_state;
        cur_state = new_state;
    }
    delete cur_state;
}

처음의 코드와 가장 나중의 코드를 비교해 보면, 어느 쪽이 더 깔끔한지 판단해 보시기 바란다.

Posted by uhm