[PREV]
[1]
[NEXT]
|
|
|
COBOL call flow 그려보기
개발이야기 |
2008/03/24 02:51
|
|
|
최근에 COBOL로 된 코드를 잔뜩 볼 일이 생겼습니다. 코볼 소스 라인이 긴게 주석을 합쳐서 2만 라인이 넘는 것들도 있고해서 분석할 엄두가 안 나더군요.. 어떻게 call flow라도 알 방법이 없을까 하고 소프트웨어를 뒤져보니 상용만 있고(과자 구하기가 힘들더군요) 오픈소스쪽도 소스포지를 좀 뒤져봤는데(ㅠ.ㅠ 나중에 얼마 안지나서 이상한 이름으로 존재하는 것이 있더군요..) 이 답답한 사정을 shining군에게 토로했더니, 30분 정도 브레인스토밍 30분 짝 프로그래밍을 해서 약식의 콜 흐름을 그려봤습니다.
일단 해야할 일을 정의했습니다. call flow를 그린다는 일은 함수가 함수를 부르는 것을 그래프로 보여주는 작업입니다. 즉 caller와 callee를 모두 구하면 됩니다. 그래프를 그려주는건 리눅스에서 library dependencies를 간단하게 graphviz로 그려주는 쉘스크립트를 보고 참조하기로 했고, 코볼의 문법 자체가 워낙에 영문에 가까워서 파싱 자체가 어렵지 않았습니다.
함수를 나타내는 부분
- 함수명 SECTION.
- 데이터섹션 예약어는 제외합니다.
함수를 부르는 부분
- CALL "외부함수"
- CALL '외부함수'
- PERFORM 내부함수
- PERFORM으로 시작하는 것중 문법적인 예외사항 제거
일단 가능한 빠른 시간내에 구현을 제약사항으로 처리했기 때문에 최대한 간단하게 호출하는 3부분만 파싱해서 구현했습니다. 중복제거 및 awk , shell 의존성 제거 먼저 저 3 가지 타입만 가볍게 긁어오는 것을 shining군이 awk로 작성을 했습니다. 정규표현식으로 해당 부분을 긁어서 graphviz용 입력파일을 작성했습니다. 생성된 그래프 파일입니다. (함수 이름은 모두 변경했습니다.)
 코드는 다음과 같습니다.
#!/usr/bin/env ruby
# FILE EXTENSION $COBOL_EXTENSION = '.cbl' $DOT_EXTENSION = '.dot' $PNG_ENTENSION = '.png'
# DOT SETTING if RUBY_PLATFORM.match(/mswin32$/) then # windows $DOT_EXECUTE=ENV['ProgramFiles'] + '\Graphviz\bin\dot.exe' if ENV['ProgramFiles'] $FONTNAME="Tahoma" else $DOT_EXECUTE="dot" end $FONTSIZE=12
# perform reserve words PERFORM_RESERVES = [ 'AFTER', 'BEFORE', 'BY', 'FROM', 'TEST', 'UNTIL', 'VARYING', 'WITH' ]
def cobol_call_flow_generate( filename ) # cobol file extension check return if File.extname(filename) != $COBOL_EXTENSION
# dot tempoary filename dot_filename = ARGV[0]+$DOT_EXTENSION # dot output image filename output_filename = ARGV[0]+$PNG_ENTENSION # function call related calls = []
# current sections name current_function = "INVALID-SECTION" # cobol file read & parsing File.open(ARGV[0], 'r') { |f| while input_line = f.gets case input_line when /^\s*\*/ next when /^\s*([A-Z0-9-]*)\s*SECTION\.\s*\n$/ current_function = $1 when /^\s*PERFORM\s*([A-Z0-9-]*)/ calls << [ current_function, $1, :perfom ] unless PERFORM_RESERVES.include?($1) or calls.include?([ current_function, $1, :perform ]) when /^\s*CALL\s*['"](.*)['"]/ calls << [current_function, $1, :call ] unless calls.include?([current_function, $1, :call ]) end end }
# write dot file File.open(dot_filename, 'w') { |wf| wf.puts('digraph DependencyTree {') wf.puts(" node [ fontname=\"#$FONTNAME\", fontsize=#$FONTSIZE ]; "); wf.puts(' "A-MAIN" [shape=Mdiamond];') wf.puts(' "Z-FINISH" [shape=Mdiamond];') calls.each { |i| wf.puts(" \"#{i[1]}\" [shape=diamond,style=filled,color=lightgray]; ") if i[2] == :call wf.puts(" \"#{i[0]}\" -> \"#{i[1]}\"") } wf.puts('}') } # execute dot dot_command = "\"#{$DOT_EXECUTE}\" -Tpng #{dot_filename} -o #{output_filename}" system(dot_command) # remove tempoary dot file require 'fileutils' FileUtils.rm(dot_filename) end
# main if ( ARGV.size < 1 ) puts "#{File.basename $0} [cobol files]" exit 0 end
ARGV.each { |cobol_file| cobol_call_flow_generate( cobol_file ) }
현재 가지고 있는 문제점은 다음과 같습니다.
하나의 함수에서 여러번 콜을 하는 것을 고려하지 않았습니다.
- 외부함수와 내부함수를 구분하지 않습니다.
- 다른 파일을 찾아서 연동하는 부분이 빠져있습니다.
- C 함수와 COBOL함수를 구분하지 않습니다.
|
KOEI
2008/03/24 02:51
2008/03/24 02:51
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/501 |
|
|
|
|
|
Ruby 그리고 AIX
개발이야기 |
2007/03/26 02:17
|
|
|
AIX에서 Ruby 돌리기?
별로 생각하고 싶지 않은 스토리인데.. 상황이 그리 되는 어쩔 수 없으니...
1차시도 1.8.5을 visual age c compiler로 컴팔하기 => __ceil, __floor를 못 찾겠다 꾀꼬리 2차시도 1.8.5를 gcc로 컴팔하기 => 컴팔이 잘 된다 오예~, 그러나 ext/socket 은 왜 컴파일을 안 하는데. 소켓써야하는데 ㅠ.ㅠ 3차시도 1.8.6을 gcc로 컴팔하기 => 오옹 그냥 해봤는데 socket도 들어있네 아싸
흐웅 그나저나 이 머신 왜이리 접속하는데 오래걸려. 그래도 행복하게 ruby를 쓸 수 있네 ㅋㅋ 들어가는게 느리니 느리겠거니...
require 'socket'
t = TCPSocket.new('www.daum.net',80) # 가는 세월 그누구가~ 막을수가 있나요 ㅠ.ㅠ
요게 느린 것도 아마 AIX 요 머신이 특이한 걸 것이야.. 얼래 그런데 ftp로 데이터 받아오기나, 그 외의 것들은 빠른데.... -ㅅ- 알고보니 Ruby에서 ext/socket 모듈로 Socket 생성시 접속이 오라지게 느리게 된다. 꾸에엑 이를 어쩌나.. 소켓이 필요한 부분이 TCPSocket으로 접속도 해야하고, open-uri를 이용해서 ftp에서 파일도 받아와야하는데..
고객은 앞에 있고.. 데모 보여줘야하는데... ㄷㄷㄷ 부랴 부랴 netcat, wget으로 두부분을 스슥 바꿨다. UNIX는 저에게 system, popen과 ``를 주셨나이다. ㄳㄳ
일단 일단락.. 그러나 상황이 조금 바뀌어서 AIX에서 Ruby/Pcap도 써야할 상황. 컴파일을 하는데 잘 안 되네.. -ㅅ- 컴파일하고 나니 모듈 로딩이 안 된다.
구글링하던 도중 AIX에서 Ruby Building할때 1차시도의 해결책을 찾았다. 알고 보니 망할 Visual Age Compiler 6.0의 버그문제. python을 비롯한 다른 오픈프로젝트에서도 관련 얘기가 나오네. -ㅅ- 그래서 vac로 다시 한번 컴팔해봤다.
4차시도 잘 된다. 그러나 확장모듈 로딩이 죄다~ 안 된다. 쩝쩝쩝.. gcc로 다시 회귀 그러면서 아차차 생각난 것... 5차시도 CC="gcc -maix64" 요리 하니 ext/socket에서 지연되던 문제가 사라졌다. 성능도 조금 좋아진 느낌이다. GOOD!
그러나 역시 Ruby/pcap은 로딩이 되지 않는다. (해결되면 포스팅 이어짐 안되면 한동안 좀비모드일듯.. C++ Porting 고고싱 T-T) libpcap 컴파일시에 CC="gcc -maix64" CFLAGS="-g -O2" ./configure --with-pcap=dlpi --disable-ipv6 로 옵션을 줘서 해결. README.aix문서를 읽어보고 bpf를 사용하지 않고 dlpi를 사용하게 하니 문제없이 작동 - 머 특정 용도 활용이라서 원하는 기능만 돌아가는지 확인. (Fixnum을 Symbol로 사용하짐 랄라는 에러가 3개 뜬다 덜덜덜.. -ㅅ-)
추가적인 수정 사항 (흑 2006.03.29 새벽3시 퇴근 ㅠ.ㅠ) 1. Endian 문제가 있어서 (실제 데이터는 리틀엔디안 AIX는 빅엔디안) 2바이트짜리 길이를 나타내는 부분이었는데 len.unpack('s')를 len.reverse.unpack('s')로 수정 2. 이상하게 tcpdump에서 쓴 패킷 캡쳐시간이 리눅스에서와 AIX에서 14시간 정도 차이가 난다. 그냥 그런가 부다 하고 처리하는 중.
오랜만에 AIX만지면서 알게된 잼있는거 linux기반의 opensource들과 AIX가 좀 더 친해졌다. 미리 컴파일된 바이너리를 http://aixpdslib.seas.ucla.edu/ 에서 잘 받아썼는데 IBM에서 rpm으로 공식지원하는 녀석이 생겼다. 이름도 그럴듯하다~ AIX Toolbox for Linux Applications 엄훠 이젠 rpm으로 설치하세요~ 또하나는 netcat, libpcap, tcpdump 그리고 몇몇 소프트웨어를 컴팔하면서 간단하게 에러나는 것을 잡은 부분 정리
1) extern int h_errno; 를 만나면 가뿐히 지워주세요. (AIX버젼 바이너리가 올리간 사이트의 소스와 원본소스를 비교) 2) VAC C 컴파일러는 enum { AA,BB, CC,} 와 같은 코드를 용납하지 않아요~ 마지막에 여유로 쉼표를 찍지 말아주세요~ |
KOEI
2007/03/26 02:17
2007/03/26 02:17
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/489 |
|
|
|
|
|
libpcap linux에서 timeout 되게 패치하기
개발이야기 |
2007/03/25 20:39
|
|
|
근래에 계속 하고 있는 프로젝트에서 결국 tcpdump를 사용하게 되었는데, Failover 처리를 하다보니 캡쳐하다 특정 시간만큼 패킷이 없을 경우 timeout을 처리해야할 필요성이 생겼다. tcpdump 소스에서 관련 부분을 보면 - tcpdump.c -
885 *ebuf = '\0'; 886 pd = pcap_open_live(device, snaplen, !pflag, 1000, ebuf); 887 if (pd == NULL) 4번째 인자가 to_ms, timeout과 관련된 인자로 설정이 되어있어서, 잘 되는구나 하고 진행을 했으나, 리눅스에서는 timeout이 동작하지 않아서 소스를 들여다 봤다. pcap.c에서 보니 죄다 function pointer로 위임해놨군. 으움 실제 빌드를 돌려보고 실제로 컴파일되는 녀석을 찾아보니 이놈이다. pcap-linux.c 그중에 일부를 보면 - pcap-linux.c -
236 pcap_t * 237 pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, 238 char *ebuf) 239 { ... 267 /* Initialize some components of the pcap structure. */ 268 269 memset(handle, 0, sizeof(*handle)); 270 handle->snapshot = snaplen; 271 handle->md.timeout = to_ms; ... 492 /* Receive a single packet from the kernel */ 493 494 bp = handle->buffer + handle->offset; 495 do { 496 /* 497 * Has "pcap_breakloop()" been called? 498 */ 499 if (handle->break_loop) { 500 /* 501 * Yes - clear the flag that indicates that it 502 * has, and return -2 as an indication that we 503 * were told to break out of the loop. 504 */ 505 handle->break_loop = 0; 506 return -2; 507 } 508 fromlen = sizeof(from); 509 packet_len = recvfrom( 510 handle->fd, bp + offset, 511 handle->bufsize - offset, MSG_TRUNC, 512 (struct sockaddr *) &from, &fromlen); 513 } while (packet_len == -1 && errno == EINTR);
디버거를 돌릴 생각을 못하고 recvfrom 전후로 출력만을 넣어본 결과 blocking이 된다는 사실을 확인. 일단 늘 그렇듯이 당황. nonblocking으로 바꾸면.. 상상하기도 싫고..... 잠시 마음의 여유를 가지고 시간이 별로 없어서 다른 방법을 찾아보지 않고 소스를 고쳐보기로 했다. (얼마전에 했었던 C++로 작업된 프로젝트 삽질 이후에 또 삽질을.... 이번에는 C다. -ㅅ-)
이런 패턴을 어디서 많이 보던건데..... 아하! IRC서버소스에서 sleep이 없을 때 select를 쓴다.. 이건 상관없잖아!! ACE에서 recv에서 timeout을 지원하지 않을 때 어떻게 하더라. multiplexer의 timeout을 이용하면. That's right~ 그래서 붙이려다가 이왕이면 select말고 다른거 한번 써보자. 해서 epoll로 살포시 붙여봤다. 작업하면서 소스보기 번거로우니 쓰지 않는 파일이나 화면에 출력결과를 보여주는 루틴등은 싸악 날려주고 -ㅅ- 오옹 보다보니 리눅스에서 어떻게 캡쳐를 하면 되는지도 나온다. 여러 인터페이스(각각 fd로 나오니까)에서 캡쳐하는걸 하나의 epoll로 처리해도 괜찮겠다는 생각이 들기 시작.. 그러나 그런게 중요한건 아니니..
#include <sys/epoll.h>
.... 중략 ....
#define EPOLL_EVENTS_COUNT 16 static int epollfd = -1; static struct epoll_event ev, * epoll_events = NULL;
.... 중략 ....
pcap_t * pcap_open_live(const char *device, int snaplen, int promisc, int to_ms char *ebuf) { .... 중략 ....
handle->selectable_fd = handle->fd; do { if ( -1 == epollfd) epollfd = epoll_create(EPOLL_EVENTS); if ( -1 == epollfd ) break; if ( NULL == epoll_events ) epoll_events = (struct epoll_event *)malloc(sizeof(*epoll_events) * EPOLL_EVENTS); ev.events = EPOLLIN; ev.data.fd = handle->selectable_fd; if ( -1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, handle->selectable_fd, &ev) ) break;
handle->read_op = pcap_read_linux; handle->inject_op = pcap_inject_linux; handle->setfilter_op = pcap_setfilter_linux; handle->setdirection_op = pcap_setdirection_linux; handle->set_datalink_op = NULL; /* can't change data link type */ handle->getnonblock_op = pcap_getnonblock_fd; handle->setnonblock_op = pcap_setnonblock_fd; handle->stats_op = pcap_stats_linux; handle->close_op = pcap_close_linux;
return handle; } while(0); fprintf(stderr, "epoll setting error\n"); pcap_close_linux(handle); free(handle); return NULL; } .... static int pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata) { u_char *bp; int offset; #ifdef HAVE_PF_PACKET_SOCKETS struct sockaddr_ll from; struct sll_header *hdrp; #else struct sockaddr from; #endif socklen_t fromlen; int packet_len, caplen; struct pcap_pkthdr pcap_header;
int n ; /* epoll returned count */ .... 중략 ....
/* Receive a single packet from the kernel */
bp = handle->buffer + handle->offset; do { /* * Has "pcap_breakloop()" been called? */ if (handle->break_loop) { /* * Yes - clear the flag that indicates that it * has, and return -2 as an indication that we * were told to break out of the loop. */ handle->break_loop = 0; return -2; } fromlen = sizeof(from);
n = epoll_wait(epollfd, epoll_events, EPOLL_EVENTS, handle->md.timeout); if (0 >= n) { break ; } for ( int i = 0 ; i < n ; ++i ) { if ( epoll_events[i] == handle->selectable_fd ) {
packet_len = recvfrom( handle->fd, bp + offset, handle->bufsize - offset, MSG_TRUNC, (struct sockaddr *) &from, &fromlen); } } while (packet_len == -1 && errno == EINTR);
if ( n == 0 || ( n < 0 && errno == EINTR ) ) { /* callback function must check packet_header, buffer_pointer is NULL. if they are NULL, it is timeout */ callback(userdata, NULL, NULL); return 0; }
.... 중략 ....
static void pcap_close_linux( pcap_t *handle ) { struct pcap *p, *prevp; struct ifreq ifr;
.... 중략 ....
if (handle->md.device != NULL) free(handle->md.device); handle->md.device = NULL; if (handle->selectable_fd > 0 ) /* 여긴 대충 대충 -ㅅ- */ { close(epollfd); } pcap_close_common(handle); 요렇게 간단하게 몇 부분을 고치니 timeout을 callback에서 packet_header와 capture buffer가 NULL임을 확인하는 것으로 timeout 체킹이 가능해졌다. 다만 50라인 정도 작업하는데 3시간 정도를 쓰다니 -_+ 요튼 소기의 목적을 달성하고 timeout을 이용한 다른 코드를 tcpdump에 붙이기 성공. 작업하면서 뻘짓한 것은 tcpdump 소스를 보면서 print-*.c 얘네들을 지우면서 소스를 파악할 때까지는 분위기가 좋았다. libpcap 소스를 보면서 먼저 문서화된 것을 보지 않아서 인터페이스를 파악하는데 삽질한 것(Ruby/Pcap만으로 이미 난 대부분을 알고 있어라는 자만심에)에서 시간을 많이 뺏겼다. 언제나 그렇듯이 소스보다는 문서를, 문서보다는 예제를 먼저 보자라는 생각을 빼먹는다. 작업하면서 printf에서 사용하는 formatted string에서 잼있는 것을 발견. (나만 몰랐는지도)
sprintf(buffer, "%s%0*d", orig_name, max_chars, cnt);
%*d라고 적으면 두개의 인자를 받고 앞의 인자가 *로 자리수를 받고 뒤의 인자가 값이 된다.
여튼 즐거운 working sunday..... ㅠ.ㅠ
|
KOEI
2007/03/25 20:39
2007/03/25 20:39
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/488 |
|
|
|
|
|
근래에 하는 프로젝트를 통해 배운 것들 몇가지
개발이야기 |
2007/03/17 14:09
|
|
|
요사이 개발자로써의 목표는 "새로운 것을 접할 때 빠른 결과와 좋은 성능을 보장하는 개발하기"였다. 실제 여러가지 실제적인 상황을 보다보면 대부분 아는 것보다 모르는 것이 많은 것이 당연한데, 늘 그러한 일을 할 때면 밑바닥부터 모든 것을 만들겠다는 아집에 많은 시간을 쓰는 경우가 많았다. (알고보면 모든 것을 밑바닥부터 만들는 경우는 없는 것과 마찬가지이다.)
해야할 일은 TCP로 돌아다니는 패킷을 캡쳐 => 캡쳐된 패킷을 스트림으로 전환 => 특정 프로토콜에 맞게 파싱
장님 코끼리 만지기 식으로 도입한 프로세스 1. 고객에게 정보 수집 2. 먼저 빠르게 사용할 수 있는 라이브러리를 찾기.(libpcap) 3. lightweight framework language( Ruby, Python과 같은 유연하고 제네릭한 언어)에서 해당 라이브러리 찾기 (pcapy, Pcap/Ruby) 그중 Pcap/Ruby에서 아주 쓰기 편하고 이쁜 라이브러리를 기반으로 구현을 해서 나머지 기능을 모두 기 능 구현을 했었다. 4. 그 외에 세부적으로 필요한 기능상의 내용은 TCP가 어떻게 돌아가는지는 오픈소스 구현물 tcpflow, wireshare, 그리고 관련 자료 wikipedia, the TCP Guide를 참조해서 Ruby로 만든 프로그램에 적용하는 식으로 개발을 했었다.
3주 정도의 기간에 결과물을 대략 산출해내는데 성공했으나 - 아직 진행중 - , 몇 가지 문제점 및 개선방안을 발견할 수 있었다. 결국 실용주의 프로그래머에서 본 내용을 다시 한번 뼈저리게 느끼게 된다.
수행하면서 좋았던 점 1. 1주일 이터레이션 : 피곤하고 많은 시간을 뺏긴다고 생각했으나, 가야할 방향을 알려주는 나침반 역할을 수행했다. 2. Lightweight framework language 활용 : ruby,python과 같은 유연하고 쉬운 언어를 통한 접근은 새로운 기능을 사용하는데 있어서 지렛대 역할을 수행했다. 또한 어떤 부분이 성능이 필요한지, 아닌지에 따라서 사용 여부를 판단할 수 있었다.
개선해야할 점 및 반성점 1. 기존에 있던 구현물을 최대한 활용하기(Unix 철학처럼 simple is best를 최대한 활용하라.) 기능을 구현하는데 있어서 라이브러리보다 더 좋은 수단은 이미 구현된 프로그램이다. 그것을 최대한 재활용할 수 있는 방법을 먼저 강구했었으면 더 빠르게 그리고 안정적으로 구현이 가능했을 것이다. tcpdump를 이용해서 파일을 주기별로 만든 다음에 그걸 파싱하는 프로그램을 작성한다던지 하는 등의 개발방법론을 적용했다면 더 빠르게 개발이 가능했었을 것이다. 2. 고객 그리고 같이 일하는 사람들의 얘기를 좀 더 주의깊게 들으라. 해당 문제를 좀 더 쉽게 단순화시킬 가능성이 높아진다. 받은 데이터를 기존에 같이 일할 때처럼 csv파일과 같은 형태로 만들기 위해 모두 파싱을 하느라 3-4일 정도의 기간을 소요했는데, 오프셋으로도 처리가 가능했었다. 기존에 같이 일할 때 방법만을 생각하고 개발하느라 그러한 문제점이 발생했었는데 생각해보니 지나가는 말로 이 안건을 개발하면서 관련 얘기를 들었던거 같다. 3. 그중에서도 실시간성을 어떻게 감안하느냐에 따라서 concurrent 모델 혹은 배치작업(분리된 프로세스)로 처리하는 것이 월등히 좋은 성능을 발휘할 수 있다. 실시간의 DB 트랜젝션 그리고 실시간의 raw data를 파싱하는 작업등은 시간에 민감한 다른 작업에 지대한 영향을 줄 수 있다. 다량의 로그데이터를 처리하거나 실시간성이 중요한 패킷캡쳐등을 할 때 작업을 분리해 배치처리를 하는 방향으로 모델을 작성했었어도 나쁘지 않은거 같다.
프로세스 수정안 1. 고객에게 여러가지 제반사항에 관한 정보를 수집하기. 2. 같이 개발할 사람들과 협업할 부분에 대한 정보를 얻기 3. 모델을 쉽게 재구성하기 4. 해당 기능과 같은 혹은 비슷한 프로그램을 찾기 5. 실행해보기(기능, 성능체크) 6. 역할에 맞게 최소한의 수정 (라이센스 정책을 어기지 않게 모델을 구성하는데 참조) 7. 그 외의 부분은 상황에 따라 lightweight framework language와 기존의 언어로 빠르게 구현
|
KOEI
2007/03/17 14:09
2007/03/17 14:09
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/485 |
|
|
|
|
|
[짝프로그래밍] Rath 님과 MEConsole 한글화
개발이야기 |
2007/02/17 17:37
|
|
|
HanIRC의 #perky에서 유명하신 rath님과 했던 짝프로그래밍 이야기 - 아직 진행중 -
어느날 밤인가 sourceforge.net에서 윈도우용 오픈소스들을 둘러보다 발견한 MEConsole이라는 프로그램을 rath님이 관심을 보이면서 시작했던 일이다. 전부터 자바로 무지빠르게 어플을 작성하신다는 소문의 개발자라고 들었는데 , 정말 갱장한 분 :$ 짝프로그래밍을 할 사람이 없나 호시탐탐 기회를 노리는 K모군에게는 무엇보다 좋은 물고기. MEConsole은 ssh, telnet, cmd 콘솔창들을 탭으로 쓸 수 있고 창도 쪼갤 수 있는 MDI 어플리케이션인데 역시 한국에도 먼저 발견하신 분이 계셨다. 여튼 이 녀석은 화면 갱신이 느린 것을 제외하면 (Fixed Sys 폰트로 하면 머 으음 그럭저럭) 꽤 쓸만한데 콘솔을 광적으로 좋아하신다는 rath님 곧 문제점을 발견하셨는데, cmd창에 한글이 쳐지지 않는다는 것이었다. 두둥!! 사용기를 올리시고는 패치를 할까 하셨는데 이때를 놓치지 않고 덥썩을 시도 성공! 2월 3일 토요일날 건대에서 뵙기로 했으나, 감기로 앓아누워버린 KOEI군 덕분에 2월 10일날 첫 접선, 지가 약속을 정해놓고 깬게 죄송해서 점심을 가볍게 닭한마리 먹고 건대병원 스타벅스에서 환자들이 커피를 마셔대는 장소 사이에서 짝프로그래밍을 시작. 그러나 rath님의 PDA에서 잡히는 무선인터넷은 왜 노트북으로는 안 잡히는거야. 그냥 소스를 분석하기 시작. 초기목표는 다음과 같았다. * 다국어 지원 기능 (MEConsole의 개발자는 중국인인듯, 인코딩이 중국어랑 디폴트인코딩만 지원, CP949 그리고 UTF-8 추가부터) * cmd창에서 한글을 쓸 수 있게(ssh는 잘 된다!) - 컴파일 하니 클리어
무지막지하게 빠르게 보시는 rath님의 보조를 쫒아가지 못해 열심히 정리하면서 소스를 파악. 2시간정도 파악을 하고 나서 전체적인 구조를 파악해 내었는데 의외로 cmd와 ssh가 돌아가는 구조가 복잡해서 실제로 돌려보면서 버그를 잡아야한다고 결론을 내림
일단은 네트웍이 안 되는 이를 어쩐다. 근처에 피씨방으로 이동, 2자리를 놓고 하는데 MEConsole은 wxWidgets을 쓰기에 다운로드 받고 컴파일. 그런데 wxWidgets은 컴파일이 잘 되는데 MEConsole과 컴파일하는데 windows.h에서 winsock.h를 포함하는 것과 wx.h에서 winsock2.h를 포함하는게 에러가 났다. 예전에 게임회사에서 익숙하게 겪던 것이었는데 해결하기가 의외로 쉽지 않았고, 또 한가지 PC방은 집중을 하기 좋은 환경이 되지 못했다. 일단 2대의 피씨가 추가된게 의외로 작업을 분산시켰고, KOEI는 아직 감기가 다 낫지 않아서 담배연기에 헤롱헤롱대기 시작했다. 물론 짝프로그래밍을 2시간을 넘어 2시간 정도 더 진행한 것이 집중력 저하의 원인인지도 모르겠지만..
여튼 담번을 기약하고 (컴파일도 해오는 것을 기본) 헤어지고, 주중에 한번 더 하는거로 했으나 직장인인 관계로 주말에, 이번 주말은 설날인지라 담주 화요일날 오후 8시에 뵙기로 했다. 장소가 의외로 커다란 문제라서 알아보니 스터디모임용 공간(관련포스팅)이 건대에도 있더라. 이름이 CINO COFFEE 커뮤니티카페 요기를 주거점으로 한동안 이용할 듯 싶다. 그동안 성과가 안 나온 것도 있고, 게으름에 포스팅을 게을리하다가 오랜만에 휴일에 일단 포스팅. 곧 진행사항이 보여서 결과물을 내야!!!
반성했던 점은 처음 만나는것치고는 어색함이 덜 했으나(우리는 확실한 목적이 있었다.) 신나서 너무 오버한게 아닌가 하는 생각이랑 더 잘 짝프로그래밍을 할 수 있지 않았나 하는 생각이 든다. 지나치게 말이 많았던건 아닌지 혹은 정말 괴수앞에서 아는 척한 모양새는 아니었는지.. 소심해질만큼. - 하지만 무엇보다 좋았던 건 개인적으로는 몇 안되는 경험이지만 짝프로그래밍을 했던 경우중에 rath님과의 짝프로그래밍이 가장 잼있었다. 설날이 끝난 화요일이 무척이나 기대가 되는걸 보면 말이다. 좀 더 나은 성과와 재미를 만들어가길 기대하며..
아직 공개하긴 그렇지만 작업했던 결과는 http://trac.fiaa.net/cgi-bin/trac.cgi 에서 확인할 수 있다. |
KOEI
2007/02/17 17:37
2007/02/17 17:37
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/481 |
|
|
|
|
|
Active Directory로 계정 정보 옮기기
개발이야기 |
2007/01/10 07:17
|
|
|
- 발주처 : 한국xxx공제조합
- Objective : Oracle에 있는 계정 정보를 Active Directory로 동기화 하자.
- 작업대상은 300-400명
- 그리고 시간은 몇 분 정도까지 허용
- 추정시간 : 2-3일
- 작업시간 :
- 개발기간 2006.12.20 - 2006.12.23 (조사기간까지 포함하면 1주일 정도 소요),
- 배치기간 2006.12.26
- 작업인원 : Crazia, KOEI.
- 작업환경 : VB.NET, Windows 2003
- 반성 :
- 작업추정이 극도로 잘못. 지나치게 낙관적
- 짝프로그래밍의 높은 생산성을 체험
- 배치 및 인수인계 준비가 부족했음.(애드립으로 해결 -_-v)
- 새로운 영역에 대한 접근방법론을 제시
2006년 말에 했었던 결재관련시스템과 별도로 추가로 하게된 일. 최초에 주어진 미션과는 조금 다르게 되었지만(원래는 소스가 AS/400이었다.) 일을 시작할 때 추정과정에서 하루에서 이틀정도가 소요될 것으로 생각을 했으나, 그건 정말 말그대로 또 하나의 커다란 추정 실패였을뿐이다. (다시금 생각해봐도 2006년 거듭된 실패의 연속이었다. Crazia와의 짝프로그래밍이 성공적이지 않았다면 정말 끔찍한 연말이었을꺼다 -ㅅ-)
초기에 개발은 고객쪽의 요청(유지보수문제?)와 담당개발자(초기 KOEI 1人에서 Crazia 1人로 변경)의 언어선호로 C++로 개발을 하기로 했으나, 2인의 짝프로그래밍 체제로 가면서, agile한 개발방법론 주창을 한 K모군의 음모(그는 과연 agile의 A라도 제대로 알고 있었을까?) 로 VB.NET으로 변경. 추후에 VB.NET으로 개발을 하면 C++로 포팅을 하는 방향으로 가는 것으로 목표를 설정했다. 기존까지는 두 사람 다 Visual Basic이라고는 [Comparing Programming Languages in real life](언어를 여성에 빗대어 표현하고 있다.)과 비슷한 평가(She is little bitch)를 두 사람 다 내리고 있는 단계였다.(Basic의 평가 역시 크게 다르지 않다. 누구나 한번쯤은~) VB.NET을 초기개발언어(?)로 선정한 이유는 Active Directory를 제대로 파악하고 있는 사람이 없었고, 빠른 개발언어를 사용하고 싶었는데, VB.NET은 Microsoft에서 주도하는 언어이고, 이는 우리가 작업하고자 하는 영역에 많은 예제와 - 실제로 구글링시 많은 유용한 검색결과를 보여주었다. What are you expected? she is VB.- 쉬운 접근이 가능할거 같다는 기대치를 쉽게 채워주었기 때문이다. 또한 결재시스템 작업을 C#으로 하면서 .NET Framework에 대한 최소한의 이해도가 생겼고, 이는 VB.NET으로 작업하는데 있어서 문법적인 차이 이상은 새로운 것을 사용할 필요가 없었다. (최초 고려대상은 perl이었다. XML-RPC기반의 Active Directory Bridge 구현과 같은 아름다운 기사도 찾아볼 수 있었고, 어찌되었건 Active Directory라는 녀석이 결국 LDAP이란 말이지 라는 놀라운 사실로 알려주었다. 다만 현재 개발자들에게 보다 친숙하고 편리한 개발환경 구축이 편이해야했고-최소한 디버깅 환경은 익숙해야했다.- 결정적으로 설치와 예제코드의 라인수에서 밀렸다. Perl누나는 다음 기회로 ㅠ.ㅠ)
작업은 처음 낙관적인 추정이 나온대로 간단했다.
- 작업 환경 설정
- 오라클에서 계정정보를 가져오기
- Active Directory에 해당 유저 정보 삽입 및 갱신
최초의 작업장벽은 테스트 환경 설치였고, 이는 Active Directory 및 Oracle 설치였는데, Oracle을 윈도우에 설치해본 경험이 전무해서 의외로 시간을 2일이라는 시간을 잡아먹었다. 할튼 설치를 하고 오라클에서 계정 정보를 가져오는 코드를 작성하는데도 하루의 시간이 걸렸다. ODP.NET을 활용했고 일반적인 예제와 토시 하나 다르지 않다 -ㅅ-.
' Set Connection String Dim ConnectionString As String = "User Id=[userid];Password=[passwd];Data Source=[LISTENER];"
Dim con As OracleConnection = New OracleConnection() ' Create the OracleCommand object Dim cmd As OracleCommand = New OracleCommand()
cmd.Connection = con cmd.CommandType = CommandType.Text
Try ' Open the connection con.Open() Console.WriteLine("Connection to Oracle database established successfully !") Console.WriteLine(" ")
Dim cmdQuery As String = _ "SELECT 아이디 AS id, 표시이름 AS displayname, 부서 as dept, 상위부서 AS ascent_dept, 암호 AS userpasswd, 패스워드만료기한 as expired" & _ " FROM 테이블들" & _ " WHERE 조건"
cmd.CommandText = cmdQuery
' Execute command, create OracleDataReader object Dim reader As OracleDataReader = cmd.ExecuteReader()
Dim ADAcessor As ActiveDirectoryAddContacts.Class1 = New ActiveDirectoryAddContacts.Class1()
Dim id As String Dim passwd As String = "" Dim displayname As String Dim description As String Dim expirePasswd As String
While (reader.Read()) id = reader("id") displayname = reader("displayname") description = displayname & "/" & reader("dept") & "/" & reader("ascent_dept") expirePasswd = reader("expired") If (False = reader.IsDBNull(4)) Then passwd = reader("userpasswd") Else Continue While End If
Try ' Active Directory Processing ADAcessor.UpdateUser(id, displayname, passwd, description, expirePasswd) Catch ex As Exception Console.WriteLine(ex.ToString()) End Try End While Catch ex As Exception Console.WriteLine(ex.Message) End Try
여튼 저튼 대부분의 시간은 작업환경을 설정하고, 새로운 문법에 살포시 발 담가보고(차라리 익숙한 C#으로 할 걸이라는 후회를 하기 시작했으나, C#만은 제발이라는 외부의 요청이 있었다. 포팅할껀데 하고 -ㅅ- 생각을 이때 했다면) 그리고 알려준 테이블 스키마와 예제데이터에서 관련 정보를 가져오는데 시간이 걸렸다.
Active Directory에 새로운 사용자를 추가하거나, 기존의 사용자 정보를 갱신하는 기본 코드는 다음과 같다.
' 현재 시스템의 Active Directory Path가져오기 Dim DSESearcher As System.DirectoryServices.DirectorySearcher = New System.DirectoryServices.DirectorySearcher() Dim ADSPath As String = DSESearcher.SearchRoot.Path ADSPath = ADSPath.Insert(7, "CN=Users,")
Dim oRoot As DirectoryEntry = New DirectoryEntry(ADSPath, 어드민아이디, 어드민패스워드, AuthenticationTypes.Secure)
Try 'Make sure this user is not already there. Dim oNewUser As DirectoryEntry = oRoot.Children.Find("sAMAccountName=유저아이디", "user") With oNewUser .Properties("displayName").Value = displayname .Properties("givenName").Value = displayname .Properties("description").Value = description .Invoke("SetPassword", passwd) .CommitChanges() End With Return Catch oCOMException As System.Runtime.InteropServices.COMException 'User is not in AD. So, add oNewUser = oRoot.Children.Add(string("cn={0}", displayname), "user") With oNewUser .Properties("sAMAccountName").Add(id) ' Add/Specify more properties, if needed .Properties("displayName").Add(displayname) ' Add/Specify more properties, if needed .Properties("givenName").Add(displayname) .Properties("description").Add(description) .Properties("name").Add(displayname) .Properties("userPrincipalName").Add(id & "@" & DomainName)
.Properties("UserAccountControl").Value = .Properties("UserAccountControl").Value - 2 .Invoke("SetPassword", passwd) ' Now that the user is created, set the password
.CommitChanges() End With End Try
유저를 찾아보고 있으면 변경을 하고, 없으면 예외를 발생 시키니 그 예외처리로 새로운 유저를 추가하면 된다는 간단한 코드였다. 물론 Active Directory나 LDAP(Crazia은 LDAP에 대한 경험이 있으셨다고 하나 Active Directory에 대한 부분은 상황이 같았다.)에 대한 정보가 부족했기 때문에, User라는 DirectoryEntry가 가지는 속성값을 찍어보기 위한 디버깅 코드도 작성을 했어야했고(초기에는 MSDN을 찾았으나 제대로 된 정보를 찾지 못했다. 검색능력 부족.) cn과 sAMAccountName이 실제로 Active Directory에서 어떻게 표현이 되는지 하는 것을 찾고, 꽤 많은 시간을 보냈다. 가장 크게 고생했던 부분은, MSDN에서 정보를 찾고, 예제코드를 보아도 저 코드가 동작하지 않는 것이었다. 왜 그럴까? 우리가 작성한 코드에 문제가 있지 않을까 하고 디버깅과 코드 리뷰, 예제코드와 비교 등 끝임없이 삽질을 계속했다. 첫번째줄은 유저의 초기 설정값으로 다음번에 들어올 때 패스워드를 바꿔야함을 끄기위한 설정이었다. 두번째 줄은 말그대로 패스워드를 바꾸는 것이고.. 먼가 문제가 있는거 같아 단계별로 CommitChanges()를 호출해보기도 하고.. 결국 문제의 원인은 계정생성 도중에 패스워드를 바꾸려고 했다는 것이었다.
With oNewUser .Properties("sAMAccountName").Add(id) .Properties("displayName").Add(displayname) .Properties("givenName").Add(displayname) .Properties("description").Add(description) .Properties("name").Add(displayname) .Properties("userPrincipalName").Add(id & "@" & DomainName) .CommitChanges() End With
'now user is created, set the password oNewUser = Nothing
oNewUser = oRoot.Children.Find(CNName, "user") With oNewUser .Properties("UserAccountControl").Value = .Properties("UserAccountControl").Value - 2 .Invoke("SetPassword", passwd) .CommitChanges() ' Commit the changes again End With 일단 패스워드 설정과 관련된 부분을 빼고 저장을 하고, 해당 유저를 찾아오는 코드를 추가하는 것을 추가하는 것(로그인을 하는 것과 같았다.)이 해결방법이 되겠다. 그 외에도 현재 Active Directory의 정책에서 최소패스워드길이라든지, 패스워드의 복잡도 설정으로 패스워드가 설정되지 않는 문제도 있었다. 겉으로 보기에는 아니 왜 됐다 안 됐다해? 여기서 짝프로그래밍의 장점이 나오는데, 만약 혼자 이 작업을 했다면 찾는 도중이라든지 예외를 보이는 도중에 분명히 잘 안되네 하고 IDLE 타임이 생겼을텐데 우리는 서로의 눈치를 보면서 끝없이 돌진 돌진 돌진했다. 그리고 그 과정은 힘들기는 했으나 토론을 하면서 진행하는 과정은 꽤 즐겁기도했다.
위 코드까지 작성을 하고 일단 작업을 마무리하고, 코드 정리를 하고 일을 끝냈다. 패스워드만료기한 설정은, Crazia 혼자 추후에 작업을 하는 것으로 하고 크리스마스 연휴를 조촐하게 보냈다. (암호만료기한 설정은 Crazia가 간단하게 해결을 했고, 추후에 고객이 필요없다는 얘기에 빠지게 되었다.) 여기까지 총 작업시간은 6시간이 걸렸다. 꽤 많이 헤매고 찾아본 것에 비해서는 빠르게 마무리가 되었다. (단연코 말하건데 위 작업을 혼자 했다면 찾아보고, 시도해보고 하면서 중간에 IDLE 타임 및 딴짓을 하면서 워킹데이로 5일 즉 1주일는 걸렸을꺼 같다. 그래 별로 실력없는 개발자 맞다.
크리스마스 연휴 이후에 실제 배치는 KOEI 담당이었고, 그날 겪었던 문제는 다음과 같다.
- 사용자를 최초에 찾아오는 코드에서 sAMAccountName로는 사용자를 찾을 수 없다.
=> 개발하면서 cn(사용자관리화면에 표시되는 이름)과 sAMAccountName(실제로그인아이디)의 차이를 알아내고 이를 코드에 반영하면서 생긴 오류인듯 하고, 해당코드를 cn으로 고쳐서 해결을 봤다. ("sAMAccountName=유저아이디" => "cn=출력이름")
- cn으로 고친다음에는 동명이인의 경우 추가되지 않는 문제가 생겼다.
=> 결국 표시되는 이름을 사용자 아이디로 수정했고, 고객의 요청사항에 따라 description에 사용자 이름을 기술하는 것으로 해결했다.
- 한국xxx공제조합에서 담당자에게 유지보수를 위한 코드 설명, 개발툴 사용법을 인수인계해야했는데 Visual Studio 8.0을 라이센스 문제로 설치할 수 없었다.
=> .NET Framework SDK를 깔고 컴파일러 사용법을 알아보고 Editor등을 설정하고 하다가, SharpDevelop이라는 OpenSource 개발툴을 찾아서 해당 개발툴을 컴파일이 가능할 정도까지 같이 익히고 소스코드 리뷰를 했다.
해당 모든 일을 마무리하고 반성한 점은 지나치게 낙관적으로 일정추정을 했다. 만약 AS/400을 그대로 썼다고 했다면 이미 알고 있는 Oracle보다 더 많은 시간을 소요했고 개발환경 설정도 쉽지 않았을 것이다. 실제로 알고 있는 정보는 적어서, 예정처럼 혼자 작업을 했다면 꽤 긴 시간을 작업해야했을 것이다. VB.NET과 같이 쉬운 언어로 작업을 하면서도 의외로 많은 문제에 부딪쳤다. 개발언어 선택은 일단은 VB.NET으로 작업, 추후에 C++로 포팅하는 방법론은 성공적이었다. Active Directory를 제대로 알지도 못하는 상황에서 만약 C++로 초기개발부터 시작을 했다면 포인터를 잘못 사용했거나, COM으로 작성된 라이브러리를 제대로 사용을 못했는지 등의 사소한 실수와, Active Directory를 제대로 몰라서 문제가 발생을 했는지를 제대로 판단하기 어려웠을 것이다. 작성한 코드의 길이가 길어질수록 문제점을 찾는 일은 더 어려워진다는 점도 무시할 수 없다. VB로는 약 300-400라인의 소스코드가 나왔는데 이를 C++로 작업했을 때는 어느정도의 양이 나올지 대충 생각을 해봐도 꽤나 아득하다. (.NET 기반의 언어들은 COM을 사용하는데 아주 편리해서 그것만으로도 코드의 길이 차이가 꽤 될듯 하다.) 작업후 실제로 포팅은 수행하지 않았는데, 작업결과가 특별한 문제점이 보이지 않았고, 고객에게 먼저 실행결과와 소스를 리뷰한 결과 소스가 그리 길지 않다는 점에서 유지보수에 호의를 보였다.(물론 고객을 잘 만난거 같기도 하다. 꽤 많은 시행착오가 있었는데 너그러이 그리고 잼있게 같이 작업을 할 수 있었으니 -ㅅ-) 가장 큰 성과는 짝프로그래밍의 생산성 및 성과를 확인할 수 있었던 것이다. [대안언어축제]에서 겪었던 경험이 실제 업무에서도 충분히 효과적임을 확인할 수 있었고, 이는 Crazia와의 협업에서도 잘 나타났다. 두 사람 모두 짝프로그래밍에 대해서 긍정적인 반응을 보였고 꽤나 잼있었다. 기존에도 Crazia와 몇몇 서버 코드를 작성시에 짝프로그래밍을 했었으나, 대부분의 작업은 1人이 작업하는 정보를 다른 1人이 작성한 서버 프로그램을 수정하는데 주로 사용되었다. 새로운 부분에 대해서도 어찌보면 더 폭발(!)적인 생산성을 보일 수 있다는 것은 고무적인 일이다. 다만 반성을 하자면 짝프로그래밍시에 너무 키보드를 독점하고 있지 않았나 하는 생각이 든다. 밸런스가 지나치게 무너졌던게 아닌가 (여전히 나는 아집과 고집이 쎄다.) 하는 생각이 드는데 그럼에도 불구하고 좋은 결과를 보인 것은 Crazia의 배려로 이루어졌던게 아닌가 하는 생각이 든다. - 이같은 점을 고치기 위해서 개인적인 노력 이외에도 회사에 짝프로그래밍용 타이머를 도입했다. - Active Directory와 LDAP에 대해 습득한 지식이 기간이 짧아서 그리 체계적이지 않다. 전반적인 LDAP에 대한 개념도 그러하지만, Active Directory만 해도 .NET Framework에서 제공하는 API로는 Password의 값을 쓸 수만 있는데 이것을 읽어낼 방법 역시 추후에 비슷한 일을 맡게 되면 필요할지 모르는 일이다. (상식적으로는 왠지 가능할것 같지는 않기도 하다.) 상대적으로 거의 일어날리 없겠지만 M$솔루션의 사용자계정정보를 타기종 시스템으로 이전할 일이 왜 없겠는가? (실제로 IBM AS/400 관련 조사에서는 해당 솔루션을 판매하고 있었다.)
사족) 앞으로 개발을 하면서 겪었던 경험을 이 카테고리에 정리해 나갈 생각입니다. 가장 먼저 적는 글이라 근래에 했던 일중에 잼있었던 프로젝트를 다뤘습니다. 자주는 아니더라도 자신이 해왔던 일들에 대한 발자취를 남기는 공간으로 활용하려고 합니다. 그리고 이글에서는 회사 동료분들의 실명을 직접 쓰기는 어려우니 닉을 쓰기로 했습니다. KOEI는 아실테구 이글에서 같이 작업을 한 Crazia는 제가 다니는 회사 사장님이십니다. CEO이시기 이전에 풍부한 개발경험을 가진 아주 Smart 한 개발자이시기도 합니다. 실제 개발에서 시도 방법이라든가 문제를 바라보는 방법, 그리고 개발스킬(게으른 개발방법론도 포함. 나중에 일례를 소개하겠음) 많은 것의 배움의 대상이기도 하지요~ |
KOEI
2007/01/10 07:17
2007/01/10 07:17
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/474 |
Tracked from 해적의 쉼터 2007/01/17 15:11 x
제목 : Active Directory 에 사용자 정보 넣기
누리인포스 라는 회사에서 아는 사이니 도와달라고 하는 일을 억지로 맡아서 하게 됐습니다. 그래서 아는 사이가 더 무섭다는 말을 하는 겁니다. 좋게 좋게 대해주면 사람을 무시하게 되는건가요. 하지만 그건 있습니다. 너무 열받아 할 필요는 없다는거지요 , 그 사람은 저에게 빚을 지고 있는것이니까요 일 자체의 컨셉은 간단했습니다. 초기 목적은 AS400 의 사용자 정보를 가져와서 Active Directory 에 그 내용을 입력하는것이였습니다. 그게 AS.. |
|
|
|
|
|
2006 대안언어축제 참가 후기
개발이야기 |
2006/09/04 00:40
|
|
|
9.1 - 9.3 일까지 2006 대안언어축제 다녀왔습니다.
요사이 개발자로써 삶에 조금 지쳐있었는데..
새로운 곳에서 많은 자극을 받고 돌아왔습니다.
학부때 Programming Language 시간에 Lisp, Prologue등의 언어를 배우면서
자신의 흥미와 상관없이 언어를 배울 때와 새삼 다른 느낌 OOP, Functional 요 두가지가 2006 대안언어의 테마.
특히 functional language들이 무척이나 적응하기 힘들었지만
신선한 느낌과 즐거움을 가져다준 것으로 기억합니다.
특히 Haskell이 즐거웠습니다. Python, Ruby 비슷한 느낌의 두 언어를 접하면서
빠른 개발에 대해서 생각을 품을 수 있었고
Io, Smalltalk같은 순수 OOP (Io가 순수 OOP인지는 아직 모르겠지만..)들에서
이쁘다(아니 객체 + 메시지라는 방식에)라는 생각을 할 수 있습니다. 행사를 진행하면서 언어교환, BOF, OST등 다른 이들과 대화와 상호작용을 많이 할 기회가 있었는데
생각만큼 얼굴에 철판이 덜 깔려서 아쉬움이 남고, 거의 끝날때가 되어서야 열린 마음을 가지고 대화를 할 수 있었네요.
내년에 또 참가를 하게 되면 시작부터 좀 더 적극적이고 열린 마음을 가지기. 내년에까지 목표? 잠을 좀 적게 자긴 했지만, 머리 쓰는 시간이 길어지니까 몸이 무척이나 힘들었었다. 2-3일정도 전력투구를 해도 멀쩡한 건강한 개발자가 되는 것이 목표! |
KOEI
2006/09/04 00:40
2006/09/04 00:40
|
|
| 이 글의 관련글(트랙백) 주소 :: http://koei.fiaa.net/trackback/449 |
|
|
|
|
«
2010/07
»
| 일 |
월 |
화 |
수 |
목 |
금 |
토 |
| |
|
|
|
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 |
|
|
Total : 24890
Today : 5
Yesterday : 6 |
|
|