처음에 주어진 지저분한 모습의 HTML 태그제거기코드에 대한 1차 리펙토링이 끝났습니다. 1차 리팩토링은 매직넘버를 기호상수로 대체하라였습니다. 처음코드보다는 약간 더 좋아진 코드가 만들어졌습니다.
이번에는 모듈화(:12)와 관련된 내용을 다루어볼까 합니다.
'좋은 코드는 가능한 모듈화 되어야 한다.는 얘기는 소프트웨어 공학 입문단계에서 들을 수 있는 격언입니다. 소스코드가 길어지면 보기도 힘들고, 보기 힘든만큼 에러가 발생할 확률도 많습니다. 많은 부분에서 코드의 중복이 발생할 수도 있겠죠.
컴퓨터(:12)는 빠른 연산속도를 장점으로 깊이우선 계산을 특히나 잘하는 기계입니다. 이러한 류의 계산을 하다보면 아무래도 중복된 연산작업이 많아지게 되는데요, 모듈화는 이러한 연산작업을 위한 중복된 코드의 생산을 막아주게 됩니다. 코드의 양도 줄어들게 될 뿐더러, 모듈단위로 테스트와 디버깅(:12)이 가능하니 유지보수 역시 쉽게 되겠죠.
모듈화된 단위 코드는 다른 부분에 재사용될 수 있기 때문에 생산성역시 높아지는 효과를 노릴 수 있습니다. 이러한 모듈화된 코드를 모아서 재사용할 수 있게 만든 코드모음을 라이브러리(:12)라고 합니다.
모듈화는 이를테면 공정의 세분화정도로 보시면 될거 같습니다.
아래는 첫번째 리펙토링 과정을 거친 HTML(:12) 태그(:12)제거 프로그램입니다.
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
using namespace std;
const int MAX_BUF_SIZE=1024;
enum tag_token {TAG_LT = '<', TAG_GT='>'};
int main(int argc, char **argv)
{
int fd;
int readn;
int ridx;
int widx;
char rbuf[MAX_BUF_SIZE] = {0x00,};
char wbuf[MAX_BUF_SIZE] = {0x00,};
int lt_flag = 0;
// used tokenizing
char seps[] = "()|{}, \t.;&-[]\"\':`";
char *tr;
int tag_status = 0;
int offset = 0;
if (argc != 2)
{
fprintf(stderr, "Usage : %s [file]\n", argv[0]);
return 1;
}
if ((fd = open(argv[1], O_RDONLY)) < 0)
{
return 1;
}
while ((readn = read(fd, rbuf+offset, MAX_BUF_SIZE)) > 0)
{
ridx = 0;
widx = 0;
while(ridx < readn)
{
if (rbuf[ridx] == TAG_LT)
{
lt_flag++;
}
if (rbuf[ridx] == TAG_GT)
{
lt_flag--;
if (lt_flag == 0)
{
ridx++;
continue;
}
}
if (lt_flag == 1)
{
ridx++;
continue;
}
// Special Chracter 제거
if (rbuf[ridx] == '&')
{
int i =0;
char special[10] = {0x00,};
for(i = 0; i < 10; i++)
{
if (rbuf[ridx+i] == ';')
{
ridx = (ridx+i);
break;
}
}
}
if (rbuf[ridx] == '\n' || rbuf[ridx] == '\r')
wbuf[widx] = ' ';
else
wbuf[widx] = rbuf[ridx];
widx++;
ridx++;
}
tr = strtok(wbuf, seps);
while (tr != NULL)
{
printf("%s\n", tr);
tr = strtok(NULL, seps);
}
memset(rbuf, 0x00, MAX_BUF_SIZE);
memset(wbuf, 0x00, MAX_BUF_SIZE);
}
return 0;
}
흔히 말하는 통짜쏘스로 하나의 함수(main 함수)내에서 모든 작업을 처리하고 있습니다. 이것을 여러개의 단위 모듈로 분리하도록 하겠습니다.
이 프로그램은 다음의 3개의 단위로 구성되어 있습니다.
인자처리 : 프로그램의 실행인자(:12)를 분석하고 처리하는 부분
파일읽기 : 처리하는 파일(:12)을 읽는 부분
HTML 태그제거 : 입력된 문자열에서 HTML 태그를 제거하는 부분
출력부분 : HTML(:12)태그를 제거하고 tokenizing(:12)된 문자열 데이터를 출력한다.
인자처리 모듈
사용자의 인자(:12)를 검사해서 필요한 액션을 취합니다. 독립된 프로세스라고 볼 수 있겠지만 main()함수에 매우 크게 종속이 되므로 따로 모듈로 분리하는 것은 오히려 프로그램의 가독성을 해칠 수 있습니다. 이 프로그램의 경우 단지 하나의 인자만 주어지므로, 굳이 모듈화 할 필요는 없을 거라고 생각됩니다. 해서 if 문을 switch 문으로 변경하고, getopt()를 이용해서 인자의 처리를 좀 더 유연하게 하는 정도로 수정을 하겠습니다.
만약 C++(:12)을 사용하고 있다면, 파일작업관련 클래스를 하나 만들어서 동일한 클래스네임으로 메서드만 달리해서 호출하는 방식을 사용하면 편할겁니다. 그렇지만 C(:12)스타일로 코딩된 이 프로그램은 이러한 방법을 사용하지 않을 생각입니다. 물론 구조체와 함수포인터(:12)를 사용해서 비슷하게 구현할 수는 있겠지만, 오히려 리펙토링 입장에서 나쁠 수도 있겠구요. C든 C++에서든 객체화를 통한 은닉은 다음 기회에 다루도록 하겠습니다.
여기에서 파일관련 작업은 수정하지 않고, 읽어들인 문서에서 HTML(:12)을 제거하는 부분만 모듈화 하도록 하겠습니다.
다음은 만들어진 함수입니다.
int lt_flag = 0;
char *html_trim(char *src, char *dst, int size)
{
int ridx = 0;
int widx = 0;
while(ridx < size)
{
if (src[ridx] == TAG_LT)
{
lt_flag++;
}
if (src[ridx] == TAG_GT)
{
lt_flag--;
if (lt_flag == 0)
{
ridx++;
continue;
}
}
if (lt_flag == 1)
{
ridx++;
continue;
}
if(src[ridx] == '&')
{
int i = 0;
char specia[10] = {0x00,};
for (i = 0; i < 10; i++)
{
if (src[ridx+i] == ';')
{
ridx= (ridx+i);
break;
}
}
}
if (src[ridx] == '\n' || src[ridx] =='\r')
{
dst[widx] = ' ';
}
else
{
dst[widx] = src[ridx];
}
widx++;
ridx++;
}
return dst;
}
이제 main() 함수는 아래와 같이 단순해졌습니다.
int main(int argc, char **argv)
{
int fd;
int readn;
int ridx;
int widx;
char rbuf[MAX_BUF_SIZE] = {0x00,};
char wbuf[MAX_BUF_SIZE] = {0x00,};
int lt_flag = 0;
int test_argv=0;
int c;
char *src_file;
// used tokenizing
char seps[] = "()|{}, \t.;&-[]\"\':`+#=";
char *tr;
int tag_status = 0;
int offset = 0;
while((c = getopt(argc, argv, "hf:")) != -1)
{
switch(c)
{
case 'h':
break;
case 'f':
src_file = optarg;
test_argv = 1;
break;
default:
break;
}
}
if (!test_argv)
{
help();
return 1;
}
if ((fd = open(src_file, O_RDONLY)) < 0)
{
return 1;
}
while ((readn = read(fd, rbuf+offset, MAX_BUF_SIZE)) > 0)
{
html_trim(rbuf, wbuf, readn);
tr = strtok(wbuf, seps);
while (tr != NULL)
{
printf("%s ", tr);
tr = strtok(NULL, seps);
}
memset(rbuf, 0x00, MAX_BUF_SIZE);
memset(wbuf, 0x00, MAX_BUF_SIZE);
}
return 0;
}
make를 통한 모듈별 분할 컴파일
이렇게 모듈을 분할시켜 놓으면, 각각의 모듈을 포함하는 소스파일을 따로 만들어서 관리할 수 있습니다. 이럴 경우 수정된 부분만 컴파일이 되기 때문에, 컴파일 시간을 줄일 수 있다는 잇점을 얻을 수도있습니다. 이에 대한 자세한 내용은 make(:12) 관련문서를 참고하시기 바랍니다.
인자처리 모듈
HTML 제거
make를 통한 모듈별 분할 컴파일
Recent Posts
Archive Posts
Tags