아, C++이었지

hey의 이미지

얼마전의 일입니다. 직장 동료 하나가 엑셀 로더를 작성하고 있었습니다. 엑셀 로더는 익스포터와 한 쌍으로 동작하는데, 기획자가 익숙하게 사용하는 엑셀로 데이터를 작성하면 일단 XML로 내보낸 다음 실제 프로그램은 이 XML을 로더를 통해 읽어들이는 형태입니다. 엑셀에는 VBS와 노트 등 우리가 관심없는 내용이 덕지덕지 붙어있으니 가볍게 사용하기에는 XML로 내보내 사용하는 것이 편하죠. 특히나 XML은 리비전 관리와 비교가 쉬운 장점도 있습니다.

새로운 엑셀 파일을 익스포터에 끌어다 놓으면 자동으로 내보낼 컬럼이 지정되고, 필요없는 컬럼을 제거하거나 컬럼의 자료형을 지정해 주어야 합니다. 기존에는 문자열, 정수, 실수, 불린 자료형을 지원하고 있었습니다. 나는 그에게 새로운 타입으로 enum을 지원하게 해달라고 했습니다. 기획자가 문자열로 enum 상수를 입력하면 엑셀 로더에서 문자열과 기 입력된 Enumeration 정보를 참조해 숫자로 변환해 주는 변수형입니다. 기획자는 글자로 프로그래머는 상수로 쓸 수 있으니 실수도 줄고 편하겠죠. 대신 로딩하기 전에 Enumeration 객체를 로더에 넘겨주어야 하는데, 인터페이스를 다음처럼 디자인 했습니다.

class Enumeration
{
public:
    bool addEnum( int, const char* ); ///< Enum 등록
    const char* convertEnumToString( int ); ///< Enum을 문자열로 변환
    int convertStringToEnum( const char* ); ///< 문자열을 Enum으로 변환
};

그리고 enum 상수가 추가되거나 변경될 때 문자열을 일일이 등록할 필요가 없도록 하기 위해 매크로 함수를 적절히 사용하는 유명한 트릭도 함께 사용하기로 결정했습니다.

EnumOptionType.h

AUTO_ENUMERATION( eOPTION_ZERO )
AUTO_ENUMERATION( eOPTION_ONE )
AUTO_ENUMERATION( eOPTION_TWO )
AUTO_ENUMERATION( eOPTION_THREE )

위 모양대로 헤더 파일을 등록시켜 놓으면,

#define AUTO_ENUMERATION(o) o,
enum EOPTION
{
    #include "EnumOptionType.h"
};
#undef AUTO_ENUMERATION

Enum 등록과,

#define AUTO_ENUMERATION(o) #o,
const char* enumerations={
    #include "EnumOptionType.h"
    NULL
};
#undef AUTO_ENUMERATION

문자열 정의를 한 번에 하게 됩니다.

그런데 기획자가 작성한 문자열이 코드상에는 없는 enum이라면 로딩 시점에 가서야 이걸 알 수 있습니다. XML로 내보내는 단계에서 검증하는 방법은 없을까요? 좋아, 그럼, "내보낼 때 svn 웹 뷰어에서 최신 소스를 받아와서 한 차례 검증해 주세요".

문제가 발생했습니다. 동료의 표정이 좋지 않습니다. 무슨 일일까요? 그냥 제가 알려준 주소에서 내용을 받아와서 정규표현식으로 거른 다음 문자열 비교만 하면 될텐데. 내보낼 때야 시간이 좀 걸려도 누가 뭐라 하지 않을텐데 말이에요.

아, 그렇죠, 우리 프로젝트 전체는 C++로 짜여진 코드 덩어리입니다. 파이썬 류의 스크립트 구현만 떠올리고 단순하게 생각했던 거에요! 조엘 29에서 지나가면서 본 기억이 있거든요. 루비로 작성된, 소스 파일을 가져오는 코드입니다.

require 'net/http'
Net::HTTP.start('hailydaily.net', 80) do |http| print( http.get('/sources/EnumOptionType.h').body) end

이 일을 파이썬으로 하면 어떻게 될까요? 제 사정을 듣고 나이누옹이 알려주었는데, 코드 조각을 잃어버리는 바람에 새로 적었습니다.

import urllib, re
def getHeader(url):
    return urllib.urlopen(url).read()


def getEnums(string):
    pattern='AUTO_ENUMERATION\( *([a-z].[0-9a-z_]+) *\)'
    return re.compile(pattern, re.IGNORECASE).findall(string)

헤더 파일을 가져오는 것 한 줄, 내친김에 정규식 처리에 두 줄이 소요되었습니다. 억지로 줄인 모양새도 아니라 보기도 좋습니다. 물론 예외 처리를 하면 더 길어지겠지만요. 루비의 정규식 처리는 아직 안 봐서 잘 모르겠습니다. 하는 김에 PHP로도 작성해 볼까요?

<?
function getHeader($url)
{

    $fp=fopen($url, "r");
    return fread($fp, 1024);
}

function getEnums($contents)
{
    $enums=array();
    preg_match_all("/AUTO_ENUMERATION\( *([a-z].[0-9a-z_]+) *\)/i", $contents, $out);
    for ($i=0; $i < count($out[0]); $i++) {
        array_push($enums, $out[1][$i]);
    }
    return $enums;
}

?>

좀 길지만 나쁘지 않습니다.

자, 두려운데요. C++에서 이 작업을 하려면 어떻게 해야합니까? libcurl이라는 라이브러리가 있습니다. 예제를 참조해 작성해 보겠습니다. MS VS 2003에서 프로젝트를 만들고 다음의 코드를 작성해 넣었습니다.

#include "stdafx.h"
#include <curl/curl.h>

struct HttpResult {
    HttpResult(size_t recvKb=0)
    {
        if (recvKb == 0) {
            recvKb = 1024 * 1;
        }
        size = 0;
        maxSize = 1024 * recvKb;
        contents = new char[maxSize];
        memset(contents, 0, maxSize);
    }

    ~HttpResult()
    {
        delete contents;
    }

    char* contents;
    size_t size;
    size_t maxSize;
};

size_t writeMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data)
{
    HttpResult* result = (HttpResult*)data;
    size_t totalSize = size * nmemb;

    size_t resultSize = result->size;
    if (resultSize + size < result->maxSize) {
        memcpy(result->contents + resultSize, ptr, totalSize);
        result->size += totalSize;
        return totalSize;
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    CURL* curl;
    CURLcode res;

    HttpResult result;

    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "http://hailydaily.net/sources/EnumOptionType.h");
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);

        res = curl_easy_perform(curl);

        curl_easy_cleanup(curl);
    }

    printf("%s\n", result.contents);

    getchar();
    return 0;
}

휴, 됐습니다. 그나마 미리 잡아둔 버퍼 크기를 넘어가면 받질 못하는군요. C++이니만큼 std::string을 사용하도록 바꿔보겠습니다.

#include "stdafx.h"
#include <curl/curl.h>
#include <string>

using std::string;

size_t writeMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data)
{
    string* result = (string*)data;
    result->insert(result->size(), (char*)ptr);
    return size * nmemb;
}

int _tmain(int argc, _TCHAR* argv[])
{
    CURL* handle = curl_easy_init();
    if (handle) {
        string result;

        curl_easy_setopt(handle, CURLOPT_URL, "http://hailydaily.net/sources/EnumOptionType.h");
        curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeMemoryCallback);
        curl_easy_setopt(handle, CURLOPT_WRITEDATA, &result);
        curl_easy_perform(handle);
        curl_easy_cleanup(handle);

        printf("%s\n", result.c_str());
    }

    getchar();
    return 0;
}

이제 좀 단순해졌습니다. 다른 스크립트 언어의 코드처럼 단순하고 아름답진 않지만 그렇다고 엉망으로 복잡하지도 않습니다. 이걸로 저 자신도 가지고 있었던 분명한 두려움은 지나친 것이라는 걸 알게 되었습니다. 필요한 문자열 처리를 추가해서 동료에게 알려주어야겠어요.

댓글

atie의 이미지

그동안 kldp에서 잃어가던 재미를 오랫만에 느끼게 하는 글이군요. ^^
----
I paint objects as I think them, not as I see them.
Ubuntu Edgy user / Ubuntu KoreanTeam

----
I paint objects as I think them, not as I see them.
atie's minipage

leigh의 이미지

MFC를 쓰면 좀 길어지긴 해도 비슷하게 하실 수 있을 것 같네요.
libcurl도 괜찮은 초이스일것 같긴 한데, 만약 직장 동료가 MFC 프로그래머라면,
CHttpConnection, CHttpFile 클래스로 URL에서 파일을 받아오고, CAtlRegExp 클래스로 정규식 매칭을 하는 방법을 제안하면 어떨까요 :)

...

hey의 이미지

안녕하세요, 댓글 고맙습니다 :)
이 글의 주제는 근거 없는 두려움에 대한 것입니다. 처음에는 애자일하지 못한 C++을 보는 저의 부정적인 관점이랄까, 또는 모든 작업에 가장 잘 아는 동일한 도구를 사용하려는 - 그래서 결국엔 작업에 덜 적합한 도구를 사용하게 되고 마는 프로 개발자들의 경향(이건 생각이 좀 지나쳤습니다 반성)에 대해서 쓰려고 했습니다. 그런데 글을 적다보니 생각보다 쉽게 일이 풀리는 거예요. 그 순간에는, '어? 이게 아닌데?' 했습니다만, 앞에서 지적하고자 했던 문제와는 별개로 저 자신에게 '근거없는 두려움'이 있었다는 것을 발견했습니다. 그래서 그런 쪽으로 가까스로 '발전적인' 결론을 내렸고요..
그러나 MFC에 대해서는 제가 아직 두려움을 가지고 있습니다. ^^; 말씀해 주신 것 공부해볼게요.

May the F/OSS be with you..



----------------------------
May the F/OSS be with you..


netisinfinite의 이미지

좋은 글입니다 :-)
생각해 보면 결국 작업에 덜 적합한 도구를 사용하게 되고 마는 것도 근거없는 두려움 때문이 아닐까 해요. 저만 해도 종종 특정 작업에 좀 더 나은 도구를 쓰려다가는 툴문제 연동 문제 속도 문제 숙달 문제 유저 커뮤니티 문제 등등 근거없는 온갖 핑계를 대면서 결국 주 언어와 툴로 돌아와버리고 말거든요.
편견과 꺼려함이 없어야 좋은 프로그래머가 될텐데, 그 장벽을 넘어서기가 쉽지 않네요...
어쨌든 잘 읽었습니다.

마잇의 이미지

require 'net/http'
Net::HTTP.start('hailydaily.net', 80) do |http| print( http.get('/sources/EnumOptionType.h').body) end

require 'open-uri'
puts open('http://hailydaily.net/sources/EnumOptionType.h').read

루비를 한창 배우고 있어서 스리쓸쩍 참견을 -ㅅ-
--
마잇


--
마잇

hey의 이미지

제가 제일 바라던 부류의 댓글 ^^; 고맙습니다

May the F/OSS be with you..



----------------------------
May the F/OSS be with you..


mithrandir의 이미지

이거 순간

실용주의 프로그래머를 읽고 있는건가?.. 라는 착각이 들었군요 ;)

루비의 정규식 처리도 뭐 별다르지 않아요.

"Merong World".gsub(/M.*g/, 'Hello')
 
str = "this is merong world"
str =~ /(.*)merong(.*)/
 
puts "#{$1} -- #{$2}"

정도가 되겠군요.

언제나 삽질 - http://tisphie.net/typo/
프로그래밍 언어 개발 - http://langdev.net

hey의 이미지

'영광, 영광' 입니다. 루비에는 정규식이 기본 타입인가보군요. 더 간단하네요 !_!

May the F/OSS be with you..



----------------------------
May the F/OSS be with you..


익명사용자의 이미지

C++은...언어자체는 너무너무 강력한데...뒷받침은 모조리 서드파티 라이브러리로 때워야되지요 ㅋㅋ

그래서 더더욱 매력을 느끼는지도 모르겠어요~ 정복할수없는 그대~ 일까나요 ^^;;;

루비..많이좋나봐요? 여기저기 자꾸언급되어서리...시간내서 공부해볼까나?;;

manea의 이미지

말로만 듣던 루비에 대해서좀 보게 되었네요 ^^ 시간 많으면 JAVA로도 붙여보고 싶네요 ㅎㅎ 틈틈이 들르는 중이라서 죄송 ^^;

열정!! ^^

열정!! ^^

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.