<< Previous | Home | Next >>

권위와 가치

Square의 CEO인 Jack Dorsey가 재미난 글을 올렸네요. 회사생활을 하면서 우리도 이런 비슷한 유혹을 많이 받게 되죠? 아래는 Jack Dorsey가 Medium에 올린 글입니다.

회사 생활을 하다보면 사람들이 아이디어를 제시할 때 권위있는 사람들을 이름을 빌려서(혹은 유명한 포스트나 뉴스 기사 등도 포함) 자신의 아이디어에 설득력을 보강하려한다는 것을 많이 발견하게 됩니다.

누군가의 이름을 빌려서 아이디어를 팔 때는 아래 두가지의 일이 일어난다네요.
  1. 당신의 권위가 떨어진다.
  2. 그 아이디어의 가치가 떨어진다.
간단하다: 당신이 이야기(의견이나 아이디어)의 논지를 남에게 설득시키기 위해서 타인의 이름과 권위를 이용한다면, 당신이 한 의견의 가치는 떨어진다.(당신 스스로가 이야기를 믿지 않고 있을 수도 있다) 당신이 정말 그 일이 옳다고 믿는다면, 스스로가 만들어서 그것을 증명하는데 초점을 맞추어야 한다. 권위는 가치를 제시할 때 나오는 것이지 타인의 권위를 등에 엎는 것으로 자신의 생각에 대한 가치가 부여되는 것은 아니다.

우리는 "John이 이렇게 하라고 해요. 이것이 우리의 방식이에요."라는 결론에 이르게하는 토론이 아니라, 우리가 당연하게 생각했던 것들을 다시 생각하고 대담하고 말도 안되는 아이디어에 대해 열정적으로 토론하기를 원한다. 후자의 방식이라면 Agile하고 혁신성을 유지할 수 있지만, 전자는 시대에 뒤떨어지게 된다.
Tags :

README가 대문자인 이유

README 파일은 해당 디렉토리나 아카입의 소프트웨어에 대한 정보를 기술하는 파일이다. 그런데 왜 대문자로 구성되어 있는지 확인해 보니...

위키에 그 사례가 실려져 있었다.

It is traditionally written in upper case so that on case-preserving environments using an ASCIIbetical ordering, the name will appear near the beginning of a directory listing (since upper-case letters sort before lower-case letters in ASCIIbetical ordering).

ASCII 코드 순으로 정렬되는 환경을 채택한 곳에서는 README 파일이 제일 앞에 보이게 하기 위해서 전통적으로 대문자를 쓰게 된다.
그래서 맥에서 LANG=C이나 LC_COLLATE=C 환경에서 ls -l를 실행해 보니,
$ ls -l
total 7656
-rw-r--r--   1 pepsi  staff    16317 Jul 23  2013 README.md
drwxr-xr-x  11 pepsi  staff      374 Jul 27  2013 bin
-rw-r--r--   1 pepsi  staff     1316 Jul 23  2013 build.xml

ASCII 문자 코드 순으로 README가 맨 먼저 보여지게 된다. 결국, README의 대문자 관습이 존재 의미를 더욱 부각시켜 주는 거 같다.
Tags :

왜 일이 예상대로 끝나지 않는가?

Why is not end as expected things?

1. 시간을 낭비하고 있는건 아닌지.
시간이 있을 때 그때 그때 스스로 찾아서 완결해 가야 하는데, 마감이 임박하지(빠듯하지) 않으면 동기 부여가 안된다. 초반에 느슨하게 하다가 막판에 불지르다가 일정과 품질에 악영향을 준다.

2. 인터럽트 되는 일을 우선시 한건 아닌지.
일을 하게 되면 항상 생기게 마련이 일의 집중도를 떨어뜨리는 인터럽트이다. 이것 때문에 지금 하는 일이 밀려나고 나중에 다시 시작할 땐 업무 전환 비용이 생긴다. 이럴 땐 장유유서, 상명하달의 문화가 큰 적이 된다.

3. 딜레이는 다른 딜레이를 낳는다.
하는 일을 완결하지 못하면 다음 일에도 영향이 생긴다. 여기엔 능력 부족이 큰 기여를 한다.

4. 완벽한 성과를 내려고 한다.
완벽한 성과를 내려면 준비 과정의 비중을 많이 두게 된다. 그리고 진행중에는 적당한 룰 없이 이리 재고 저리 재고하다보면 그러한 업무태도가 결국 시간을 먹는 하마가 된다.
Tags :

사이드 프로젝트의 효용성 그리고 중요성


기업에서 개인 자신에 이르기까지 직원들이나 개인 스스로 자신의 잉여 시간을 잘 활용하는 방법에 대해 고민을 많이 하고 있습니다. 그 중에 하나로 사이드 프로젝트를 활용하는 사례가 많이 도출되고 있어 좀 정리를 해 봤습니다.

생각에 그치지 않고 그것을 실현해가는 습관을 길러주는 사이드 프로젝트, 개인에게 사이드 프로젝트 하나쯤은 지속적으로 유지하면 더 커진 나를 발견할 수 있다고 생각합니다.

그리고 먼저 지속적으로 유지해 보세요. 뭔가 달라질 것 같아 보이지 않으세요? ^^

왜 사이드 프로젝특 좋은가?

사이드 프로젝트라함은 자신의 메인 잡은 그대로 유지를 하고 자기가 좋아하고, 하고 싶은 아이디어, 기술을 통해 부가적으로 프로젝트를 만들어서 시간이 날때마다 실현하는 것을 말한다.

스타트업에서는 "Fail Fast"(빨리 실패하기), "MVP"(최소한의 핵심적 가치를 투입)를 모토로, 빨리 시장에 내놓고 고객의 피드백으로 개선해 가는 방식을 중요한 비즈니스 사이클로 인식되고 있지만, 이와 다른 관점에서 대두되고 있는 것이 바로 Succeeding Slowly(느린 성공)이라는 개념이다. 그리고 이 느린 성공에 어울릴법한 형태가 바로 사이드 프로젝트가 아닌가 해서 언급하고자 한다.

"Why Side Projects matter(왜 사이드 프로젝트가 중요한가)?"라는 아티클에서 보듯이 자신의 본업으로 살아가면 먹고 살수는 있지만, 사이드 프로젝트는 자신을 크게 성장할 수도 있고, 가능성이 큰 아이디어를 실현하는 기쁨, 길게 보고 꾸준한 노력을 통해 나중에 큰 성공을 가질 수 있다는 희망, 자신이 즐거워하는 것들, 가슴을 두근거리게 만드는 작업을 한다는 건 개인적으로 커다란 동기부여와 축복이 아닐 수 없다. 그리고 거시서 자신에게 보이지 않았던 창조성을 찾을 수도 있고 또 그것을 발휘할 수도 있게 해준다.

그래서 본업은 "Fail Fast"(빠른 실패)를, 사이드 프로젝트는 "Succeeding Slowly"(느린 성공)의 관점을 지향하다보면 본업이나 사이드 잡중에서 성공을 맛볼 기회가 커지지 않을까?

또한, 3M의 Jefferson도 사이드 프로젝트의 유용성에 대해 "Success on the Side - The American, A Magazine Ideas"라는 아티클을 썼다. 그 내용 중에 키포인트만 요약해 보면..
혁신은 천재가 고민한 끝에 나온 좋은 아이디어로 만드는게 향상 제품이 되는 건 아니다 :
  • 아이디어는 예기치 않은 곳에서 나온다.
  • 아이디어는 머리로 생각하고 발견되는 것이 아니라, 실제로 뭔가를 만들고 이행하는 중에 나오는 것이다.
  • 중요한 것은 모든 직원의 전체적인 힘, 특히 고객에 가까운 직원의 힘이 중요하다.
라는 말로 사이드 프로젝트의 타당성을 강조하고 있다.

사이드 프로젝트의 성공 사례

"Start Something : The Power of Side Projects(사이드 프로젝트의 힘)"에서 보면 지금은 없어졌는지 모르겠지만, Google의 20%룰에 의해 gmail, news, adsense가 태어났고, Twitter, Instagram, Uber, StumbleUpon 등이 사이드 프로젝트에서 출발해 성공을 이룬 서비스들이다. 또한, 셀로판 테이프(Scotch Tape)를 Richard Drew라는 사원이 개발한 것도 3M이 추구한 "15-percent-time rule"에 의해 이루어졌다고 한다.

모든 시간과 정렬을 본업에 집중해도 경쟁력이 생길까 말까하는 시각도 있을 수 있지만, 잉여 관점에서 프로젝트는 자율성이 보장되고, 다양성이 보장되는 환경에서 스치듯 지나가는 아이디어를 긴 시간을 두고 갈고 닦게 되면 또다른 성공 사례의 가능성도 내포되어 있다고 볼 수 있어 설득력이 있는 방법인 것 같다. 그리고 개발자의 경우 자기가 좋아하는 분야의 기술력 향상을 이룰 수 있는 도구도 될 수 있어 꾸준이만 한다면 마이너스가 되는 전략은 아니라고 본다.

개발자의 사이드 프로젝트 진행 사례 소개

jQuery를 만든 John Resig이 사이드 프로젝트를 통해 메일 코딩하는 습관의 중요하다는 내용의 글 "Write Code Every Day"를 썼다.

그 중에서 자기가 습관화시키는 데 따르고자 했던 룰과 코딩을 습관화하면서 얻은 흥미로운 것들을 몇가지를 여기서 소개하고 사이드 프로젝트를 진행하거나 하고자 하는 분들에게 도움을 주고자 한다.

John Resig이 사이드 프로젝트를 하면서 발생했던 문제들: 주중엔 코딩을 많이 할 수 없어서 주말에 몰아서 하곤 했는데 완성도나 업무의 연결성 등에 문제가 있었고, 주말에 약속이 없다는 것도 보장을 못하고, 기본적으로 해야한다는 스트레스가 컸다고 한다. 그래서 이렇게는 안되겠다고 생각해 Jennifer Dewalt의 사례를 보고 자신만의 코딩을 습관화하기 위한 룰을 만들었다.
  1. 매일 코드를 작성해야 한다. 문서나, 블로그 기사, 혹은 다른 것들을 작성하는 것은 내가 코드 작성하고 난 뒤 여유가 있을 때 작성한다.
  2. 작성한 코드는 유용해야 한다. 들여 쓰기나 코드의 외형 수정은 작성된 코드에 포함하지 않는다. 가능하면 리팩토링도 포함하지 않는다.(이 모든 일은 하루 동안의 일이 아닐때만 허용되는 것이다).
  3. 모든 코드는 자정전에 써야 한다.
  4. 코드는 오픈 소스로 Github에 올여야 한다.
이 룰을 모두가 따라야한다는 것은 아니지만 이 룰을 통해 John Resig은 코드 습관화에 성공할 수 있었고 20주 연속으로 진행했고 유지하고 있다고 한다. 또, 좋은 변화도 생겼다도 하니 추천하는 바이다.

그럼 John Resig이 이 룰을 통해 코드 습관화를 하면서 일어난 코드 작성의 변화나 삶과 인생에 흥미로운 현상들이 생겼다고 해 여기에 몇가지 소개를 한다.

최소 실행가능한 코드. 나는 적어도 하루에 30분은 코딩에 투자를 했다.(의미있는 코드를 짧은 시간에 쓰기는 어렵다. 특히 전날 어디까지 했는지 생각하는 것도 어렵다) 주중의 일부는 약간만 코딩만 했고(1시간보다 작지만), 주말에는 때때로 하루 종일 코드를 짤 수 있었다.

습관화된 코딩. Github에 나타내는 코딩 이력 차트를 신경쓰지 않는다는 것이 좋다. 이런 시도(외부 시선을 평가하는 것)를 없애는 것이 제일이다 : 당신 자신을 위해, 당신 인생에서 무엇을 했는가 하는 것이 중요한 변화이지, 당신이 한 일을 누구에게 인정 받기위한 것을 중요한 것은 아니다. 다이어트나 운동도 같은 형태라고 말할 수 있지만, 자기 스스로를 개선하지 않으면, 진정으로 성공할 수 없다.

불안과의 사투. 이 실험을 시작하기전에 완성도는 "적절하게" 타협하거나, 진척도는 "충분한 지"에 관해 매우 불안한 느낌을 종종 가졌다.(자신의 사이드 프로젝트 납기는 없기 때문에, 이 둘을 상대적으로 계량화하기는 어려워서) 진척도를 느끼는 감각은 실제 진행하는 것만큼이나 중요하다는 걸 깨달았다. 이것은 놀라운 발견이었다. 매일 지속적으로 작업을 진행했을 때(습관화 했을 때), 불안 따위는 녹아 없어져 버리기 시작했다. 나는 내가 한 일의 양에 평온한 기분을 느꼈고, 광적으로 어떤 일을 완료하기 위해 고압적인 욕망을 가지지 않아도 됐다.

주말 작업. 주말에 작업을 완료하는 것은 절대적으로 중요한 것이었다.(자신의 중요한 사이드 프로젝트 코드를 완료하는 유일한 시간이었다) 지금은 그다지 중요하지 않게 되었지만, 그것은 좋은 것이다. 나는 주말에 일주일 동안 달성해야 할 기대치를 쌓았지만, 결국 실망감만 안겨줬다. 내가 원하는 일 모두를 끝낼 수 있었던 적은 거의 없었고, 내 작업을 완료하기 위해 내가 즐거워하던 다른 일정을 취소하는 지경에 이르기도 했다. (딤섬을 먹고, 미술관을 방문하거나 공원에 가거나 내 파트너와 시간을 보내는 등) 내 사이드 프로젝트는 생활을 배제해 버릴만큼 중요한 것이 아니라는 것을 강하게 느낀다.

백그라운드 처리. 매일 자신의 사이드 프로젝트의 코드를 작성하는데 있어서 한가지 재미있는 부작용은 자신의 현재의 작업이 항상 머릿속의 뒤편에서 구동되고 있다는 것이다. 산책하러 나가거나 샤워를 하거나 무언가 뇌를 사용하지 않는 활동을 하고 있을 때면 언제든지 코딩하려고 내용을 생각하고 있기 때문에, 문제를 해결하는 좋은 방법이 발견되기도 한다. 이것은 일주일에 한번 이라든지, 격주로 코드를 작성하는 경우에는 일어나지 않는다. 대신에 그 시간을 다른 작업을 생각하는데 소비되거나 자신의 사이드 프로젝트 작업이 완료되지 않은 것을 넘어 혼란 상태로 바꿔놓기도 한다.

컨텍스트 스위치(업무 전환). 자신의 사이드 프로젝트 작업을 재개하려고 할 때, 컨텍스트 스위치 즉, 업무 전환 비용이 생겨 버린다. 불행히도, 일주일 내내 다른 프로젝트를 진행한 후 자신의 사이드 프로젝트를 다시 생각하는 것은 매우 어렵다. 매일 작업하는 것은 업무에 대한 기억 간격이 짧아지므로, 무엇을하고 있었는지 쉽게 기억할 수 있게 된다.

업무 균형. 이러한 변화중에 가장 중요한 부분중에 하나는 일/삶/자신의 사이드 프로젝트의 균형의 취하는 방법을 배운 것이다. 내 사이드 프로젝트에 매일 하면서 알게 된 것은 내 시간의 균형을 더 잘 잡아야 한다는 것이다. 저녁에 외출해서 밤 늦게까지 돌아 오지 않았을 경우 일정을 짠다면, 다음날 일찍 일어나서 나의 주요 업무인 Khan Academy를 하기 전에 내 자신의 사이드 프로젝트의 목표한 것을 완료할 필요가 있다. 게다가 자신의 사이드 프로젝트 작업이 아직 완료되지 않았다면 밤늦게 나갔을 경우 당신은 서둘러 집으로 돌아가 나머지를 완성해야 한다(대신 하루가 빠져나갈 수 있다). 나는 취미에 시간도 줄어 들었지만, 그것은 살아가는 데 필요한 합리적인 트레이드오프라고 스스로에게 타이르고 있다.

외부의 인식. 이러한 습관은 외부적으로 커뮤니케이션하는데 많은 도움이 되었다. 내 파트너는 내가 매일 코드를 완료해야 하는 것을 이해해 주었고 이들 활동 때문에 일정을 맞춰 주기도 했다. 그 덕분에 "그래, 나가자/영화를 보자. (나가도 좋지만) 나중에 코드를 쓰면 되잖아"라고 편하게 말할 수 있게 되었고, 그리고 이해와 고려도 얻을 수 있었다.

얼마나 코드를 썼어? 지난 몇 개월 동안 써온 코드의 양은 스스로도 믿기 어려울 정도다. 나는 2개의 웹 사이트를 만들었고 몇 가지 프레임 워크를 재작성했고, 새로운 노드 모듈은 셀수 없을 정도 많이 만들었다. 또 내가 잊어버릴만큼 많은 코드를 만들었다. 몇 주 전의 작업이 이제 먼 기억처럼 보인다. 그래서 내가 완료한 일의 양으로 도 만족해 했다.

John Resig의 코딩 습관화가 중요하다는 경험담이나 룰은 참 마음에 와 닿는다. 사이드 프로젝트가 모든것을 해결해주는 은총알은 아니다. 다만 잉여력을 성과로 만들어 내기 위한 좋은 방법 중에 하나라는 생각만은 든다. 그리고 개발자의 전문성을 높이는 가장 효과적인 방법이 업무 외적 분야에서 의도적인 수련의 질과 양을 높이고 증가시키는 것이라고 한다. 그 예가 바로 사이트 프로젝트인 것이다.

저도 주로 주중에는 리서치하고 주말에 세미 프로젝트를 하곤 하는데 이런 방법은 개인적으로도 자기자신을 담글질하는 데 아주 좋은 방법이라고 생각해서 몇자 적어봤습니다.

끝으로, 여러분들도 사이드 프로젝트로 개인적으로나 회사적으로 대박 나시길..

왜 개발자들은 이전 개발자를 나쁜 사람으로 모는가?

"Why your previous developer was terrible"이라는 포스트가 갑자가 가슴에 확 달려드는 것 같아서 몇자 적어 봅니다.

The curse of the present(현재의 저주)라는 표현이 참 와 닿는다.
기술적인 결정을 한 시점 즉, 그 당시에는 불투명했던 상황에서 내린 최선의 결정일 수도 있는데, 조직의 룰에 의해 결정되었을 수도 있는데, 그 이후 시간이 경과한 상태에서 후임자가 현재의 상황을 기준으로 과거의 상황을 비판하는 것은 옳지 않다는 의미의 글이다.

과거를 비판해서 단기간에 자신의 우수함을 보이도록 해 단맛을 보는것 보다는 해당 문제를 현재의 관점에서, 적절한 판단을 해 소화할 수 있는 범위내에서 스스로 보이지 않게 리팩토링해 보는 건 어떨까?

"왜 기업에서 오래된 소프트웨어가 업그레이드 되지 않는가?"에 대한 문제와도 연결되어 있는듯 하다.

  • IT에 대한 비용 인식으로 업그레이드 등 유지보수 업무 자체가 무시되고 비용 산정에도 수많은 난관이 존재했을지도 모르는 상황에서 자신은 어떠했는지
  • 함부로 업그레이드를 해 문제가 되면 책임이 따르니 좋은게 좋은거, 안전이 최우선으로 생각해 당장 큰 문제가 되지 않는다는 인식하에 넘기고 넘기는 관행이 자신에게는 없었는지
욕하기전에 공범자는 아니었는지 고민해 봐야하고, 더 중요한건 스스로 그런 문제를 만들지 않게 행동으로 옮기는 사람이 되는게 더 중요한 듯 하다.
Tags :

JavaScript Promises

Javascript를 사용하다보면 비동기 call 요소들이 많아서 로직의 가독성과 오류 디버깅 문제등이 복잡하게 얽히게 되smsep, (이를 헬이라고도 표현하는데), 이를 회피하기 위한 방법중에 하나가 Promise를 사용하는 것입니다.
대부분 경우 라이브러리로 제공하고 있어, 그 내용을 잘 모르고 사용하는 경우가 많아 오용되는 사례를 경험하게 됩니다. 그래서 내부를 좀 더 이해하는데 도움이 되는 좋은 아티클이 있어서 번역을 해보았습니다.

소개 아티클은 "JavaScript Promises ... In Wicked Detail" 입니다. 상세한 내용은 아래를 따라가 보시면 될 거 같네요.

왜 Promise가 필요한가?

왜 Promise에 대해서 디테일하게 이해하는데 신경을 써야할까? 실제 Promise가 어떻게 동작하는지를 안다는 것은 그것을 활용하는 능력이 향상되고 문제가 발생했을 때에도 더 성공적으로 디버깅을 할 수 있기 때문이다. 이 아티클을 쓰게 된 계기는 동료와 내가 Promise의 까다로운 경우를 접해서 고생한 적이 있었다. 지금 Promise에 대해 알았던 것을 그 때도 알았더라면 고생을 덜 했을 수 있었을 것이다.

심플한 사용 사례

먼저 가장 간단한 Promise에 대한 구현을 살펴보자. 다음 함수가 있다고 가정한다.
doSomething(function(value) {
  console.log('Got a value:' + value);
});

이 함수를 다음과 같이 사용하려 한다.
doSomething().then(function(value) {
  console.log('Got a value:' + value);
});

위처럼 하기 위해 doSomething()에서 다음과 같은 부분을 변경해야 한다.
function doSomething(callback) {
  var value = 42;
  callback(value);
}

다음과 같이 Promise 기반으로 변경한다.
function doSomething() {
  return {
    then: function(callback) {
      var value = 42;
      callback(value);
    }
  };
}


실행 화면 : Fiddle

이 구현은 Calback 패턴의 단순한 대체이며, 이것만으로는 큰 의미가 되지 않는다. 그러나 아주 간단한 구현이었지만, Promise의 핵심적인 아이디어를 이미 이해했다고 볼 수 있다.

Promise는 객체안에 궁극적으로 처리 결과값를 수집한다.

이점에서 Promise가 매우 흥미롭다고 생각하는 큰 이유이다. 일단 Promise 내에 처리 결과가 수집되면, 그 다음은 매우 강력하게 일을 진행할 수 있다. 이 점에 대해서는 좀 더 나중에 설명한다.

Promise 타입 정의
위의 간단한 객체 리터럴 구현은 잘 돌아가지 않을 것이다. 우리는 앞으로 잘 설명하기 위해, Promise 클래스를 정의한다.
function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    callback(value);
  }

  fn(resolve);
}

이를 사용하여 doSomething()을 재구현하면 다음과 같이 된다.
function doSomething() {
  return new Promise(function(resolve) {
    var value = 42;
    resolve(value);
  });
}

그런데, 여기에는 문제가 있다. 실행해서 추적하면 resolve()가 then()보다 먼저 호출되고 그 결과 callback은 null이 된다. 이 문제에 대응하기 위해 setTimeout을 사용(해킹 위협이 있지만)한다.
function Promise(fn) {
  var callback = null;
  this.then = function(cb) {
    callback = cb;
  };

  function resolve(value) {
    // force callback to be called in the next
    // iteration of the event loop, giving
    // callback a chance to be set by then()
    setTimeout(function() {
      callback(value);
    }, 1);
  }

  fn(resolve);
}


실행 화면 : Fiddle

이 코드는 문제가 될 소지가 많은 좋지 않은 코드이다.
이 Promise의 취약성이 많은 코드를 제대로 동작시키기 위해서는 비동기 처리를 해야한다. 이 구현에서 오류를 유발시키는 것은 간단해서 then()을 비동기적으로 호출하라. 그런 후 다시 callback은 null이 된다. 왜 이렇게 취약한 코드를 설정했을까? 위의 구현은 아주 이해하기 쉽다는 잇점이 있기 때문이다. 위와 같은 간단한 구현을 통해 Promise에서 중요한 then()과 resolve()를 명확하게 알 수 있다. then()과 resolve()는 Promise에서 중요한 개념이다.

Promise가 상태를 가진다.

우리의 어설픈 코드는 예상치 못한 것들을 발생시킬 수 있다. 그래서 Promise는 상태를 갖는다는 것이다. 우리는 프로세스를 진행하기 전에 Promise가 어떤 상태인지를 알 필요가 있고, 그리고 그 상태가 어떻게 이동하고 있는지를 정확히 알아야 한다. 이제 코드의 취약성 부분을 없애자.
  • Promise는 값이 확정될 때까지 pending 상태에로 유지되었다가 값이 결정되면 resolved상태가 된다.
  • 일단 값이 resolved되면 항상 그 값을 유지하고 다시 확인을 하지 않는다.
(Promise에는 rejected라는 상태도 있지만, 나중에 오류 처리 때 다시 설명한다.)

해킹의 가능성이 있는 setTimeout을 없애고 구현체 내부의 상태 변화를 추적할 수 있도록 해보자.
function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(onResolved) {
    if(state === 'pending') {
      deferred = onResolved;
      return;
    }

    onResolved(value);
  }

  this.then = function(onResolved) {
    handle(onResolved);
  };

  fn(resolve);
}


실행 화면 : Fiddle

조금 복잡하지만 호출자(호출하는 측)는 원하는 시간에 then()을 호출할 수 있게 되었다. 그리고 피호출자(호출되는 쪽)는 언제든지 resolve()를 부를 수 있게 되었다. 동기나 비동기 상황에서도 완벽하게 동작하고 있다.

이렇게 된 이유는 state 플래그를 사용하기 때문이다. then()과 resolve() 둘 다 새로운 함수인 handle()에 처리를 위임한다. handle()은 상황에 따라 두가지 작업을 구분한다.
  • 피호출자 resolve()를 실행하기 전에 호출자가 then()을 실행하면 값을 돌려줄 준비가 되지 않은 상태이다. 이러한 경우 state는 pending 상태가 될 것이고 호출자가 지정한 callack이 나중에 사용될때까지 유지된다. 그런 후 resolve()가 호출되면 callback이 실행되고 호출자에게 값을 전달한다.
  • 호출자가 then()을 호출하기 전에 피호출자가 resolve()를 호출하면 값은 확정된 상태가 된다. 이 경우 then()이 호출되면 값을 반환할 준비를 한다.
주목할 것은 setTimeout의 구현체는 없어졌지만, 그것은 일시적이고 나중에 다시 나타난다. 자세한 내용은 그 때 설명한다.

Promise를 사용하면 then()과 resolve()의 호출 순서를 고려하지 않아도 된다. then()과 resolve()는 우리의 이용 목적에 따라 언제라도 호출할 수 있다. 이것은 Promise 처리 결과값를 객체에 저장하는 데 아주 큰 장점 중에 하나다.

우리가 구현해야 할 스펙안에는 아주 많은 일이 있지만, 우리의 Promise 이미 파워풀하다. 이 구현체를 사용하면 then()를 원하는 만큼 여러 번 호출해도 항상 같은 값을 돌려받을 수 있다.
var promise = doSomething();

promise.then(function(value) {
  console.log('Got a value:', value);
});

promise.then(function(value) {
  console.log('Got the same value again:', value);
});

이 아티클에서 구현한 Promise는 완벽하지는 않다. 예를 들어, 만약 resolve()가 호출되기 전에 여러 번 then()을에 호출하면 마지막에 호출한 then()만 사용된다. 이 문제를 해결하기 위한 한가지 방법은 callback을 배열로 유지(deferreds)하는 것이다. 그러나 여기에서는 구현은 않고 Promise의 개념을 소개하는데 코드를 가능한 한 단순하게 하려는 목적이 있다. 지금까지 구현으로도 Promise를 설명하기 위해 충분하다.

Promise 메소드 체인

Promise는 객체에 비동기적으로 값을 유지하고 있기 때문에, 메소드를 체인하거나, map처리를 하거나, 병렬 혹은 순차적으로 여러종류의 일을 수행할 수 있다. 다음 코드는 Promise의 대표적인 사용 사례이다.
getSomeData()
.then(filterTheData)
.then(processTheData)
.then(displayTheData);

getSomeData()는 즉시 then() 호출이 되었는지 알 수 있어서 해당 Promise를 반환한다. 그러나, 첫번째 then()의 호출 결과도 Promise며, 그 반환값을 사용해 다음 then()을 호출한다.(그 다음도 마찬가지다). then()에서 Promise를 반환하여 무슨일이 일어나는지 알수 있어 더 많은 정보를 얻을 수 있다. 그것이 메소드 체인을 사용하고 있다는 것이다.

then()은 반드시 Promise를 반환한다.

여기서 우리 Promise 클래스와 함께 메소드 체인을 추가하자.
function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    value = newValue;
    state = 'resolved';

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if(state === 'pending') {
      deferred = handler;
      return;
    }

    if(!handler.onResolved) {
      handler.resolve(value);
      return;
    }

    var ret = handler.onResolved(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved) {
    return new Promise(function(resolve) {
      handle({
        onResolved: onResolved,
        resolve: resolve
      });
    });
  };

  fn(resolve);
}


실행 화면 : Fiddle

휴. 약간 코드가 조금 복잡해졌다. 우리가 천천히 구축하는 것이 기쁘지 아니한가? 여기서 중요한 키는 then()이 새로운 Promise를 반환했다는 것이다.

then()은 항상 새로운 Promise객체를 반환하기 때문에 하나 이상의 여러 Promise객체(created, resolved, ignored 되는)가 있을 수 있다. 이것은 객체의 낭비처럼 보일수 있다. Callback 기법에서는 이것은 문제가 되지 않는다. 다른 한편으로 Promise가 비판을 받을 수 있는 중요한 요소가 된다. 그러나 몇몇 JavaScript 커뮤니티에서는 이런 점으로 인해 Promise를 꺼리는 형태를 취하지 않고, 제대로 접근하기 시작했다.

두번째 Promise가 해결해야하는 것은 무엇인가? 그것은 첫번째 Promise가 반환하는 값을 받는 것이다. 즉,두번째 Promise는 첫번째 반환 값을 인수로 받는다. 이것은 handle() 후반 부분에 구현되어 있으며, handler 객체는 resolve()에 대한 참조와 onResolved의 callback에 대한 참조를 가지고 있다. 거기에는 resolve()의 복사본을 가지고 있고 각 Promise들은 자신의 resolve()를 복사하거나 내부에서 실행 클로저를 갖는다. 이것이 첫번째 Promise와 두번째 Promise의 다리다. 첫번째 Promise를 다음 코드에서 해결한다.
var ret = handler.onResolved(value);

이 예제에서는 handler.onResolved는 다음과 같다.
function(value) {
  console.log("Got a value:", value);
}

다르게 말하면, 어찌하면 이것이 첫번째 호출로 then()에 전달되는 프로세스다. 첫번째 handler 반환 값이 두번째 Promise를 해결하기 위해 사용된다. 이제 메소드 체인 구현이 완성되었다.
doSomething().then(function(result) {
  console.log('first result', result);
  return 88;
}).then(function(secondResult) {
  console.log('second result', secondResult);
});

// the output is
//
// first result 42
// second result 88


doSomething().then(function(result) {
  console.log('first result', result);
  // not explicitly returning anything
}).then(function(secondResult) {
  console.log('second result', secondResult);
});

// now the output is
//
// first result 42
// second result undefined

then()은 항상 새로운 Promise객체를 반환하기 때문에 원하는만큼 메소드 체인을 연결할 수 있다.
doSomething().then(function(result) {
  console.log('first result', result);
  return 88;
}).then(function(secondResult) {
  console.log('second result', secondResult);
  return 99;
}).then(function(thirdResult) {
  console.log('third result', thirdResult);
  return 200;
}).then(function(fourthResult) {
  // on and on...
});


만약 위의 코드에서 마지막에서 모든 처리의 결과를 받고 싶은 경우에는 어떻게하면 될까? 메소드 체인을 사용하여 필요에 따라 우리 스스로가 결과를 매뉴얼하게 전달할 필요가 있다.
doSomething().then(function(result) {
  var results = [result];
  results.push(88);
  return results;
}).then(function(results) {
  results.push(99);
  return results;
}).then(function(results) {
  console.log(results.join(', ');
});

// the output is
//
// 42, 88, 99

Promise는 항상 하나의 값을 해결한다. 만약 두 개 이상의 값을 전달하려면 여러 값을 처리할 수 있는 것들(배열, 객체, 결합된 문자열 등)을 이용해야 한다.

Promise를 더 잘 사용하는 방법으로는 Promise 라이브러리의 all() 메소드나, 많은 다른 Utility 메소드를 사용하면 Promise의 유용성을 더 좋아진다. 무엇을 사용 하는가는 독자 여러분의 취향에 맞게 선택하면 된다.

Callback은 선택사항이다.
then()에 지정된 callback은 엄밀히 필수 사항은 아니다. 만약 callback을 없앴을 경우 Promise는 이전 Promise과 같은 값을 처리해 준다.
doSomething().then().then(function(result) {
  console.log('got a result', result);
});

// the output is
//
// got a result 42

이미 구현된 handle()의 내용을 보면 callack이 없는 경우에는 Promise를 resolve하고 처리를 종료하도록 되어있는 것을 볼 수 있다.
if(!handler.onResolved) {
  handler.resolve(value);
  return;
}


메소드 체인 내에서 Promise 반환
우리의 체인 구현 방법은 약간 정교하지 못한 부분이 있다. 우리의 resolve된 값에 대해 아무것도 확인하지 않고 다음 작업에 그대로 전달한다. 만약 resolve된 값 하나가 Promise 객체이라면 어떻게 될까요? 예를 들어 다음과 같은 경우이다.
doSomething().then(result) {
  // doSomethingElse returns a promise
  return doSomethingElse(result)
}.then(function(finalResult) {
  console.log("the final result is", finalResult);
});


이 코드는 우리가 원하는 대로 움직이지 않는다. finalResult에는 resolve된 값이 아니라 Promise 객체가 전달된다. 의도된 결과값을 얻기위해 다음과 같이 구현을 수정해야 한다.
doSomething().then(result) {
  // doSomethingElse returns a promise
  return doSomethingElse(result)
}.then(function(anotherPromise) {
  anotherPromise.then(function(finalResult) {
    console.log("the final result is", finalResult);
  });
});


코드가 꽤 복잡하게 되었다. 이 솔루션은 Promise의 구현을 변경하여 resolve된 값이 Promise 객체인지 호출자가 의식하지 않고 처리할 수 있도록 한다. 이것은 쉽게 구현할 수 있는데 resolve()에 전달 된 값이 Promise객체인 경우에 특별한 처리가 추가되었을 뿐이다.
function resolve(newValue) {
  if(newValue && typeof newValue.then === 'function') {
    newValue.then(resolve);
    return;
  }
  state = 'resolved';
  value = newValue;

  if(deferred) {
    handle(deferred);
  }
}


실행 화면 : Fiddle

Promise를 수신했을 경우 resolve()를 재귀적으로 계속 호출하게 된다. 타입이 Promise가 아니면 그 시점에서 처리를 다음으로 진행한다.

이런 경우에는 무한 루프가 될 가능성이 있다. Promise/A+ 스펙은 필수 사항은 아니지만 무한 루프를 해결할 수 있는 형태로 구현하는 것을 추천하고 있다.

또한 여기에서 소개하는 구현체는 스펙을 충족하지는 않았다. 마찬가지로 이 아티클에서 소개한 구현체도 스펙을 충족하는 것은 아니다. 여기에 더해 좀 더 호기심을 가지고 있다면 Promise resolution procedure를 읽는 것을 추천한다.

주목할만한 건, newValue가 Promise객체인지 여부의 판정이 느슨하게 체크를 하는 건 아닐까? 여기선 then() 함수가 있는지만 확인한다. 이 덕 타이핑(동적 타이핑의 한 종류로, 객체의 변수 및 메소드의 집합이 객체의 타입을 결정하는 것)은 의도적인 것이다. 의도적으로 모호하게 한 것으로, Promise의 구현체가 서로 조금 다른 3`rd Party 라이브러리간에 상호간의 조합도 서로 Promise이라고 해석할 수 있게 된다.

스펙을 적절하게 따른다면 여러 Promise 라이브러리들을 서로 조합할 수 있다.

여기서 메소드 체이닝과 함께 Promise 구현체는 아주 완벽하다. 하지만 에러 핸들링 부분은 무시되어 있는 것은 알아 두었으면 한다.

Promise 거부하기

Promise가 처리되는 동안 문제가 발생한다면 사유와 함께 거부(reject)해야 한다. 거부된 경우에 호출자는 어떻게 알 수 있을까? 그것은 then()의 두번째 콜백 인자에 오류 발생시에 처리를 전달하여 오류 알림을 알아차릴 수 있다.
doSomething().then(function(value) {
  console.log('Success!', value);
}, function(error) {
  console.log('Uh oh', error);
});

앞에서 언급한 바와 같이, Promise 상태는 pending에서 resolved, 또는 rejected 중 하나의 상태로 전환한다. 결코 두 상태가 될 순 없다. 다른말로 한다면, 위의 2개의 콜백 중 하나만 호출하는 것이다.

Promise가 reject()을 실행하여 rejected를 활성화할 수 있어, reject()라는 함수는 resolve()처럼 중요한 기능을 한다. 그리고 아래에 doSomething()에서 오류 처리를 추가되었다.
function doSomething() {
  return new Promise(function(resolve, reject) {
    var result = somehowGetTheValue();
    if(result.error) {
      reject(result.error);
    } else {
      resolve(result.value);
    }
  });
}

Promise 구현체 내부에는 거부 기능이 필요하다. Promise가 reject되면 그 이후의 모든 Promise도 거부될 필요가 있다.

그럼 다시 Promise 전체에 대한 구현체를 보자. 이번에는 reject 기능이 추가되었다.
function Promise(fn) {
  var state = 'pending';
  var value;
  var deferred = null;

  function resolve(newValue) {
    if(newValue && typeof newValue.then === 'function') {
      newValue.then(resolve, reject);
      return;
    }
    state = 'resolved';
    value = newValue;

    if(deferred) {
      handle(deferred);
    }
  }

  function reject(reason) {
    state = 'rejected';
    value = reason;

    if(deferred) {
      handle(deferred);
    }
  }

  function handle(handler) {
    if(state === 'pending') {
      deferred = handler;
      return;
    }

    var handlerCallback;

    if(state === 'resolved') {
      handlerCallback = handler.onResolved;
    } else {
      handlerCallback = handler.onRejected;
    }

    if(!handlerCallback) {
      if(state === 'resolved') {
        handler.resolve(value);
      } else {
        handler.reject(value);
      }

      return;
    }

    var ret = handlerCallback(value);
    handler.resolve(ret);
  }

  this.then = function(onResolved, onRejected) {
    return new Promise(function(resolve, reject) {
      handle({
        onResolved: onResolved,
        onRejected: onRejected,
        resolve: resolve,
        reject: reject
      });
    });
  };

  fn(resolve, reject);
}


실행 화면 : Fiddle

reject()을 추가하는 것 외에도, handle() 에서도 reject를 처리할 수 있다. handle()내에서 reject 패스와 resolve 패스는 state 값에 의해 결정된다. 이 state 값은 다음 Promise에 전달되고 다음 Promise에서 받은 state의 값을 바탕으로, reject()와 resolve()를 호출하며 자신의 state 값으로 받은 state 값을 설정한다.

Promise를 사용할 때 오류 콜백을 생략하기 쉽다. 그러나 오류 콜백을 생략한 경우에는 문제가 발생했을 때 그것을 찾아갈 수 없다. 적어도 체인된 마지막 Promise에 에러 콜백을 가지고 있어야 한다. 나중에 좀 더 자세한 내용을 다룰 것이다.

예기치 않은 오류도 reject와 연결되어야 한다.
지금까지 에러 핸들링은 알려진 오류만을 대상으로하고 있었다. 그래서 예상되지 않는 오류가 발생할 경우에는 모든게 파멸될 것이다.Promise의 구현체는 예기치 않은 예외를 캐치하고 적절하게 reject하는 것이 필수적이다.

이것이 resolve() 메소드는 try/cach 블록으로 에워싸야 한다는 것을 의미한다.
function resolve(newValue) {
  try {
    // ... as before
  } catch(e) {
    reject(e);
  }
}


또한 마찬가지로 중요한 점은 호출자가 지정한 콜백이 예상치 못한 예외를 던지지 않을 수도 있다. 이 콜백은 handle()에서 호출되는데 다음과 같이 해결한다.
function handle(deferred) {
  // ... as before

  var ret;
  try {
    ret = handlerCallback(value);
  } catch(e) {
    handler.reject(e);
    return;
  }

  handler.resolve(ret);
}


Promise는 오류를 먹어버릴 수 있다.
Promise 대한 이해가 잘 못하면 에러를 완전히 묵살해 버리는 구현을 할 가능성이 있다. 많은 사람들이 겪을 수 있는 것이다.

다음 예를 생각해 보자.
function getSomeJson() {
  return new Promise(function(resolve, reject) {
    var badJson = "uh oh, this is not JSON at all!";
    resolve(badJson);
  });
}

getSomeJson().then(function(json) {
  var obj = JSON.parse(json);
  console.log(obj);
}, function(error) {
  console.log('uh oh', error);
});


실행 화면 : Fiddle

무슨 일이 일어날까? then()의 콜백 인자는 올바른 형식의 JSON을 받을 것을 예상한다. 콜백에서 받은 값을 확인하지 않고 JSON 파싱하면 예외가 발생하게 된다. 그러나 우리가 오류가있을 경우에 대비하여 then()의 두번째 파라미터에 오류 콜백을 지정한다. 이렇게 구현하면 의도대로 에러 콜백이 호출되는 것일까?

아니다. 오류 콜백은 호출되지 않는다. 위의 fiddle 예제를 실행해 보면 아무것도 출력하지 않는다는 것을 알 수 있다. 등골이 서늘해진다.

왜 그렇게 될까? 이것은 예기치 않은 오류(JSON 파싱에 실패한 예외)는 handle()내에서 캐치되지만, JSON 파싱 처리가 호출될 때에는 대상 Promise는 이미 resolved 상태이기 때문에 reject가 호출되지 않는다. 예외가 발생한 경우에는 다음 Promise가 reject된다.

항상 기억하라. then()콜백 안에, Promise는 이미 resolved 상태하는 것이다. 콜백의 결과가 무엇이든 Promise에 영향을 주지 않는다.

만약 위의 오류를 케치하려면 오류 콜백을 다음 then()에서 지정해야 한다.
getSomeJson().then(function(json) {
  var obj = JSON.parse(json);
  console.log(obj);
}).then(null, function(error) {
  console.log("an error occured: ", error);
});

이제 오류 로그를 제대로 기록 할 수 있다.

내 경험상,이 점이 Promise의 가장 큰 함정이다. 더 나은 해결책을 위해서는 다음 섹션을 읽어라.

done()을 구제용으로 사용하자
(전부는 아니지만) 대부분의 Promise 라이브러리들은 done() 메소드를 가지고 있다. 이것은 then()과 매우 비슷하지만, done()을 사용하는 것으로 위에서 말한것처럼 then()의 함정을 피할 수 있다. done()은 then()이 호출되는 곳에서는 언제라도 호출 할 수 있다. 가장 큰 차이는 done()은 Promise를 반환하지 않는다는 것이다. 또한 done()내에서 발생한 어떠한 예외도 Promise 구현체에서 캐치되지 않는다. 다른한편으로, done()은 Promise 체인이 모두 resolved되었을 때 호출하는 것이다. getSomeJson() 예제는 done()을 사용해 좀 더 완전한 구현체가 될 수 있다.
getSomeJson().done(function(json) {
  // when this throws, it won't be swallowed
  var obj = JSON.parse(json);
  console.log(obj);
});

done()에 오류 콜백을 지정할 수 있으며, then()과 마찬가지로 done(callback, errback)라는 상태로 지정할 수도 있다. 오류 콜백(errback)이 Promise 작업이 완전히 종료하고 호출되므로 Promise를 이용한 일련의 처리에서 발생한 어떠한 오류도 캐치할 수 있다.

done()은 (적어도 당분간은) Promise/A+의 스펙이 아니므로, Promise 라이브러리에 의해 구현되지 않을 수도 있다.

Promise의 종료는 비동기가 필요하다.

이 아티클의 초기에 setTimeout을 사용해 약간의 속임수를 썼다. 그런 다음 해킹 위협으로 이를 수정하여 setTimeout을 사용하지 않았다. 그러나 실제로 Promise/A+의 스펙은 Promise 종료는 비동기적으로 이루어진다. 이 스펙의 요구사항을 충족하기 위해 handle() 함수 구현의 대부분을 setTimeout 호출로 감쌀 필요가 있다.
function handle(handler) {
  if(state === 'pending') {
    deferred = handler;
    return;
  }
  setTimeout(function() {
    // ... as before
  }, 1);
}

Promise/A+ 필요한 구현은 이상이 전부다. 사실, 많은 Promise 라이브러리에서 비동기를 지원하기 위해 setTimeout을 사용하지 않는다. 만약 라이브러리가 NodeJS에서 작동하는 경우라면 process.nextTick을 사용할 것이고, 만약 브라우저에서 작동하는 경우면 setImmediate나 setImmediate shim(지금 IE에서만 작동합니다)와 Kris Kowal의 asap(Kris Kowal은 Q라는 인기있는 Promise 라이브러리) 같은 라이브러리를 사용할지도 모른다.

왜 이러한 비동기 요구 사항이 스펙에 있는가?
비동기를 지원함으로써 일관성과 신뢰할 수 있는 흐름을 제공할 수 있다. 다음의 억지로 꾸민 예를 살펴 보자.
var promise = doAnOperation();
invokeSomething();
promise.then(wrapItAllUp);
invokeSomethingElse();

이 구현에서 처리 순서는 어떻게 될까? 이름을 기반으로 추측해 보면, invokeSomething() -> invokeSomethingElse() -> wrapItAllUp() 순으로 호출 할 수 있게 설계되어 있다. 그러나 이런 실행 순서는 Promise의 resolve 처리가 동기적인지 비동기적으로 수행되는지에 따라 달라진다. 만약, doAnOperation() 함수가 비동기적으로 처리된다면 위의 예상대로 실행 순서가 될 것이다. 그러나 doAnOperation()이 동기적으로 처리될 경우 invokeSomething() -> wrapItAllUp() -> invokeSomethingElse() 순으로 되어 가정한 것과 다른 결과가되어 버린다. 이 문제를 해결하기 위해 Promise는 항상 비동기적으로 resolved 되어야 한다. 심지어 비동기가 아닐때도. Promise가 비동기적으로 resolved 됨으로써(합리적인 코드에 비유) Promise 이용자는 Promise가 비동기 처리에 대응하고 있는지 여부를 생각하지 않아도 된다.

Promise는 resolved되기 위해서 한번 이상의 이벤트 루프(작업의 주 스레드에서 루프)가 필요하다. 이것은 표준 콜백 접근 방식의 필수는 아니다.

then/promise 이야기를 끝내기 전에

세상에는 Promise 스펙을 모두 충족 라이브러리는 많이 있다. 그 중에서도 then 팀의 promise 라이브러리는 상당히 심플하다. 그 라이브러리는 간단한 구현 스펙을 충족하고 있고 스펙 이외의 것은 구현하지 않았다. 만약 그 구현을 볼 기회가 있으면, 그 구현이 여기와 매우 비슷하다는 것을 알 수 있다. then팀의 promise 라이브러리는 이 아티클을 쓰기 위한 기반이 되었고, 우리는 거의 비슷한 구현체를 여기 아티클에 구축했다. 개발자인 Nathan Zadoks와 Forbes Lindsay의 위대한 라이브러리 덕분에 JavaScript Promise를 작동시킬 수 있었다. 또한 Forbes Lindsay는 promisejs.org 사이트를 시작했다고 언급했다.

이 아티클에서 구현한 내용과 실제 구현에는 몇 가지 차이점이 있다. 그것은 Promise/A+는 여기서 다루지 않는 다른 자세한 스펙들이 정의되어 있기 때문이다. 꼭 Promise/A+ 스펙 읽어 보길 권한다. 명세서는 비교적 짧고 읽기 쉽다.

결론

끝까지 읽어 주셔서 감사하다. 지금까지 Promise의 핵심 부분을 다뤄왔다. 그리고 많은 Promise 구현 라이브러리는 all() , race() , denodeify() 등 그 밖에도 다양한 기능들이 제공되고 있다. Promise의 가능성을 알기위해서는 API docs for Bluebird를 읽는 것이 좋다. 나는 Promise가 어떻게 작동하는지, 그리고 무엇을 해결하려고 하고 있는지를 이해하고부터는, Promise 정말 좋아하게 되었다. Promise는 내 프로젝트 코드를 매우 간결하게 그리고 우아하게 해준다. 더욱 더하고 싶은 말은 이 문서는 서문에 지나지 않는다는 것이다.
Tags :

나는 왜 아직도 프로그래밍을 하고 있는가?

이 글은 컴퓨터 과학자 Daniel Lemire씨가 쓴 "Why I still program"이란 제목의 글을 번역한 내용입니다. 평생을 프로그래머라는 직업으로 살아가는 분들의 고귀한 존재 이유를 설득력 있게 쓴 글입니다.

보통 사람들은 나이를 먹으면 프로그래밍과 같은 실무적인 일에서 멀어지고 팀 관리나 자금 조달과 같은 좀 더 고급스런 일을 하는 것으로 통념화 되었다고 생각한다. "진정한 교수"는 디테일한 것은 아래 사람들에게 맡기고 "빅 픽처"를 맡고 있다면 좋게 생각하는 것이 학계에서는 진리일까요? 다른 말로 말하면, 그런 조직은 수직 협력의 역학 관계가 작동하도록 설계되어 있다. 즉, 계층 구조의 정점에 서 있는 사람들이 다른 직원들(임금이 싼)을 관리 감독하는 구조이다. 연구 분야에서 수석 과학자가 아이디어를 내고 주니어 과학자가 그 아이디어를 구현한다는 것을 의미한다. 시간이 지나감에 따라 수석 과학자는 주니어 과학자의 일을 할 수 없게 되어 버릴지도 모른다. 하지만, 그들은(수석 과학자) 자금 조달의 전문가이다. 이 모델은 규모를 키울 수 있다. 즉, 수석 과학자는 중간 과학자에 지시하고 그 중간 과학자는 좀 더 젋은 과학자를 관리 감독하고 ..등등의 방식이다. Jorge Cham이 이 모델을 "교수 피라미드(Profzi scheme)" 라고 정의했다. 자금이 풍부하고 또 계속 늘어나는 환경에서는 최상의 모델이기 때문이다.


그 반대의 형태는 수평 협력의 역학 관계이다. 이 모델에서는 수석 과학자는 큰 아이디어 도출부터 실행에 이르는 모든 일을 처리한다. 그들은 가능하면 바빠지게 하는 귀찮은 일을 자동화하거나 피하려고 한다. 협력은 주로 자신과 다른 관점을 얻기 위해, 혹은 부족한 부분을 보완 해주는 전문 지식을 얻기 위해 협력하게 된다. 이 모델은 자금이 부족한 경우에도 잘 작동된다. 그러나, 관련된 사람(이해 당사자)의 수가 많아지게 되면 실패하는 경우가 많다. 수평 협업은 사람과의 친밀한 관계가 필수적이기 때문이다.

각 모델이 최상의 효과를 낼 수 있는 형태는 작업의 종류별로 다르다. 추측컨데, 수직 협력은 장기 계획이나 결과를 예측하는 경우에 적합하다. 수평 협력은 우연한 발견이나 "야성적(거친)" 아이디어를 도출하는 것을 시도해 보는데 적합하다고 생각한다.

수평 협업을 좋아하는 사람중 한사람으로써, 나는 나이가 들었음에도 불구하고 아직까지 프로그래밍을 하고 있다. 이것은 보통 일은 아니다. 사람들이 놀랄정도의 이상한 일이다. 몇몇 프로그래밍은 오래 걸리는 경우도 있다. 나는 연간 2~3 개월은 프로그래밍 하는데 시간을 보내고 있다. 아마도 시니어인 나의 시간 가치때문에 내 수입보다 작게 버는 사람들이 가장 잘 할 수 있는 프로그래밍인, 이런 비천한 일에 시간을 투자할 수 없다. 그런데 왜 나는 아직도 프로그래밍을 할까요?

아마, 나를 가장 옹호해주는 것은 거장 "Donald Knuth" 일지도 모른다. 그의 말을 빌리자면,

높은 수준의 추상적인 아이디어의 힘과 아름다움을 발견한 사람은 종종 이런 실수를 한다. 뭐냐면, 낮은 수준의 구체적 아이디어는 상대적으로 가치가 없고, 잊어 버려도 상관 없다고 생각한다. (중략) 반대로, 최고의 컴퓨터 과학자는 실제 컴퓨터가 어떻게 움직이고 있는지에 대한 기본 개념을 철저하게 뿌리 깊은 곳까지 알아버리려고 한다. 실제로 컴퓨터 과학을 하는데 있어서 핵심이되는 본질은 높은 수준 뿐만 아니라 다양한 수준의 추상화를 동시에 생각할 수 있는 능력이다.

그러나, 나는 이것과는 별개로 내 나름대로의 지론을 가지고 있다.
  • 나는 중요한 일이나 임펙트가 있는 일을 하고 싶다고 생각한다. 그러나 논문에 많이 인용되는 것들조차도 논문의 결과물을 가져다 쓴 사람들에게는 읽혀지진 않는다. 큰 임팩트를 가진 논문은 거의 없다. 한편, 소프트웨어와 함께 의미있는 일을 하는 것은 논문 세계보다 더 쉬워진다. 예를 들어, 최근 Facebook에 있는 개발 팀이 Apache Hive 내의 내 "압축 비트맵 인덱스 라이브러리"중 하나를 사용해 시스템에 통합했다. 내기를 해도 좋겠지만, 이 소프트웨어를 위해 내가 쓴 원 논문을 Facebook의 어느 누구도 읽은 사람이 없었을 것이다. 중요한(실용적인) 것은 소프트웨어인 것이다.
  • 나는 자신의 아이디어를 구현하려고 할 때 그 아이디어를 더 깊이 이해할 필요성을 느껴왔다. 평상시에 논문에서 정상적이며 합리적으로 보였던 것이 자신이 직접 구현할 때에는 다루기 힘들게만 느껴진다. 또한 나는 종종 수학적 논의에 버그가 있었다는 것을 구현을 통해 발견할 수 있었다. 내가 이런 일을 누군가 다른 사람에게 아웃 소싱할 수 있을까? 아마도 못할 것이다. 하지만, 이런 프로세스는 좋은 성과를 내지 못한다.
  • 사람은 시간이 지남에따라 프로그래밍이 능숙해 진다. 나는 수십년 동안이 전문성을 길러왔다. 다른 사람이라면 몇 주 또는 몇 달이 걸릴 어려운 문제를 처음부터 착수해서 몇일 내에 어려운 문제를 풀어 버리는 것은 기분 좋은 일이다.

만약 위에서 제시한 나의 논리가 합리적이라면, 심지어 Donald Knuth가 나의 편이라면, 내가 프로그래머겸 과학자라고 털어놓는다면 왜 아직도 사람들은 놀랄까요? 하층의 작업으로 인식해서 프로그래밍을 거절하는 현상은 "유한 계급의 이론(Theory of the leisure class)"에 의해 설명 될 수 있다. 결과적으로 사람들은 유용성이 아닌 명예를 찾고 있다. 도구 만들기, 요리 또는 농업 등은 명예가 되지 않는다. 명예를 극대화하고 싶다면, 유한 계급에 올라서야 한다. 당신이 할 일은 즉시 뭔가 유용한 일이여서는 안된다. 그래서, 요리사와 간호사가 되는 것보다 CEO와 정치가가 되는 것이 더 명예를 높이게 되는 것이다. 저 멀리서 일을 감독하는 과학자가 더 명예가 있는 것이다. 프로그래밍은 도구 만들기 같은 것이기 때문에, 유한 계급의 사람들은 되려고 하지 않는다. 그들은 스스로를 엔지니어, 분석가, 혹은 개발자로 부르겠지만, 너무 실용적이기 때문에 프로그래머라는 호칭을 사용하지 않는다.

주의 : 모든 사람이 프로그래밍을 해야하는 것은 아니다. 프로그래밍은 많은 시간을 소비하는 활동이다. 내가 프로그래밍을 너무 좋아하기 때문에 내가 하지 못하는 다른 많은 흥미로운 일도 존재한다는 것도 염두해 둘 필요가 있다.