Reactor 패턴과 Proactor 패턴

Reactor 패턴

  • 어떠한 이벤트가 발생하면, 이곳으로 알려달라는 방식.
  • 윈도우 메시지 핸들러처럼, 특정 이벤트가 발생한다면 통지 받겠다는 방식.

Proactor 패턴

  • 특정 작업을 시키고, 그 작업이 완료되면 알려달라는 방식.
  • IOCP에서 Completion Port가 이 방식을 취하고 있다.

말로만 보면 비슷한데, 깊이 고민해보면 차이점이 명확해진다.

Proactor는 작업을 시키면서 콜백함수를 직접 넘김으로써 구현되고, Reactor는 디스패쳐를 구현하는 구조가 일반적.

Reactor 패턴 사용시에는 디스패쳐를 통함으로써 스팟 포인트가 발생하게 되는 단점이 있다고 보면된다.

이에 비해 Proactor는 명령을 내린 작업에 대해서만 통지를 받게 된다.

IOCP의 예를 들면, 내가 물려놓은 소켓에 Recv 이벤트가 발생했다고 해도, WSARecv를 걸지 않으면, 해당 이벤트가 발생했는지 여부에 대해 통지를 받지 않음을 의미한다.

이는 이벤트 폭발로 인한 부하 발생이나, 스팟 포인트를 감소 시킬 수 있는 테크니컬한 멀티 스레드 프로그래밍이 가능하게 하려면 Reactor 패턴보다는 Proactor 패턴이 유리하다는 것을 의미힌다.

Reactor 패턴은 디스패쳐를 거치기 때문에, 직렬화에 유리하긴 하나 이 디스패쳐가 스팟 포인트가 되는 것이 일반적이란 단점이 있다. (물론 그렇지 않게 구현 할 수 도 있으나, 그렇게 멀티스레드 로직을 수행하기엔 예외 상황이 지나치게 커질 위험이 있다.) 

stateless 하게 구현하기 위해선 proactor 기반의 처리 구조로 구현하는 것이 좀 더 유리하다고 볼 수 있다. (stateless하게 만들 수 있다면, reactor패턴을 쓴다고 해도 스팟 포인트를 줄일 수 있긴 하다.)

C++ 구조체 이니셜라이저 문제

struct CHAR_COLLECTION_DATA
{
    int CharID;
    int Value;
    int ValueCode;
};

CHAR_COLLECTION_DATA CollectionData  = {m_CharID, m_Value, m_ValueCode };

이런 코드가 있었다.

기능을 추가 하시려다보니 습관적으로

struct CHAR_COLLECTION_DATA
{
    int CharID;
    int ClassID; // 다른 변수를 중간에 추가함.
    int Value;
    int ValueCode;
};

CHAR_COLLECTION_DATA CollectionData  = {m_CharID, m_Value, m_ValueCode };

같은 코드고 컴파일 오류도 없지만 원래 코드와 다르게, CharID, Value, ValueCode를 채우지 않고, CharID, ClassID, Value에만 값을 채우는 코드가 되어버렸다.

물론 사용 코드를 전부다 훓어보지 않은 문제가 있긴 하지만, 컴파일 오류로 강제되지 않은 초기화도 좋은 제약은 아니다.

struct CHAR_COLLECTION_DATA
{
    int CharID;
    int ClassID; // 다른 변수를 중간에 추가함.
    int Value;
    int ValueCode;

    CHAR_COLLECTION_DATA()
                : CharID(0)
        , ClassID(0)
        , Value(0)
        , ValueCode(0)
    {

    }


    CHAR_COLLECTION_DATA(int charID, int classID, int value, int valueCode)
        : CharID(charID)
        , ClassID(classID)
        , Value(value)
        , ValueCode(valueCode)
    {

    }
};

기존 코드가 이렇게 짜여져 있었다면, 기존 코드였던

CHAR_COLLECTION_DATA CollectionData(m_CharID, m_Value, m_ValueCode);

는 컴파일 오류를 일으키기 때문에, 자연스레

CHAR_COLLECTION_DATA CollectionData(m_CharID, m_ClassID, m_Value, m_ValueCode);

이와 같이 고치게 될 것이다.

물론 모든 상황에서 주의 깊게 코드를 찾아보고 고치면 얼마나 좋겠냐 만은, 커버리지는 높을수록 좋은 것.

컴파일 오류로 막을 수 있는 습관은 갖추는 게 좋다.

구조체 이니셜라이저는 자제 하자.

첨언을 하자면, 대입될 변수를 지정하는 방식의 이니셜라이저는 추천이다. legacy한 문법을 허용하는 C스타일 구조체 이니셜라이저를 자제하자는 얘기다.

(서평) 읽기 쉬운 코드가 좋은 코드다

제가 여러번 극찬한 임백준씨의 번역서입니다.

얼마나 좋았으면 직접 번역을 하셨을까 궁금하더군요.

이전에 번역하신 해커와 화가도 두고 두고 읽을만큼 색다른 시각이 좋았던지라, 이 책에 대한 기대도 그만큼 컸다고 볼 수 있습니다.

대략 책 제목에서도 알 수 있듯이, 어떠한 코드가 읽기 좋은 코드인지에 대한 기준을 제시하는 책입니다.

한빛 미디어 사이트 목차에서 발췌하자면, 담고 있는 내용은 아래와 같습니다.

1장. 코드는 이해하기 쉬워야 한다
01. 무엇이 코드를 '더 좋게' 만드는가?  
02. 가독성의 기본 정리  
03. 분량이 적으면 항상 더 좋은가?  
04. 이해를 위한 시간은 다른 목표와 충돌하는가?  
05. 어려운 부분  

PART I. 표면적 수준에서의 개선

2장. 이름에 정보 담기
01. 특정한 단어 고르기  
02. tmp나 retval 같은 보편적인 이름 피하기  
03. 추상적인 이름보다 구체적인 이름을 선호하라  
04. 추가적인 정보를 이름에 추가하기  
05. 이름은 얼마나 길어야 하는가?  
06. 이름 포메팅으로 의미를 전달하라  
요약  

3장. 오해할 수 없는 이름들
01. 예: Filter()  
02. 예: Clip(text, length)  
03. 경계를 포함하는 한계값을 다룰 때는 min과 max를 사용하라  
04. 경계를 포함하는 범위에는 first와 last를 사용하라  
05. 경계를 포함하고/배제하는 범위에는 begin과 end를 사용하라  
06. 불리언 변수에 이름 붙이기  
07. 사용자의 기대에 부응하기  
08. 예: 이름을 짓기 위해서 복수의 후보를 평가하기  
요약  

4장. 미학
01. 미학이 무슨 상관인가?  
02. 일관성과 간결성을 위해서 줄 바꿈을 재정렬하기  
03. 메소드를 활용하여 불규칙성을 정리하라  
04. 도움이 된다면 코드의 열을 맞춰라  
05. 의미 있는 순서를 선택하고 일관성 있게 사용하라  
06. 선언문을 블록으로 구성하라  
07. 코드를 '문단'으로 쪼개라  
08. 개인적인 스타일 대 일관성  
요약  	
    
5장. 주석에 담아야 하는 대상
01. 설명하지 말아야 하는 것  
02. 생각을 기록하라  
03. 코드를 읽는 사람의 입장이 되어라  
04. 마지막 고찰 - 글 쓰는 두려움을 떨쳐내라  
요약  
    
6장 명확하고 간결한 주석 달기
01. 주석을 간결하게 하라  
02. 모호한 대명사는 피하라  
03. 엉터리 문장을 다듬어라  
04. 함수의 동작을 명확하게 설명하라  
05. 코너케이스를 설명해주는 입/출력 예를 사용하라  
06. 코드의 의도를 명시하라  
07. 이름을 가진 함수 파라미터 주석  
08. 정보 축약형 단어를 사용하라  
요약  
    
PART II. 루프와 논리를 단순화하기

7장. 읽기 쉽게 흐름제어 만들기
01. 조건문에서 인수의 순서  
02. if/else 블록의 순서  
03. (삼항 연산자로 알려진)?:를 이용하는 조건문 표현  
04. do/while 루프를 피하라  
05. 함수 중간에서 반환하기  
06. 악명 높은 goto  
07. 중첩을 최소화하기  
08. 실행 흐름을 따라올 수 있는가?  
요약  
    
8장. 거대한 표현을 잘게 쪼개기
01. 설명 변수  
02. 요약 변수  
03. 드모르간의 법칙 사용하기  
04. 쇼트 서킷 논리 오용하기  
05. 예: 복잡한 논리와 씨름하기  
06. 거대한 구문 나누기  
07. 표현을 단순화하는 다른 창의적인 방법들  
요약  
    
9장. 변수와 가독성
01. 변수 제거하기  
02. 변수의 범위를 좁혀라  
03. 값을 한 번만 할당하는 변수를 선호하라  
04. 마지막 예  
요약  
    
PART III. 코드 재작성하기

10장. 상관없는 하위문제 추출하기
01. 소개를 위한 예: findClosestLocation()  
02. 순수한 유틸리티 코드  
03. 일반적인 목적의 코드  
04. 일반적인 목적을 가진 코드를 많이 만들어라  
05. 특정한 프로젝트를 위한 기능  
06. 기존의 인터페이스를 단순화하기  
07. 자신의 필요에 맞춰서 인터페이스의 형태를 바꾸기  
08. 지나치게 추출하기  
요약  
    
11장. 한 번에 하나씩
01. 작업은 작을 수 있다  
02. 객체에서 값 추출하기  
03. 더 큰 예제  
요약  
    
12장. 생각을 코드로 만들기
01. 논리를 명확하게 설명하기  
02. 라이브러리를 알면 도움이 된다  
03. 논리를 쉬운 말로 표현하는 방법을 더 큰 문제에 적용하기  
요약  
    
13장. 코드 분량 줄이기
01. 그 기능을 구현하려고 애쓰지 마라 - 그럴 필요가 없다  
02. 요구사항에 질문을 던지고 질문을 잘게 나누어 분석하라  
03. 코드베이스를 작게 유지하기  
04. 자기 주변에 있는 라이브러리에 친숙해져라  
05. 예: 코딩 대신 유닉스 도구를 활용하기  
요약  

PART IV. 선택된 주제들
    
14장. 테스트와 가독성
01. 읽거나 유지보수하기 쉽게 테스트를 만들어라  
02. 이 테스트는 어떤 점이 잘못되었을까?  
03. 이 테스트를 더 읽기 쉽게 만들기  
04. 읽기 편한 메시지 만들기  
05. 좋은 테스트 입력값의 선택  
06. 테스트 함수에 이름 붙이기  
07. 이 테스트 코드는 무엇이 잘못되었는가?  
08. 테스트에 친숙한 개발  
09. 지나친 테스트  
요약  
    
15장. '분/시간 카운터'를 설계하고 구현하기
01. 문제  
02. 클래스 인터페이스 정의하기  
03. 시도1: 순진한 해결책  
04. 시도2: 컨베이어 벨트 설계  
05. 시도3: 시간-바구니 설계  
06. 3가지 해결책 비교하기  
요약  
    
Appendix 추가적인 도서목록
01. 높은 수준의 코드를 쓰는 방법을 다루는 책들  
02. 다양한 프로그래밍 주제에 대한 책들  
03. 역사적 사례를 담고 있는 책들  
찾아보기

이 중 가장 유용했던 내용이라면 주석에 대한 내용입니다. Doxygen을 쓰다보니 강제당한 주석이 너무 당연한 내용을 너무 많이 담고 있었습니다.

물론 제가 Doxygen 규격을 따라가다 느낀 장점은, 당연한 내용을 작성하던 중에 다시 한번 생각하게 된다라는 점이었는데요, 그런 점을 제외하고는 사실 커다란 이득을 보기 어려웠던게 사실입니다.

특히나 최초 작성시에는 그렇다쳐도, 추후 유지보수에는 더더욱이 큰 메리트는 없다고 볼 수 있었습니다.

그렇다고 주석이 필요 없는 코드가 좋다고 항변하기엔, 그런 코드만 작성할 수 있는 것도 아니고 말이죠.

생각을 기록하라라는 이야기도 꽤나 크게 와닿았습니다.

또, 결함을 설명하라는 이 책에 대한 만족도를 크게 높여줬다고 볼 수 있었죠.

  • TODO: 아직 하지 않은일
  • FIXME: 오작동을 일으킨다고 알려진코드
  • HACK: 아름답지 않은 해결책
  • XXX: 위험! 여기에 큰 문제가 있다.
  • TextMate:ESC (이게 뭔뜻인지 아시는분?)

조금 아쉬웠던 것은, 저도 반대하면서도 장점과 단점을 명확히 제시하고 싶었던 goto에대한 설명이 아주 짧게 쓰여있다는 점이었습니다만…뭐 그분들도 제대로 설명하기 어려웠던 걸려나요?

아참! 제가 요새 절실히 깨닳고 있는, 코드 분량줄이기 챕터도 아주 좋습니다.

일주일에 코드 몇만줄 짜냐느니 등으로 일을 열심히했다는 바로미터로 삼는 한심한 일도 비일비재하지만, 대다수의 능력있는 프로그래머들은 적은양의 코드를 작성하면서 원하는 목표를 달성하는 일이 얼마나 가치있는지 잘 아실테니까요.

책 페이지 수도 많지 않고 (부록 제외 240여 페이지), 내용들도 쉽게 쉽게 읽히는 편인지라, C++ 코딩의 정석 만큼이나 추천드리고 싶은 책입니다.

물론, C++ 코딩의 정석은 실수 사례도 모은 느낌이고, 이 책은 코드 작성 + 코드 읽기 관점이지만 말이지요.

팀 내 가이드로 삼아도 될만큼 좋은 책입니다. 추천!

Active Record Query Interface

액티브 레코드 쿼리 인터페이스

튜토리얼만 잘 읽고 가이드만 잘 따라가도 평타를 칠 수 있는 언어! 루비….인데, 액티브 쿼리 인터페이스 읽다말고 find_by_sql을 발견 한 후, find_by_sql 위주로 작업을 했더니 몇가지 문제가 있었습니다.

현재의 세팅환경은, 다음과 같습니다.

develop : sqlite production : mysql

이렇게 쓰다보니, 특정 dbms 종속형 쿼리를 작성 했을시에 특정환경에선 동작하지 않는 기능을 만들어버리는 것이었죠.

ROR의 액티브 쿼리 인터페이스란걸 알고보니 어지간한건 직접 쿼리 안짜고 가능하더군요!!

아래는 ROR에서 지원하는 메소드 종류입니다.

  • where
  • select
  • group
  • order
  • limit
  • offset
  • joins
  • includes
  • lock
  • readonly
  • from
  • having

뭐 여타 DBMS에서도 흔히 볼 수 있는 구문들이므로…자세한 설명은 패스하겠습니다.

만약 find_by_sql을 많이 쓰고 계신다면, 가급적 액티브 쿼리 인터페이스를 쓰시는 것이 여러모로 장점이 많지 않을까 싶네요.

명령행 프로그램 이야기

내가 처음 접한 프로그래밍 언어는 Basic이 아닌, C였다.

그리고 Turbo-C 2.0이 첫 컴파일러였다.

내가 처음 샀던 C언어 서적이 터보 C 2.0을 알려주는 주황색 서적이었는데, 뭔가 시리즈 였던 기억이 난다.

그 책이 너무 설명이 어려워, 다음에 샀던 책이 바로, Turbo-C 2.0 길라잡이다.

내 기억에 이 책의 표지는 초록색이었는데, 사진도 목차도 안나와있어서이책이 맞는지는 모르겠다

여튼 당시 내가 봤던 서적에서의 “Hello, World!”는 다음과 같다.

#include < stdio.h >
void main()
{
    printf("Hello, World!\n");
}

뭐….당시엔 대다수 국내 C언어 서적이 저랬을려나…? ANSI C Programming의 번역서만이.

#include < stdio.h >
int main(int argc, char* argv[])
{
    printf("Hello, World!\n");
    return 0;
}

였던걸로 기억한다.

고작 이게 뭐 그리 중요하냐고? 이 별거 아닌 차이가, 유닉스 문화와 윈도우 문화를 가르는 발단이 되었기 때문이다.

GCC에서는 애초에 void main()이 허용되지 않는다.

유닉스에서는 명령행 프로그램 사이의 연동에 인자(int argc, char* argv[])를, 넘기고 그 결과로 exit 코드 (main의 int 리턴 타입)를 이용한다. 그래서 프로그램간의 연동이 쉽게 가능하며, 명령행 프로그램 위에 UI를 붙이는 과정이 이런 전제하에 있다. (물론, 파이프 통신이나 소켓 통신등으로 IPC를 하기도 하지만)

여러 포스팅을 보면, void mai()과 int main(int argc, char* argv[])의 차이에 대해 표준에 대한 이유를 언급하곤 하는데, 이 표준의 전제에는 프로그램 간의 상호 작용을 매끄럽게 하기 위해서라 할 수 있다.

터보-C와 MSC라 불리우는 Visual C++이 void main() 을 허용하다보니, 그렇게 만들어진 프로그램의 exit코드는 신용할 수 없다. 물론 리눅스 환경에서 개발됐고, ANSI C 표준을 따른 프로그램이라해도, 목표로한 동작을 완료하지 못했음에도 exit코드를 0이 아닌 값을 반환하는 상황도 있을 수 있으나, 대부분의 규칙을 잘 따른 프로그램들은 정상 수행에 0을 반환한다는 것을 목표로 삼고 있다.

이 반환값을 기반으로 도는 프로그램은 수없이 많다.

나는 철저히 윈도우 프로그래머였다. 그것도 클라이언트 온리 게임만 만들던 동인 게임 개발자였고.

애초에 프로그램간의 연동이 목표가 아니라, 그저 내 프로그램 하나가 수많은 기능을 담고 싶어했던, 바퀴를 다시 발명하는 것을 즐겼던(?) 그저 흔한 프로그래머였음은 물론이고. 심지어 게임 개발할 때마다 전용 툴까지도 만들었던…흑역사를 품고 있다.

내가 윈도우 쉘 연동으로 생산성을 높이게됐던 계기는, 서버 개발자가 된 후 라이브팀 업무때가 주로 그랬다. 나에게 주어진 시간은 그다지 많지 않았고, 컨텐츠를 개발하는 일 이외의 시간은 사실 잘 주어지지 않았다. 그렇다보니 자연스레 바퀴를 재발명하는건 시간이 부족하다보니 여의치않아졌다.

가능하면 기존에 있는 기능들을 잘 묶고, 명령행 프로그램 여러개의 기능을 엮는 자동화에 대한 의지가 늘게된 시기였다. 

물론 이전에도 난 게으르기 때문에, 게을러 지기 위한 기반 작업. 자동화에 대한 의지가 있었으나 그 자동화를 C++ 코드위에서 하곤 했다. FTP고, HTTP고 WIN32 API를 통해 다시 만들어, 내 프로그램 위에 얹었다.

잘 만들어져 있는 명령행 프로그램들을 엮는 일이, 얼마나 내 생산성을 높아지게 했는지 뼈저리게 느꼈다. 그와 함께 기존에 안좋았던 습관들이 조금씩 사라지게 된 계기가 됐고.

그런 깨닳음을 얻어가던 중 내가 만든 여러 프로그램들을 돌이켜봤다.

어째서 내 프로그램은 어찌 하나같이, GUI 기반으로만 동작하는가?!? GUI기반으로 동작하는건 좋다 이거야. 헌데, 다른 프로그램과 엮기 왜 이렇게 힘든거지?

내가 짠 프로그램들은 하나같이 main함수에서 return 0으로 프로그램을 종료시키고 있었다. 원하는 목표를 달성했는지 여부와는 전혀 상관없이 말이다.

기능을 재사용하는건 , 라이브러리화 해둔 코드를 재사용하는 것에 의존했을 뿐이었고.

별거 아닌거 같으면서, 큰 깨닳음을 얻고, 자동화와 각종 보조 업무등을 위해 다른 사람들이 만든 프로그램들을 엮는 작업이 늘어가면서 내가 만든 프로그램들도 그 사이에 엮을 수 있게끔  맞춰가고 있다.

아직 많이 미숙하지만 리눅스 환경에서 ROR을 운영하며 리눅스에서의 기능 재사용, 프로그램 사이의 연동의 문화, 흔히 리눅스 문화라 불리는 것들을 잘 느낄 수 있었다. 

윈도우 환경 위에서 만들어진 수 많은 메이저 프로그램이 명령행 기능을 지원하지 않는다. 

특정 프로그램에서 95%가 만족스러운데, 살짝 아쉬운 한두가지 요소 때문에, 다른 프로그램을 찾아 다녀야했던 일이 리눅스 문화라고 없는건 아니지만, 명령행 위에 UI를 얹은 많은 프로그램들은 이런 고민을 해결해준다.

이런 깨닳음이 늦게 온 원인이 윈도우 환경 위에서 프로그래밍을 해왔기 때문이라고 핑계대고 싶진 않다. 다만 GUI 환경의 문화가 프로그램 사이의 연동을 어렵게 만드는 요인이라는 생각을 갖게 됐다.

아직 능숙해지려면 멀었지만 리눅스 환경에 조금씩 적응될수록 여러가지 생각이 드는데, 그 중 명령행 프로그램에 대한 이야기는 꼭 한번 하고 싶었다.

앞으로도 윈도우 환경에 대한 감사함을 얼마나 느끼게 될지, 리눅스 환경에 대한 감탄을 얼마나 하게 될지는 잘 모르겠지만 윈도우’만’ 써왔다고 할 수 있는 프로그래머가, 리눅스 환경에 적응해가며 드는 감상을 적어보겠다.