아, C++이었지
얼마전의 일입니다. 직장 동료 하나가 엑셀 로더를 작성하고 있었습니다. 엑셀 로더는 익스포터와 한 쌍으로 동작하는데, 기획자가 익숙하게 사용하는 엑셀로 데이터를 작성하면 일단 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;
}
이제 좀 단순해졌습니다. 다른 스크립트 언어의 코드처럼 단순하고 아름답진 않지만 그렇다고 엉망으로 복잡하지도 않습니다. 이걸로 저 자신도 가지고 있었던 분명한 두려움은 지나친 것이라는 걸 알게 되었습니다. 필요한 문자열 처리를 추가해서 동료에게 알려주어야겠어요.
댓글
그동안 kldp에서
그동안 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
MFC를 쓰면 좀
MFC를 쓰면 좀 길어지긴 해도 비슷하게 하실 수 있을 것 같네요.
libcurl도 괜찮은 초이스일것 같긴 한데, 만약 직장 동료가 MFC 프로그래머라면,
CHttpConnection, CHttpFile 클래스로 URL에서 파일을 받아오고, CAtlRegExp 클래스로 정규식 매칭을 하는 방법을 제안하면 어떨까요 :)
...
글의 주제
안녕하세요, 댓글 고맙습니다 :)
이 글의 주제는 근거 없는 두려움에 대한 것입니다. 처음에는 애자일하지 못한 C++을 보는 저의 부정적인 관점이랄까, 또는 모든 작업에 가장 잘 아는 동일한 도구를 사용하려는 - 그래서 결국엔 작업에 덜 적합한 도구를 사용하게 되고 마는 프로 개발자들의 경향(이건 생각이 좀 지나쳤습니다 반성)에 대해서 쓰려고 했습니다. 그런데 글을 적다보니 생각보다 쉽게 일이 풀리는 거예요. 그 순간에는, '어? 이게 아닌데?' 했습니다만, 앞에서 지적하고자 했던 문제와는 별개로 저 자신에게 '근거없는 두려움'이 있었다는 것을 발견했습니다. 그래서 그런 쪽으로 가까스로 '발전적인' 결론을 내렸고요..
그러나 MFC에 대해서는 제가 아직 두려움을 가지고 있습니다. ^^; 말씀해 주신 것 공부해볼게요.
May the F/OSS be with you..
----------------------------
May the F/OSS be with you..
좋은 글입니다
좋은 글입니다 :-)
생각해 보면 결국 작업에 덜 적합한 도구를 사용하게 되고 마는 것도 근거없는 두려움 때문이 아닐까 해요. 저만 해도 종종 특정 작업에 좀 더 나은 도구를 쓰려다가는 툴문제 연동 문제 속도 문제 숙달 문제 유저 커뮤니티 문제 등등 근거없는 온갖 핑계를 대면서 결국 주 언어와 툴로 돌아와버리고 말거든요.
편견과 꺼려함이 없어야 좋은 프로그래머가 될텐데, 그 장벽을 넘어서기가 쉽지 않네요...
어쨌든 잘 읽었습니다.
require
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
루비를 한창 배우고 있어서 스리쓸쩍 참견을 -ㅅ-
--
마잇
--
마잇
사실
제가 제일 바라던 부류의 댓글 ^^; 고맙습니다
May the F/OSS be with you..
----------------------------
May the F/OSS be with you..
이거 순간 실용주의
이거 순간
실용주의 프로그래머를 읽고 있는건가?.. 라는 착각이 들었군요 ;)
루비의 정규식 처리도 뭐 별다르지 않아요.
정도가 되겠군요.
언제나 삽질 - http://tisphie.net/typo/
프로그래밍 언어 개발 - http://langdev.net
하하
'영광, 영광' 입니다. 루비에는 정규식이 기본 타입인가보군요. 더 간단하네요 !_!
May the F/OSS be with you..
----------------------------
May the F/OSS be with you..
C++은...언어자체는
C++은...언어자체는 너무너무 강력한데...뒷받침은 모조리 서드파티 라이브러리로 때워야되지요 ㅋㅋ
그래서 더더욱 매력을 느끼는지도 모르겠어요~ 정복할수없는 그대~ 일까나요 ^^;;;
루비..많이좋나봐요? 여기저기 자꾸언급되어서리...시간내서 공부해볼까나?;;
멋지네요 ^^
말로만 듣던 루비에 대해서좀 보게 되었네요 ^^ 시간 많으면 JAVA로도 붙여보고 싶네요 ㅎㅎ 틈틈이 들르는 중이라서 죄송 ^^;
열정!! ^^
열정!! ^^
댓글 달기