Categories
Life

착한 사람이 조직을 망치는 방법

착한 사람들은 시스템의 문제나 조직의 문제를 보고, 그것을 지적하고 프로세스를 개선하는 길을 선택하는 대신, 책임감 중독에 빠져 스스로를 희생하여 조직의 문제를 자신이 커버해야 된다고 생각한 뒤, 이 생각을 행동으로 옮긴다.

초반에는 스스로가 많은 일들을 처리하고 있기 때문에 가치있는 존재가 되었다는 기분에 우쭐해 하기도 하고, 기존 문제점을 아는 사람들로부터 중요한 사람으로 평가되기도 한다.

이들은 머지 않아 곧 지치게 되고, 시스템이 가지고 있던 많은 문제점들이 다시 드러나게 된다. 하지만 때는 이미 늦었다. 왜냐하면 이들이 자꾸 문제점들을 숨겼기 때문에 (좋게 말하면,  스스로 처리해버렸기 때문에) 누구도 프로세스에 문제가 있다는 생각을 할 수 없게 만들었기 때문이다. 시간이 흘러감에 따라 일의 복잡도가 늘어나는 것이 보통이기 때문에, 이들이 업무에 착수할 때보다 문제점들은 더욱 더 커져있게 마련이다. 결국 또 한 명의 착한 사람이 필요하게 되고, 악순환은 깊어지게 된다.

물론 나는 이런 사람을 착한 사람이라고 생각하지 않는다. 이기적인 사람일 뿐이다. 혹은 현재의 후진 시스템을 개선하는 작업에 두려움을 느끼고 한 발 뒤로 물러선 회피성 성격의 사람일 뿐이다.

문제점들은 공유되고 까발려져야만 한다. 임원들은 이런 사람들의 사탕발림에 넘어가지 않도록 각별히 신경을 써야할 것이다.

Categories
Daily Development

Google이 발표한 새로운 시스템 프로그래밍 언어 Go 써보기

구글이 새로운 시스템 프로그래밍 언어인 Go를 발표했습니다.

이것이 무엇인가.. 에 대한 글은 인터넷에 마구마구 퍼져있으니 찾아보시면 되고요, 어제 Google Go를 보고 제가 미투데이에 남겼던 기록들은 여기에서 볼 수 있습니다.

안타깝게도 release revision의 json 모듈에 버그가 있어서 2시간정도 삽질을 했습니다. 그것이 버그인지 감별하는데 대부분의 시간을 보냈고, 실제로 버그를 고치는데는 30분 정도가 걸린 듯 하네요.

$GOROOT/src/pkg/json/parse.go 에서 유니코드 문자열 처리를 위해 \u를 만났을때 다음 4바이트를 읽어 int로 변환하는 _UnHex 펑션에서 start, end offset을 받게 해놓고, caller는 start, length를 불러서 생긴 어처구니 없는 버그였습니다. 영광스럽게 패치를 보내려고 하니.. current revision에는 이미 고쳐있지 뭐에요. 별 수 있나요. 울었습니다.

문법에 익숙해지는데 꽤 시간이 걸렸지만, 깔끔합니다.

시험삼아 미투데이에서 내게 달린 최근 댓글을 확인하는 콘솔 프로그램을 만들어보며 학습을 했는데, make 파일과 me2comment.go 파일을 한 번 둘러보시지요.

* Makefile

1 include $(GOROOT)/src/Make.$(GOARCH)
2
3 TARG=me2comment
4 GOFILES=\
5     me2comment.go\
6
7 include $(GOROOT)/src/Make.cmd

* me2comment.go

 1 package main
 2
 3 import (
 4     "io";
 5     "http";
 6     "json";
 7     "fmt";
 8     "regexp";
 9     "flag";
10 )
11
12
13 func filter_html(str string) string {
14     remove_anchor := regexp.MustCompile("<[^>]+>");
15     var s string = remove_anchor.ReplaceAllString(str, "");
16     s = regexp.MustCompile("&lt;").ReplaceAllString(s, "<");
17     s = regexp.MustCompile("&gt;").ReplaceAllString(s, ">");
18     return s;
19 }
20
21 func main() {
22     var username string;
23     flag.StringVar(&username, "username", "rath", "Your ID of the me2DAY.");
24     flag.Parse();
25
26     url := fmt.Sprintf("http://me2day.net/api/track_comments/%s.json", username);
27     r, _, err := http.Get(url);
28     if err!=nil {
29         fmt.Printf("Error: %v\n", err);
30         return;
31     }
32
33     buf, err := io.ReadAll(r.Body);
34     ret, ok, errtok := json.StringToJson(string(buf));  
35     if !ok {
36         fmt.Printf("Error: %v\n", errtok);
37     } else {
38         to_me := ret.Get("comment_to_mes");
39         var last_post string = "";
40         for i:=0; i<to_me.Len(); i++ {
41             e := to_me.Elem(i);
42
43             nickname := json.Walk(e, "comment/author/nickname");
44             body := filter_html(json.Walk(e, "comment/body").String());
45             post_body := filter_html(json.Walk(e, "post/body").String());
46
47             if post_body!=last_post {
48                 var to_print string = post_body;
49                 if len(post_body)>50 {
50                     to_print = post_body[0:50]
51                 }
52                 fmt.Printf("* %s\n", to_print);
53             }
54             fmt.Printf("   ㄴ%s: %s\n", nickname, body);
55             last_post = post_body;
56         }
57     }
58
59     r.Body.Close();
60 }

실행시키면 ..


Jang-Ho-Hwangs-iMac:tmp rath$ ./me2comment
* 그녀의 요리 실력은 나날이 업그레이?
   ㄴ2Z[이지]: 영국이라는 게 믿기지 않아요! @.@
   ㄴJB♥: 와웅 보쌈코기 같이생겼어용ㅋㅋ뭔가맛나보이는 덮밥입니다ㅋㅋ
   ㄴ바람달: 하앍
   ㄴK-Dog: 크아!!
   ㄴtomato: 소스는 없이 먹는건가요?
* $GOROOT/src/pkg/json/parse.go 에서 \u를 만났?
   ㄴ솥아: 난 왜 최신 언어들에는 관심이 없는걸까 -.-  [글보러가기]
* 그녀의 요리 실력은 나날이 업그레이?
   ㄴ_wonhui: 맛있어 보입니다 -ㅁ-
* $GOROOT/src/pkg/json/parse.go 에서 \u를 만났?
   ㄴK-Dog: 꺅 더 좋은 세상을 위해 애쓰시는군요
   ㄴ다즐링: 이 님하 고고씽 삼매경.. ㅠㅠ 이러지마셈 흑흑..
* 버그 찾았다 >_< 2글자만 더 입력하면 ?
   ㄴgyedo: 역시 고수 ^^b
* $GOROOT/src/pkg/json/parse.go 에서 \u를 만났?
   ㄴgyedo: 보통 버그들이 ^^ 어처구니 없죠, 근데 current에는 고쳤다니 기특한걸요
* 밤에 집중이 잘 되는 나. 런던은 요새
   ㄴ늘보: 헉 ㅋㅋ 전 생산성이 떨어져요. 해가 지고나면 졸려서 ㅋㅋ
   ㄴ루치아: 컨디션 안 좋으면 쉬면서 해야죠 ㅎㅎ

이렇게 최근 내게 달린 댓글을 출력해주는 간단한 프로그램입니다.

go가 의존성을 모두 날려버리고 하나의 binary로 만들어주는 기특한 녀석이기 때문에, 이런 간단한 프로그램의 바이너리가 1.2MB나 된답니다. (darwin/amd64 빌드임)

만만한게 미투데이라 ^_^ 시스템 프로그래밍과 전혀 무관한 미투데이 클라이언트를 만들어보았는데요, 다음에 기회가 되면 go의 자랑 기능인 동시성 지원부분을 써보고 싶네요.

Categories
Daily Life Personal productivity

멀티태스킹이 어떻게 당신을 위험에 빠트리는가

나는 멀티 태스킹을 좋아한다. 왜 좋아하는지는 명확히 기술하기는 어렵다.

아무튼 자원이 낭비되고 있지 않다는 사실에서 오는 어떤 안도감을 좇는 것이 분명하다. 이것은 효율성을 좇는 것과는 또 다른 이야기이다.

큰 파일을 다운로드 받으려면 오랜 시간이 걸린다. 그런데 신기하게도, 큰 파일을 다운로드 받고 있다는 점을 내가 인식하고 있으면, 일에 잘 집중이 안된다. 다운로드 하는 작업이 날 비트적으로 정신적으로 직접적인 방해를 주는 것이 아니라, 해야 할 일에 내가 잘 집중하지 않는 것이다.

그래서 생각했다.

나는 자원이 낭비되지 않고 있다는 사실을 알면, 즉 직접적으로 내 정신력을 소비하지 않더라도 효과적이라고 생각하는 무엇이 진행되고 있다는 점을 인식하면, 더이상 열심히 일해야 할 필요가 없다고 느껴서 자연적으로 게을러지는 게 아닐까?

예를 들어 비지니스 관계에서, 이메일로 의사소통을 하고 있다고 치자. 나는 상대방으로부터 메일을 받았고, 적절한 피드백을 담아 회신 메일을 작성한다. 그리고 회신 메일 마지막에는 상대방에게 또 다른 피드백을 요구했다. 그리고 이 메일을 발송하고 나면 상대방으로부터 회신 메일이 도착하기전까지 나는 잠시 자유로워졌다는 착각에 빠지고는 한다. 상대방이 메일 회신을 위해 일하고 있다는 것을 추측했기 때문이다. 이메일을 통한 의사소통 섹션에서는 자유로워졌을지 몰라도, 다른 일들이 당신을 기다리고 있다는 사실은 조금도 변하지 않았다.

요즘 나는 페이스북 게임을 몇 개 하고 있다. 그 중 중독성이 가장 심한 것은 얼마전 Facebook Connect 버전도 출시한 Bejeweled Blitz와 Rockyou가 만든 Zoo World 이다. 비주얼드 블리츠는 게임을 하지 않는 동안에는 결코 날 괴롭히지 않는다. 대신 게임을 하는 그 1분동안 엄청난 집중력을 필요로 한다. 반면, Zoo World는 많은 집중력을 필요로 하지 않는다. 하지만, 계속 띄워놓고, 관심을 가져줄 것을 요구한다. 지금 Zoo World 레벨이 20인데, 중간 사이즈의 동물 4마리를 Breed 해야하는 미션이 활성화 되어있다. 동물 한마리를 새끼치려면 동물에 따라 다르지만 현실세계의 물리적인 시간 24시간 또는 72시간을 필요로 한다. 그 시간동안에는 게임에 접속하지 않아도 된다.

이것은 큰 파일을 다운로드 받는 것과 동일한 점이 있다. 파일을 다운로드 받겠다는 의사표시는 내가 주도적으로 한 것이며, 그 이후에는 나의 노력 여하와 무관하게 몇시간을 기다려야 한다는 것이다.  그럼 그 몇시간동안 나는 심리적으로 자유로워진다. 나의 주도적인 지시에 기반하여 어떤 일들이 열심히 벌어지고 있기 때문이다.

그래서 나는 뭔가 바쁘게 계속 하고 있다는 기분을 느낀다. 이것을 게임에 대입해서 문제지 -_- 실제 업무 환경에서도 크게 다르지 않기 때문에 이해를 돕기 위해 게임을 예로 들었다. 계속 바쁘게 뭔가를 하고 있다는 기분이 들면, 그것들이 내 성취욕의 많은 부분을 채워주었고 책임감도 덜어주었으므로, 다른 일에 몰두해야할 심리적 동기를 빼앗는 결과를 초래한다.

그래서 나는 엄청나게 바빠야 될랑말랑한 와중에도, 이렇게 딩가 딩가 시간을 보내고 있는 것이다.

이 함정에 빠지지 않으려면, 정신줄을 꽉 붙들어 메고 끊임없이 사고해야 한다. 일시적인 해결책으로 다운로드를 중단하고, 게임을 하지 않기로 선언할 수도 있다. 하지만 살다보면 이런 일들은 부지기수로 일어나게 마련이다. 지금 발등에 떨어진 불을 끄기 위해 이 일들을 없애버려본들 수주내로 이런 일들은 다시 생긴다. 도망치지 말고 싸워이겨내야 한다.

나폴레옹은 그가 신봉하는 전쟁의 원칙이 무엇이냐는 질문에 대해 자신은 어떤 원칙도 신봉하지 않는다고 답했다. 주어진 상황에 대해 매번 끊임없이 사고하고 무엇이 최적일지 생각하는 자세를 유지하는 것만이 이 올바른 길이다.

자, 달리자.

Categories
Development

Acer의 Android Netbook이 도착했습니다.

Acer가 만든 10.1인치 안드로이드 넷북을 구입하였습니다.

Acer가 빌드한 안드로이드라 조금 커스터마이징이 되어있습니다. 박스를 뜯고 바로 전원을 넣으면 안드로이드가 뜨지 않고 윈도우즈 XP가 뜹니다. 여기서 Acer가 만든 Android Manager 프로그램을 띄워서 업데이트를 하는 등.. 의 작업을 하고 난 뒤에야 안드로이드로 부팅할 수 있습니다.

그럼 부팅! Print Screen 버튼을 눌러 찍은 스크린샷들을 보시지요. (스크린샷 이미지를 클릭하면 원본 크기의 이미지를 볼 수 있습니다)

빰빰. 1024×600의 안드로이드가 부팅되었습니다. 버전을 확인하기 위해 정보보는 화면을 찾아보았으나 Acer의 Android 빌드 넘버만 나오고 SDK 버전 몇이 탑재되어있는지 확인할 수가 없었습니다.

그래서 android.os.Builder.VERSION 클래스를 이용해 버전을 확인해보니, Android SDK 1.5를 지원하고 있네요.

다음에는 브라우저를 띄워 페이스북 게임을 실행해보았습니다. 안타깝게도 플래시 플레이어가 설치되어있지 않군요.

좌측 상단에 뭐라고 줄줄.. 나온 것은 You must download and install latest version of Adobe Flash Player 입니다. 안타깝네요. Acer 가 나쁜 놈이지요, 뭐.

다음엔 이메일 클라이언트를 띄워 보았습니다. 검은 바탕에 회색 글씨가 아주 기품있습니다.

이번엔 구글톡을 실행해보지요.

네 그렇습니다. U.S. shipping only 제품이지만, 한글 표시에 문제가 없습니다.

물론 아래 화면처럼 한글 입력에도 문제가 없습니다.

단, IME 완성도가 좋지 않습니다. 종성이 없는 글자를 제대로 인식하지 못합니다. 예를 들어 ‘안녕하세요 여러분’ 을 입력하면 ‘안녕핫ㅔ요 열ㅓ분’ 이 됩니다. 그래서 종성이 없는 글자를 입력할 때에는 글자 완성 후 공백을 하나 입력하고 백스페이스를 누르는 노가다를 해줘야합니다. 좋지 않아요..

카메라 화질, 좋지 않습니다.

입 삐죽 나온 거 보이시죠? 화질 안좋다는 표정입니다.

안드로이드 플랫폼에는 별도의 Shutdown 기능이 존재하지 않습니다. 하지만 Acer의 Android Netbook은 윈도우XP와 안드로이드 둘 사이의 듀얼부트를 지원하기 때문에, 안드로이드에서 윈도우로 복귀하는 메뉴가 필요하지요. 화면 좌측 상단을 누르면 아래와 같은 다이얼로그가 표시됩니다.

마지막으로 지난 포스팅에서 만들었던 안드로이드 계산기를 실행시켜보았습니다. 가로가 너무 넓은 바람에.. 버튼의 너비가 길쭉 길쭉하여 영 보기 좋지 않습니다.

버튼이 너무 넓어요.. 세로 보기를 지원하지 않아서 멋진 스크린샷을 찍을 수 없었습니다.

결정적으로 Acer 안드로이드 넷북의 큰 단점이 있다면, 안드로이드 마켓을 쓸 수 없다는 것입니다. 메뉴얼을 봐도, 리뷰를 봐도, 플랫폼을 샅샅이 뒤져봐도 안드로이드 마켓을 이용할 수 있게 해주는 부분이 없습니다. 자신이 만든 프로그램을 설치하려면 USB로 업로드하고 디버깅하는 환경을 누릴 수도 없습니다. 그저 브라우저를 띄우고 apk 파일의 URL을 직접 입력해서 설치하는 수 밖에 없습니다. 참 불편하지요.

윈도우로 부팅했을 때 Android Platform Update 메뉴가 있긴 한데.. 패치 3개 정도밖에 없네요. 세로보기 지원을 바라지는 않지만, SD 카드에서 어플리케이션을 설치한다거나, 안드로이드 마켓을 이용할 수 있게 해준다거나 (Free 앱이라도), Android SDK 1.6 이나 2.0을 사용할 수 있게 얼른 업데이트를 제공했으면 합니다.

지금은 영.. 부족합니다.

Acer 안드로이드 넷북의 초간단 리뷰였습니다.

Categories
Daily Development

Android 스터디: 계산기 만들기

저번 포스트에 이어 오늘은 Android로 계산기를 만들어보도록 하겠습니다.

Eclipse 에 ADT 설치까지 마치셨나요? 그럼 헬로월드 실행도 성공하셨을테니, 바로 계산기를 만들어보도록 하겠습니다.

먼저 UI 설계를 하지요. balsamiq을 사용하여 간단히 그려보았습니다.

설계가 끝났습니다. 초기화 버튼이 없는게 이상하지요? 숙제로 남겨두겠습니다. 적절히 넣을 자리를 찾아 배치하고 미리 만들어진 메서드와 연결시키면 됩니다.

그러면 위처럼 화면을 구현할 수 있도록 Layout을 만들어야 합니다. Android 1.5 까지는 iPhone처럼 오직 320×480 하나만 신경써도 되서 편했을텐데요, Android 1.6 부터는 여러 해상도에 대한 지원이  들어갔습니다. support-screen 엘리먼트를 이용하여 지원하고자하는 화면크기들을 각각 지정할 수 있고  각 해상도 (미리 정의된 small, normal, large)에 맞는 XML UI를 만들 수 있습니다.

그러나 저는 그렇게 할 생각이 없습니다. 화면 해상도와 Portrait/Landscape 에 맞춰 적절히 늘어나는 UI 코드를 작성할 계획입니다. 화면 크기별로 UI를 새로 만든다니.. 생각만해도 끔찍하지 않습니까? 그래서 Common Layout Object를 사용할 것입니다.

일단 완성된 Eclipse ADT가 만들어준 HelloActivity를 가지고 바로 시작해보겠습니다.

잠깐. 새 플랫폼 공부를 시작하려면 어떻게 디버그 로그를 출력하는지 아는 것이 중요합니다. Logcat을 통해 디버그 로그를 출력하는 방법을 알고 넘어갑시다. android.util.Log.i(“my log”, “중얼중얼”) 하면 됩니다. android.util.Log 클래스엔 e(rror), w(arning), i(nfo), d(ebug), v(erbose) 메서드가 있습니다. 취향것 쓰시면 되고요, error나 warning의 경우 오류객체(Throwable)만 집어던져도 됩니다. Log 클래스를 쓰게되면 Android Debug Bridge로 해당 로그 메시지를 보내주므로 Eclipse 에서 로그메시지를 편하게 확인할 수 있습니다.

Log.d(“hehehe”, “onCreate is executed”);

를 onCreate 메서드에 적절히 넣으시고, 디버그를 시작하시면 Log 창에 뭔가 잔뜩 출력되는 것을 볼 수 있습니다. “hehehe”만 보시려면 + 버튼을 눌러 로그필터를 만드세요. by Log Tag 부분에 hehehe를 입력하시면 내가 찍은 로그만 따로 볼 수 있습니다.

Activity, 하나만 쓰겠습니다. 이번 목표는 동작하는 계산기를 만들어서 안드로이드 플랫폼에 자신감을 얻는 것이기 때문입니다. 완성된 소스코드는 github에 올려뒀습니다.

먼저, UI를 구성하기 위해 어떠한 컴포넌트를 쓸지 결정하겠습니다. 입력중인 숫자들과 결과들이 출력될 컴포넌트는 TextView를 쓰고, 각 연산자들과 숫자들은 Button을 씁니다. 위부터 아래로 출력하기 위해 LinearLayout을 쓰겠습니다.

LinearLayout view = new LinearLayout(this);
TextView activeNumber = new TextView(this);
activeNumber.setLayoutParams(new LinearLayout.LayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f));

여기까지 입력하고나니.. 버튼을 어떻게 추가해야할지 고민이 됩니다. LinearLayout을 사용하였으니 앞으로 추가하는 버튼들은 모두 세로로 표시될 것입니다. 이것은 원하던 결과가 아닙니다. 그래서 ButtonGroup 이란 클래스를 만들어 버튼들을 묶고 각 버튼들에 액션을 연결해두는 GridView를 하나 만들도록 하겠습니다.

public class ButtonGroup extends GridView

{

final int gravity = Gravity.CENTER;

final float textSize = 36.0f;

private Button[] nums = new Button[10];

private Map<Operator, Button> operatorButtons = new HashMap<Operator, Button>();

private ButtonActions action;

public ButtonGroup(Context context, ButtonActions action)

{

super(context);

this.action = action;

setNumColumns(4);

setHorizontalSpacing(1);

setVerticalSpacing(1);    

setStretchMode(GridView.STRETCH_COLUMN_WIDTH);

createButtons();

initLayout();

}

private void createButtons()

{

for(int i=0; i<nums.length; i++)

{

final int number = i;

nums[i] = new Button(getContext());

nums[i].setText(String.valueOf(number));

nums[i].setGravity(gravity);

nums[i].setTextSize(textSize);

nums[i].setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

action.processNumber(number);

}

});

}

for(final Operator op : Operator.values())

{     

Button button = new Button(getContext());

button.setText(op.text());

button.setGravity(gravity);

button.setTextSize(textSize);

button.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

action.processOperator(op);

}    

});    

operatorButtons.put(op, button);

}

}

private void initLayout()

{

// 7, 8, 9, /

// 4, 5, 6, *

// 1, 2, 3, –

// ., 0, =, +     

final Map<Operator, Button> ops = operatorButtons;

setAdapter( new BaseAdapter() {

private View[] buttons = {

nums[7], nums[8], nums[9], ops.get(Operator.DIVIDE),

nums[4], nums[5], nums[6], ops.get(Operator.MULTIPLY),

nums[1], nums[2], nums[3], ops.get(Operator.MINUS),

ops.get(Operator.DOT), nums[0], ops.get(Operator.RESULT), ops.get(Operator.PLUS)

};

public int getCount() {

return nums.length + ops.size();

}

public Object getItem(int position) {

return null;

}

public long getItemId(int position) {

return 0;

}

public View getView(int position, View convertView, ViewGroup parent) {

View view = buttons[position];

view.setLayoutParams(

new GridView.LayoutParams(parent.getWidth()/4, (parent.getHeight()-40)/4));

return view;

}

});    

}

}

GridView는 말그대로 그리드 형태로 컴포넌트를 배치시켜주는 공용 레이아웃 클래스입니다. 소스코드 하단의 BaseAdapter 구현부분이 핵심이지요. 총 배치될 컴포넌트의 개수(getCount), n번째에 있는 컴포넌트를 리턴하는 getView 메서드가 핵심입니다.

여기까지 했으면, 이제 이 ButtonGroup을 LinearLayout에 넣어보지요.

View buttons = new ButtonGroup(this, actions);

buttons.setLayoutParams(

new LinearLayout.LayoutParams(FILL_PARENT, FILL_PARENT, 1.0f));

view.addView(buttons);

ButtonGroup의 세로 크기는 화면에 꽉 차게 하고 싶으니 LayoutParams에 WRAP_CONTENT가 아닌 FILL_PARENT를 사용했습니다.

이제 각 버튼을 눌렀을 때의 처리를 만들어줘야 합니다. ButtonGroup 생성자에서 ButtonActions 객체를 받았는데요, ButtonActions의 소스코드는 안드로이드 플랫폼과 무관하게 계산기 로직이 담긴 부분이므로 별도의 설명을 하지 않겠습니다.

그리고 ButtonActions에서는 입력중인 숫자와 결과값이 출력될 TextView의 레퍼런스를 넘겨서 각 버튼을 눌렀을 때의 결과를 출력할 수 있도록 했습니다.

끝났네요. 계산기가 만들어졌습니다.