2013년 12월 31일 화요일

IT 주변기기 박람회


한마디로 표현하자면...

대실망

핸드폰 케이스와 스피커만이 가득했다.. -_-;

가장 볼만했던건 3D프린터와 의료기기를 스마트폰과 접목시킨 무슨 대학이었는데... 기억이 안난다.(가장 볼만했던걸 사진을 찍지 않았어 ㅠ)

캔고루에서 무료초정장으로 들어갔다.

조금은 관심이 갔던 무선충전

무선충전 비싸!!

카드형 USB

컴터를 제어하는 리모컨? 같은 기기

!?

코엑스에서 이런걸 보게될줄이야...!!


어찌되었건 실망만 가득한 주변기기 박람회




2013년 12월 29일 일요일

코딩호러의 이펙티브 프로그래밍 - 1



좋은 책을 읽으며 좋은문구가 많기에 시간을 들여 조금씩 옮겨보고자 한다.

실제로 개발을 하다보면 다른사람이 짠 코드나 오픈소스를 사용하게 된다.

이때의 문제점은 다른사람이 짠 코드는 못알아 보겠다는 것이고 , 오픈소스의 경우는 열어봤을 때 프로젝트의 크기가 너무나 커서 어디부터 봐야할지 막막하다는 점이다.

회사에서 개발을 한다고 해도 개발문서가 형편없을 때도 있으며(심지어는 존재하지 않을때도 있다.), 업데이트를 하면 할때마다 문서를 바꿔야 하는데 그걸 깜빡 잊는 개발자가 있다던가...

중견기업에 다니는 친구가 있는데 이 친구의 고민은 프로젝트의 크기가 너무나도 커서 모두 이해하려면 시간이 많이 걸린다는 것이다.


이런 사람들을 위해 이 책에선 "소스를 읽는 법을 배워야 한다" 라고 하고 있다.


좋은문구가 있길래 옮겨본다.


"루크 소스를 읽는 법을 배우게"


해커뉴스에서 블랜드 블룸이 쓴 글이다.


15살쯤에 나는 마이크로소프트 플랫폼을 기반으로 개발 경력을 시작했다.
마이크로소프트에서 비주얼 스튜디오로 각종 기능을 통합하는 소프트웨어 개발자로 근무하기 시작한 것이다. 그리하여 나는 비주얼 베이직 코드를 처음 작성한 이래로 10년 정도의 시간이 흐르자 내부를 들여다 볼 수 없도록 닫혀 있는 라이브러리는 더 이상 사용하고 싶지 않게 됐다.

소프트웨어를 사용하는 것은 소프트웨어를 만드는 것과 다르다. 어떤 소프트웨어를 그것이 담고 있는 핵심 기능을 위해 사용하는 경우에는 작업 자체가 잘 알려진 경로를 따른다. 그 경로를 따라가던 많은 사람들이 이미 이러저러한 문제에 봉착했을 것이며, 소프트웨어를 제작한 사람에게 그러한 문제를 해결해 달라고 요청했을 것이다. 하지만 소프트웨어를 만드는 경우에는 뭔가 새로운 경로를 밟게 된다. 그러한 경로는 너무나 많기 때문에 아무도 가지 않은 길을 걷게 되는 것이 드문 일이 아니며, 그래서 종종 낯선 구석이나 아직 충분히 검증되지 않은 코드의 경로를 밟게된다. 근본적인 문제를 해결하지 못하고 그저 우회해서 피해간 예외적인 상황도 경험하게 될 것이다.

문서는 때로 불완전하다. 심지어는 잘못된 정보를 담고 있는 경우도 있다. 하지만 소스코드는 거짓말하지 않는다.
숙련된 개발자라면 문서보다 소스코드를 읽는 편이 더 빠를때가 많다. 특히 그가 소프트웨어 패키지의 아키텍처에 이미 익숙한 경우라면 더욱 그렇다.
나는 지금 중간 정도 규모의 팀에서 일하고 있다. 이 팀은 여러 개의 스타트업 회사와 함께 프로젝트를 수행한다. 그런데 각 회사의 CTO나 엔지니어들이 우리 팀으로 찾아와서 조언을 구하는 일이 있다.
이 사람들이 자기가 이용하는 기술적 스택에 문제가 있다고 보고하면 나는 이렇게 대답한다.

"소스코드는 읽어보셨나요?"

나는 개발자들에게 자기 프로젝트에서 사용되는 소프트웨어 제품의 소스코드를 로컬 폴더에 복사해 두고 수시로 살펴보라고 권장한다. 많은 사람들이 처음에는 이렇게 하길 두려워한다.


"그 프로젝트는 너무 커요, 원하는 내용을 찾을 수 없다고요!"
"나는 그것을 이해할 만큼 똑똑하지 않답니다."
"그 코드는 정말 끔찍하군요! 도저히 살펴볼 엄두가 안 납니다."  

라고 말한다.

하지만 소스코드 전체를 살펴보라는 것이 아니다. 그저 필요한 경로를 따라가면서 부분적으로만 이해하면 된다. 자신의 소프트웨어가 기초로 삼고 있는 플랫폼을 이해하지 못하면서 어떻게 자기가 만든 소프트웨어를 이해할 수 있겠는가? 숙련되지 못한 개발자들이 아름답다고 말하는 것은 대개 표면적인 것에 그칠 때가 많다. 그리고 그들이 끔찍하다고 말하는 것은 최고의 해커가 작성한, 실전에서 단련되어 실제 제품으로 사용될 준비가 돼 있는 견고한 코드인 경우가 많다.
이러한 조언을 하고 나서 1,2년 뒤에, 나에게 찾아와서 자신을 다른 사람이 작성한 코드 속에서 허우적거리면서 헤엄치게 했던 것에 감사를 표하는 사람들이 있었다. 그들은 이제 전보다 나은 개발자가 돼 있다. 그리고 도대체 어떻게 예전에는 소스코드를 읽지도 않으면서 일을 했는지 알 수 없다는 식으로 말하기도 한다.

회사를 운영하는 경우를 생각해보자. 당신의 소프트웨어에서 버그가 발견됐다면 고객들은 그것이 당신의 잘못인지 아니면 리누스나 레일스 개발자의 잘못인지를 따지지 않는다. 그들에게는 당신의 소프트웨어에 버그가 있다는 사실이 중요하다. 다른 소프트웨어에서 발생하는 버그조차 모두 나의 것이 되기 때문에 그들의 소프트웨어도 나의 소프트웨어인 것과 마찬가지다. 뭔가가 잘못됐다면 무엇이 잘못됐는지 원인을 찾아 수정해야 한다. 위험, 유지보수 비용, 그리고 수정하는데 필요한 시간을 최소화하려면 정확한 지점에서 문제를 고쳐야 한다. 경우에 따라서는 재빨리 문제를 우회하는 편이 나을 때도 있다. 때로는 사용하던 컴파일러를 새로 컴파일해야 하는 경우도 있다. 어떤 경우에는 스택의 위쪽에 있는 다른 사람에게 문제를 수정해 달라고 부탁할 수 있을 때도 있지만 버그를 스스로 수정해야 하는 경우도 그만큼 자주 발생한다.


  • 내부를 공개하지 않는 소프트웨어 회사의 제품을 사용하는 경우에는 두가지 선택이 있다. 하나는 관용에 호소하는 것이고 다른 하나는 문제를 우회하는 것이다.



  • 실력이 부족한 개발자에게 의존하는 오픈소스 회사의 경우에는 위와같이 내부를 공개하지 않는 소프트웨어 회사와 동일한 방식으로 행동하는 경우가 많다.



  • 오래된 회사는 제품의 변종이나 패치 등을 유지보수하는데 필요한 근육을 아주 느리게 단련하는 경향이 있다.



진정한 해커는 이러한 사실을 받아들인다. 그것이 내 컴퓨터 위에서 작동하는 것이라면 그건 내 소프트웨어다. 그것에 대해 나는 모든 것을 책임진다. 나는 그 소프트웨어를 잘 이해해야 한다. 소스코드를 이용해 소프트웨어를 만드는 것은 당연한 일이지 예외적인 일이 아니다. 나는 내 환경을 전적으로 통제해야 하며, 내가 사용하는 다른 소프트웨어도 모두 통제해야 한다.






2013년 12월 22일 일요일

왜 개발자가 되었을까..?


요새 회사를 그만두고 취업전선으로 돌아왔다

취업준비를 하면서 친구들에게 많은 이야기를 듣는다. '행정학과 나와서 다른일 할수도 있고 그런데 왜 컴퓨터쪽 일만 하려고 하냐?'

그 질문을 들었을때는 '배운게 그거니까..' 라고는 말하지만 다른사람들도 마찬가지더라..

그러면서 다시한번 생각해 본다.

왜 개발자가 되었을까?


내 고민을 위해 처음으로 돌아가서 생각해보고자 한다.

내가 가장 처음으로 컴퓨터에 흥미를 느꼈던 때는 초등학교 때였다.

학교 앞에서 병아리를 사본적도, 장난감을 사본적도 있다.(참고로 병아리는 닭이 되었다. 닭되는게 어렵다는데 왜 그렇게 잘크는지..;;;)

기억나지 않나? 학교앞 병아리


그런데 어느날 왠일인지 학교앞에서 책을 팔았다. 그 당시의 가격은 삼천원! 엄마를 졸라서 책을 사본적은 이때가 처음이자 마지막인듯 싶다 ^^;;

책 이름은 잘 생각이 안나지만, 컴퓨터의 기초, 역사같은 것을 만화로 그려놓은 책이었다.


입력,출력,연산,제어장치 이런걸 만화로 설명하고, 컴퓨터의 역사를 설명해 놓은 책이었는데 내가 살면서 그렇게 책을 좋아하진 않았지만 이 책은 몇번이나 읽었던지 페이지가 너덜너덜 해졌더라...


그 이후에 컴퓨터에 대한 막연한 관심으로 컴퓨터 학원을 다니고 워드프로세서, 정보기기,정보처리 운용기능사를 초등학교, 중학교 1학년때 모두 땄던 걸로 기억이 난다.


그때는 참 부지런했던 것 같다. 학교에서 돌아와서 컴퓨터학원으로 가서 저녁내내 거기서 살았으니 말이다 -_-;;(원장선생님이 싫어하진 않았다.)


그런데 왜일까... 중학교때부터 소위 말하는 종합학원을 다니기 시작하면서 컴퓨터에 대한관심이 멀어졌다. 12시가 넘어서 집에 돌아오고 친구들과 우스갯소리로 좀있다 보자고 하는게 일상이 되었다.


그렇게 나는 내가 선택하지 못하고 휩쓸려만 다녔었다.
그러던 와중에 리버스엔지니어링을 접했었고, 그래서는 안되지만 '불법'이 주는 매력에 빠졌던 것 같다.
그러면서 다시 컴퓨터의 세계로 왔다.


처음에 C가 어렵다고 해서 Java를 선택하여 공부를 했었다.
슬슬 객체가 무엇인지, 인스턴스가 무엇인지를 파악했을 때 전문대를 졸업했다.
그 때 바로 직장에 들어갈 수도 있었지만, 지식의 목마름 때문이었을까.. 편입을 하고 첫번째로 받은 과제가 C로 LinkedList를 짜시오..
자바를 배우며 사용할 줄만 알았던 내게는 너무 어려운 과제였다. 전문대에서 배울때는 그런 과목 자체를 배운적이 없었다. 거기다가 교수나 친구 누구하나가 뭘 공부해야 한다고 말해준 사람도 없었다. 지금생각하면 핑계에 불과했지만, 그 때는 너무나 어렸던 것 같다.


그 이후 C를 공부하고, 자료구조를 공부하고 알고리즘을 공부하면서 느끼는 것은 
개발자는 '너무나 공부할게 많다.' 였다.


그런데도 난 왜 개발자가 되었을까..?
장황하게 이야기 했지만...그에 대한 답은 간단한 것 같다. 백지에서 시작해서 조금씩 조금씩 프로그램이 형태를 갖추어 가고, 개발하면서 부딪히는 문제를 해결했을 때, 그 때의 감정들 때문에 개발자를 한다고 생각한다.


2013년 12월 16일 월요일

txt 파일에서 메일 추출


커뮤니티 활동을 하다보니 과제를 물어보시는분이 있더라..

과제내용인 즉...
특정폴더(하위 디렉토리 내의 파일도 검사 해야 함)에 저장되어 있는 텍스트 파일(txt)의 내용을 검사하여 파일 내 저장 되어있는 “이메일주소”를 찾아 내는 프로그램을 만드시오.

C로 짰고 밥먹으면서 짰던거라 조금 코드가 더럽다.

윈도우용 dirent.h를 include해야한다.
http://www.mediafire.com/view/3ufjapid9foqu9q/dirent.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include <stdio.h>
#include <sys/types.h>
#include "MyQueue.h"
#include "dirent.h"
#pragma warning(disable:4996)
myQueue mQueue;
static int
find_directory(
    const char *dirname)
{
    DIR *dir;
    char buffer[PATH_MAX + 2];
    char *p = buffer;
    const char *src;
    char *end = &buffer[PATH_MAX];
    int ok;
    char * extName;
    /* Copy directory name to buffer */
    src = dirname;
    while (p < end  &&  *src != '\0') {
        *p++ = *src++;
    }
    *p = '\0';
    /* Open directory stream */
    dir = opendir (dirname);
    if (dir != NULL) {
        struct dirent *ent;
        /* Print all files and directories within the directory */
        while ((ent = readdir (dir)) != NULL) {
            char *q = p;
            char c;
            /* Get final character of directory name */
            if (buffer < q) {
                c = q[-1];
            } else {
                c = ':';
            }
            /* Append directory separator if not already there */
            if (c != ':'  &&  c != '/'  &&  c != '\\') {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
                *q++ = '\\';
#else
                *q++ = '/';
#endif
            }
            /* Append file name */
            src = ent->d_name;
            while (q < end  &&  *src != '\0') {
                *q++ = *src++;
            }
            *q = '\0';
            /* Decide what to do with the directory entry */
            switch (ent->d_type) {
            case DT_REG:
                /* Output file name with directory */
                extName = strrchr(ent->d_name, '.');
                if(extName == NULL) break;
                if(strcmp(extName, ".txt") == 0)
                {
                    //printf("fine txt file : %s\n", ent->d_name);
                    //printf ("%s\n", buffer);
                    enqueue(&mQueue, buffer);
                }
                break;
            case DT_DIR:
                /* Scan sub-directory recursively */
                if (strcmp (ent->d_name, ".") != 0  
                        &&  strcmp (ent->d_name, "..") != 0) {
                    find_directory (buffer);
                }
                break;
            default:
                /* Do not device entries */
                /*NOP*/;
            }
        }
        closedir (dir);
        ok = 1;
    } else {
        /* Could not open directory */
        printf ("Cannot open directory %s\n", dirname);
        ok = 0;
    }
    return ok;
}
void myDir(const char * dirPath)
{
    DIR * dp;
    struct dirent * ent;
    char * extName;
    dp = opendir(dirPath);
    if(dp != NULL)
    {
        while(1)
        {
            ent = readdir(dp);
            if(ent == NULL)
                break;
            extName = strrchr(ent->d_name, '.');
            if(strcmp(extName, ".txt") == 0)
                printf("fine txt file : %s\n", ent->d_name);
        }
    } 
}
int IsAvailableEMail(const char * srcMail)
{
    // 찾음 : 1
    // 없음 : 0
    
    int iAtCount = 0;   //@ 위치
    int iDotCount = 0;  // . 위치
    int i;
    char * eMail = (char*)malloc( strlen(srcMail) + 1 );
    strcpy(eMail, srcMail);
    
    if(strcmp(eMail, "") == 0)    return 0;
    
    for(i = 0; i < strlen(eMail); i++)
    {
        if(i > 0 && eMail[i] == '@' ) iAtCount = i+1;    // ①
        if(iAtCount > 0 && i > iAtCount && eMail[i] == '.') iDotCount = i+1;   // ②
    }
    free(eMail);
    if(i > iDotCount && iAtCount > 0 && iDotCount > 0) return 1;     // ③    
    else return 0;
}
int main()
{
    //입력받은 디렉토리에서 확장자가 .txt인 파일을 찾고
    //큐에 넣은 후 이메일 주소를 뽑아내는 구조
    //큐 초기화
    FILE * fp;
    char buf[1024];
    char * path;
    const char * filePath;
    initQueue(&mQueue);
    
    //디렉토리 순회
    find_directory("C:\\Mail");
    ////제대로 나오는지 출력
    //while( !empty(&mQueue) )
    //{
    //    printf("%s\n", frontQueue(&mQueue));
    //    deQueue(&mQueue);
    //}
    //
    
    //찾은 파일을 열어서 이메일 주소를 확인
    while( !empty(&mQueue) )
    {
        path = frontQueue(&mQueue);
        filePath = path;
        fp = fopen(filePath, "r");
        if(fp != NULL)
        {
            int isEmail;
            char * ch;
            int i;
            //while(fgets(buf, 1024, fp))
            while( 0 < fscanf(fp, "%s", buf) )
            {
                //printf("%s", buf);
                isEmail = IsAvailableEMail(buf);
                if(isEmail)
                {
                    ch = strchr(buf, '"');
                    if(ch != NULL)
                        *ch = ' ';
                    ch = strchr(buf, '(');
                    if(ch != NULL)
                        *ch = ' ';
                    ch = strchr(buf, ')');
                    if(ch != NULL)
                        *ch = ' ';
                    printf("%s\n", buf);
                }
            }
            fclose(fp);
            deQueue(&mQueue);
        }
    }
    destroyQueue(&mQueue);
    
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include <stdio.h>
#include <sys/types.h>
#include "MyQueue.h"
#include "dirent.h"
#pragma warning(disable:4996)
myQueue mQueue;
static int
find_directory(
    const char *dirname)
{
    DIR *dir;
    char buffer[PATH_MAX + 2];
    char *p = buffer;
    const char *src;
    char *end = &buffer[PATH_MAX];
    int ok;
    char * extName;
    /* Copy directory name to buffer */
    src = dirname;
    while (p < end  &&  *src != '\0') {
        *p++ = *src++;
    }
    *p = '\0';
    /* Open directory stream */
    dir = opendir (dirname);
    if (dir != NULL) {
        struct dirent *ent;
        /* Print all files and directories within the directory */
        while ((ent = readdir (dir)) != NULL) {
            char *q = p;
            char c;
            /* Get final character of directory name */
            if (buffer < q) {
                c = q[-1];
            } else {
                c = ':';
            }
            /* Append directory separator if not already there */
            if (c != ':'  &&  c != '/'  &&  c != '\\') {
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
                *q++ = '\\';
#else
                *q++ = '/';
#endif
            }
            /* Append file name */
            src = ent->d_name;
            while (q < end  &&  *src != '\0') {
                *q++ = *src++;
            }
            *q = '\0';
            /* Decide what to do with the directory entry */
            switch (ent->d_type) {
            case DT_REG:
                /* Output file name with directory */
                extName = strrchr(ent->d_name, '.');
                if(extName == NULL) break;
                if(strcmp(extName, ".txt") == 0)
                {
                    //printf("fine txt file : %s\n", ent->d_name);
                    //printf ("%s\n", buffer);
                    enqueue(&mQueue, buffer);
                }
                break;
            case DT_DIR:
                /* Scan sub-directory recursively */
                if (strcmp (ent->d_name, ".") != 0  
                        &&  strcmp (ent->d_name, "..") != 0) {
                    find_directory (buffer);
                }
                break;
            default:
                /* Do not device entries */
                /*NOP*/;
            }
        }
        closedir (dir);
        ok = 1;
    } else {
        /* Could not open directory */
        printf ("Cannot open directory %s\n", dirname);
        ok = 0;
    }
    return ok;
}
void myDir(const char * dirPath)
{
    DIR * dp;
    struct dirent * ent;
    char * extName;
    dp = opendir(dirPath);
    if(dp != NULL)
    {
        while(1)
        {
            ent = readdir(dp);
            if(ent == NULL)
                break;
            extName = strrchr(ent->d_name, '.');
            if(strcmp(extName, ".txt") == 0)
                printf("fine txt file : %s\n", ent->d_name);
        }
    } 
}
int IsAvailableEMail(const char * srcMail)
{
    // 찾음 : 1
    // 없음 : 0
    
    int iAtCount = 0;   //@ 위치
    int iDotCount = 0;  // . 위치
    int i;
    char * eMail = (char*)malloc( strlen(srcMail) + 1 );
    strcpy(eMail, srcMail);
    
    if(strcmp(eMail, "") == 0)    return 0;
    
    for(i = 0; i < strlen(eMail); i++)
    {
        if(i > 0 && eMail[i] == '@' ) iAtCount = i+1;    // ①
        if(iAtCount > 0 && i > iAtCount && eMail[i] == '.') iDotCount = i+1;   // ②
    }
    free(eMail);
    if(i > iDotCount && iAtCount > 0 && iDotCount > 0) return 1;     // ③    
    else return 0;
}
int main()
{
    //입력받은 디렉토리에서 확장자가 .txt인 파일을 찾고
    //큐에 넣은 후 이메일 주소를 뽑아내는 구조
    //큐 초기화
    FILE * fp;
    char buf[1024];
    char * path;
    const char * filePath;
    initQueue(&mQueue);
    
    //디렉토리 순회
    find_directory("C:\\Mail");
    ////제대로 나오는지 출력
    //while( !empty(&mQueue) )
    //{
    //    printf("%s\n", frontQueue(&mQueue));
    //    deQueue(&mQueue);
    //}
    //
    
    //찾은 파일을 열어서 이메일 주소를 확인
    while( !empty(&mQueue) )
    {
        path = frontQueue(&mQueue);
        filePath = path;
        fp = fopen(filePath, "r");
        if(fp != NULL)
        {
            int isEmail;
            char * ch;
            int i;
            //while(fgets(buf, 1024, fp))
            while( 0 < fscanf(fp, "%s", buf) )
            {
                //printf("%s", buf);
                isEmail = IsAvailableEMail(buf);
                if(isEmail)
                {
                    ch = strchr(buf, '"');
                    if(ch != NULL)
                        *ch = ' ';
                    ch = strchr(buf, '(');
                    if(ch != NULL)
                        *ch = ' ';
                    ch = strchr(buf, ')');
                    if(ch != NULL)
                        *ch = ' ';
                    printf("%s\n", buf);
                }
            }
            fclose(fp);
            deQueue(&mQueue);
        }
    }
    destroyQueue(&mQueue);
    
    return 0;
}


실행결과

조금 어설프긴 하지만 돌아가는거에서 만족..!