<< Previous | Home

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


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

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

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

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

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

스타트업에서는 "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와 정치가가 되는 것이 더 명예를 높이게 되는 것이다. 저 멀리서 일을 감독하는 과학자가 더 명예가 있는 것이다. 프로그래밍은 도구 만들기 같은 것이기 때문에, 유한 계급의 사람들은 되려고 하지 않는다. 그들은 스스로를 엔지니어, 분석가, 혹은 개발자로 부르겠지만, 너무 실용적이기 때문에 프로그래머라는 호칭을 사용하지 않는다.

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

Trello 아키텍처

CoffeeScript

Trello의 클라이언트와 서버 모두 순수 자바스크립트 프로젝트로 시작되었고, 2011년 5월까지는 적어도 그렇게 유지되었는데 그 이후부터 CoffeeScript를 얼마나 좋아하는지 보기 위해서 CoffeeScript로 이중으로 개발해 포팅하기 시작했다. 그 후 우리는 CoffeeScript를 사랑함을 확인했고 곧, 나머지 모두의 코드도 CoffeeScript로 전환을 완료했다.

CoffeeScript는 JavaScript를 읽을 수 있는 컴파일 언어이다. 우리가 시작했을 때도 걱정이 되었던 부분이지만, 소스를 직접 디버깅하는 것보다 컴파일된 코드를 디버깅하는 것이 더 복잡성이 추가된다는 것에 대해 걱정을 많이 했었다. 우리가 시도를 했을때, 비록 변환이 크롬에서 디버깅할떼 약간의 정신적인 노력이 필요하지만, CoffeeScript를 사용하는 것이 소스를 타켓 코드로 매핑하는 일도 너무나 명료했고, 또 코드도 간결하게 되었고, 가독성이 좋다는 것은 분명해 우리가 사용하는데에 있어서 설득력이 충분했다.

JavaScript는 정말 멋진 언어이다. 잘 작성된 CoffeeScript는 Javascript와 같은 내용을 나타내는데에도 더 짧고 유연했다. 그리고 실질적인 디버깅의 간접적인 문제는 발생하지 않았다.

The Client

Trello 서버는 HTML 클라이언트 측 코드를 거의 다루고 있지 않다. Trello 페이지는 2K 쉘에서 하나의 경량화되고 압축된 JavaScript 파일(3`d Party 라이브러리와 컴파일된 CoffeeScript와 Mustache 템플릿을 포함한)과 CSS 파일(인라인된 이미지를 포함, LESS 소스를 컴파일해 압축한)로 구성된 것을 클라이언트 측 앱에서 당겨오는 구조로 되어 있다. 클라이언트 측 프로그램은 250K 이하의 크기로 Amazon의 CloudFront CDN에 캐시되어 있기 때문에 모든 지역에서 지연없이 서비스할 수 있다. 적당히 높은 대역을 확보한 경우에는 응용 프로그램을 브라우저에 서비스하는데 0.5초 정도면 가능하며, 그 이후는 캐시에서 서비스가 된다.

병렬적으로 첫 번째 페이지의 콘텐츠는 AJAX로 데이터를 로드하여 시작되며, 동시에 서버와 WebSocket 연결을 설정한다.

*. BACKBONE.JS

데이터 요청 처리를 할 때 Backbone.js는 무지 바빠진다. Backbone.js는 서버에서 view와 함께 보내진 model을 렌더링하고 다음과 같은 쉬운 방법을 Backbone.js는 제공한다.

  1. view에서 생성된 HTML에서 DOM 이벤트를 감시하여 해당 model에 대응되는 메서드와 연결해서, 서버와 다시 동기화되면 다시 관련 method에 대응해 준다.
  2. 변경을 위해 model을 감시하고, 변경 사항을 반영하기 위해 model의 HTML 블록을 다시 렌더링한다.
이제 이러한 접근법으로 우리는 꽤 표준화되고, 이해하기 쉬우며, 유지보수가 용이한 클라이언트를 얻었다. 그리고 우리는 업데이트나 클라이언트측 model 재사용하기 위하여 클라이언트측 model 캐시 구조를 만들었다.

*. PUSHSTATE

클라이언트 앱 전체를 브라우저 창에서 로드하는 형태의 구조를 가지고 있지만, 우리는 또한 페이지 전환으로 인한 시간 비용을 낭비하고 싶지 않다. 페이지 간의 이동에는 HTML5 pushState를 사용하고 있어, 이 방법은 주소창에 적절하고 일관성 있는 링크를 부여할 수 있고, 전환 시점에 데이터를 읽고 적절한 Backbone.js 컨트롤러에 전달한다.

*. MUSTACHE

Mustache는 로직이 적은 템플릿 언어로서, 모델을 HTML로 변환해 주기 위해서 사용하고 있다. Mustache의 'Less is more' 접근 덕분에 우리의 코드는 엉망으로 만들거나 클라이언트 논리가 뒤죽박죽 섞이지 않게 하도록 템플릿 코드를 재사용 할 수 있다.

Pushing and Polling

실시간 업데이트 자체는 새롭지 않지만, 협업툴에서는 정보를 공유하는 서비스이므로 중요한 기능이 된다.

*. SOCKET.IO AND WEBSOCKETS

브라우저 지원(Chrome/Firefox/Safari)이 되면, WebSocket 연결을 사용하여 서버는 다른 사용자의 변경 내용을 대체로 1초 이내에 동일한 채널을 수신하는 브라우저에 푸시 할 수 있다. 우리는 Socket.io를 수정해서 사용하고 있으며, 또, 수천의 WebSockets 연결을 유지하고 그 환경에서도 CPU나 Meory를 적게 사용하도록 지원하는 서버 라이브러리를 사용하고 있다. 그래서 브라우저상의 화면에서 어떤 일이 일어났을 때, 서버 프로세스에 전달되고 1초 이내에 여러분의 브라우저에 다시 전달된다.

*. AJAX POLLING

매력적이진 않지만, 잘 작동이 되었다.


WebSocket을 지원하지 않는 브라우저(예를 들어 Internet Explorer 같은)라면 사용자가 활성화되면 몇 초마다 콘텐츠 갱신을 위해 AJAX 요청을 하고 사용자가 유휴 상태가 되면, 10 초마다 폴링을 하고 있다. 서버의 설정 덕분에 적은 오버헤드로 HTTPS 요청을 서비스 할 수 있었으며, TCP 연결을 오픈하고 있으므로 필요할 때 폴링하여 어느 정도의 사용자 경험을 보장할 수 있었다. 우리는 Socket.io의 다운로드용으로 Comet을 시도해봤지만, 앱에 리스크가 존재해서 이용하는 것을 포기했다.

Trello을 런칭 직후 Techcrunch의 기사로 급격한 트래픽 증가에 대응할 수 없었던 때도 있었지만, WebSocket의 polling 전환과 polling active와 idle의 간격 튜닝을 통해 무사히 넘어갔다.

The Server

*. NODE.JS

Trello의 서버는 Node.js로 구축되어 있다. Trello는 대량의 커넥션 오픈을 필요로 하고 있고 업데이트를 즉시 전파하는 것을 원했기에, event-driven, non-blocking 서버가 적합하다는 판단을 하고 Node.js를 시험했다. Node.js는 단일 페이지 앱의 프로토타입 만들기에도 적합하다는 것을 알 수 있었다. Trello의 서버 프로토타입 버전은 정말 하나의 Node.js 프로세스 메모리에 모델을 배열로 운영하는 Function 라이브러리들이고 클라이언트는 웹소켓 레퍼들을 이용해 functions들을 호출하는 구조이다. 이러한 구조는 트렐로의 기능들을 빨리 시작할 수 있게하고 설계 또한 올바르게 진행되고 있는지 확인하는데 빠른 방법이다. Trello의 개발 관리와 우리 Fog Creek의 내부 프로젝트들은 동일한 프로토타입 버전으로 가져갔다.

우리는 프로토타입을 완료했을 떼, Node에 대해 그 기능과 성능에 대해 흥분했고 또한 사용하기에 좋고 편안했다. 그래서 아래의 구조를 추가하고 본격 채용하기로 했다.

  • DB, 스키마(node-mongodb-native와 Mongoose)
  • 라우터나 쿠키 같은 웹 기본 기술(Express와 Connect)
  • 리스타트시 다운타임 없는 형태의 여러 서버 프로세스(Cluster)
  • pub/sub 내부 프로세스와 Redis 기반의 데이터 공유(node_redis)
Node는 위대하며 많은 활동적인 개발자 커뮤니티로 인해 새롭고 유용한 라이브러리들을 많이 많들어 내 시간이 갈수록 더 좋아지고 있다. 그리고 우리가 제어 가능하고, 코드를 잘 유지하기 위해서 비동기 라이브러리(CoffeeScript에 의해 증가된 코드 간결성)를 훌륭하게 사용하고 있다.

*. HAPROXY

Web 서버의 로드 밸런스에 HAProxy을 사용한다. 머신 간의 라운드 로빈에 의해서 TCP의 균형을 잡고, 그 이외는 모두 Node.js에 맡긴다. WebSocket을 지원하도록 연결을 오랫동안 오픈하고 AJAX 폴링을 위해 TCP 연결을 재사용 한다.

*. REDIS

서버 프로세스간에 공유되는 데이터는 디스크에 영구히 저장하는 것이 아닌 임시 데이터를 위해서 Redis를 사용한다. 세션 활동 수준이나 임시 OpenID 키 등은 Redis에 저장한다. 만약 그 데이터가 소실되어도 앱측에서는 영향이 없도록 되어 있다. allkeys-lru 를 활성화하고 실제 필요한 공간보다 5배 정도 넓은 공간을 확보해서 실행한다. 그래서 Redis는 최근 액세스되지 않은 데이터는 자동으로 삭제되고 필요할 때 다시 구축된다.

Redis 사용중에 가장 흥미로운 사용법은 model의 변경 내용을 브라우저에 보내기 위한 short-polling의 대체이다. 객체가 서버 측에서 변경되었을 때, JSON 메시지를 모든 적절한 WebSocket에 보내 클라이언트에 알린다. 그리고 동일한 메시지를 변경의 영향을 받는 model을 위해 고정 길이 리스트 내에 보관한다. 또한, 리스트에 지금까지 몇 개의 메시지가 쌓여 있는지도 기록한다. 그런후 클라이언트가 서버를 AJAX 폴링하여 마지막 폴링 이후 객체에 변경 사항이 있는지 확인하면 권한 체크를 통해 모든 서버의 응답을 처리 할 수 있다. Redis는 놀랄만큼 빠르기 때문에 싱글 CPU에 영향을 주지 않고 초당 수천건의 체크를 수행할 수 있다.

Redis는 pub/sub 기능도 있어 객체 변경 내용의 메시지를 모든 서버 프로세스에 보내는 역할을 담당하고 있다. 한번 Redis 서버를 사용하고 있다면, 모든 일에 그것을 사용하기 시작할 것이다.

*. MONGODB

MongoDB는 우리의 전통적인 RDB의 욕구를 충족시켜준다. Trello가 전광석화같이 빠른 서비스가 되는 것을 원하고 있다. 우리가 알고 있는 멋지고 가장 성능에 집착하고 있는 팀중에 하나가 바로 우리의 이웃 자매 회사인 StackExchange이다. StackExchange팀의 말을 들어보면 SQL 서버를 이용하고 있어도 성능을 내기 위해 대부분의 데이터는 비정규화된 형태로 보관하고 필요한 경우에만 정규화하고(RDB) 있는 것으로 나타났다.

MongoDB에서는 빠르게 DB에 쓰기 위해서 관계형 DB 기능(예를 들어, arbitrary join)을 포기하고 더 빨리 읽을 수 있는 비정규화를 잘 지원할 수 있게 되어 있다. 카드의 데이터를 데이터베이스 내에 싱글 다큐멘트 안에 저장할 수 있고, 다큐먼트의 서브 필드(그리고 인덱스)에 쿼리를 걸 수 있다. 서비스가 급성장하고 있기 때문에, 읽기/쓰기의 용량을 조정할 수 있는 데이터 베이스를 가지고 있다는 것은 좋은 일이다. 또한, MongoDB는 복제, 백업, 복원에도 편리하다.

문서 저장을 엄격한 구조로 가지 않는 것에 대한 또 다른 장점은 DB 스키마 마이그레이션의 번거로움 없이 동일한 DB에 다른 버전의 Trello 코드를 수행 할 수 있다는 것이다. DB 업데이트 시 서비스를 중지해야 하는 경우는 거의 없다. 이 방식은 개발에 있어서도 쿨하다. 버그 소스를 찾기 위해 관계형 테스트 DB와 hg-bisect(혹은 git-bisect) 명령을 사용하면, 테스트 DB를 업그레이드/다운 그레이드(또는 필요한 속성과 함께 새로운 것을 추가하는)의 추가 작업은 정말 큰일을 만들어 일을 지연시킬 수 있는 확률이 높다.

그 외

많이 사용하는 모듈은 Async.js 과 Underscore.js . Express , Hogan.js , AWS SDK for JavaScript 도 최근에는 이용하고 있다.

프로덕션 릴리스의 빈도는 주 3 ~ 5 회 정도이고. 긴급 수정 사항은 별도로 수시로 진행하고 있다. 패키지 과정은 CoffeeScript과 LESS 파일을 미리 컴파일하여 압축하고 CDN에 올리고 그 다음 정적 파일 준비도 할 수 있으면 준비해서 프로덕션 환경에 업로드한다. 9개의 상용 web 서버와 2대의 스테이징 서버가 운영중에 있다. 서버 1대씩 SSH를 통해 tarball 파일을 업로드 풀에서 제거 요청을 중지하고 socket을 kill하고, 노드 프로세스도 하나씩 kill한다. 프로세스가 모두 종료되면 다시 시작하고 로컬 호스트가 요청에 응답을 시작할 때까지 curl로 계속 그 web 프로세스를 HAProxy에 건네 준다. 그리고 다음 서버 작업에 임한다. 이 과정은 모두 자동화되어 있으며, 관리 화면(Graphite 데이터 베이스)에서 디스플레이 되고 있어, 무엇인가 문제가 보이면 즉시 롤백할 수 있도록 되어있다. 가까운 미래에 API를 구성하여 클라이언트 측을 서버 측과는 별도로 전개하는 형태로 진행할 것이다. 이 방법이 대단히 많은 클라이언트의 대응에도 훨씬 쉬워진다.

JASON 로그를 logstash + Elasticsearch + kibana 처리하고 있다. 최근에는 Chrome이나 Firefox에서 스택 추적이 잘 잡히지 않아 Amazon CloudFront는 Cross Origin에서 올바른 헤더를 돌려주지 않는 것이 있어, CloudFlare로 전환했지만, 루트 도메인의 CNAME 문제가 결국 하위 도메인을 채용하는 것으로 겨우 오류 모니터링이 안정화 되었다.

[참조 사이트]

4개의 테크 기업을 창업하면서 배운 90가지 유/무형의 자산들

"90 Things I’ve Learned From Founding 4 Technology Companies"라는 포스트는 기업가분들이 기업을 운영함에 있어서 산제되어 있는 많은 문제 해결을 위한 훌륭한 인사이트가 많이서 발번역 수준이지만 번역해 봅니다.

1. 당신 회사의 유일한 비즈니스를 찾아라. 그 비즈니스는 아래 3가지 진리라는 공통점을 가지고 있어야 한다.
- 당신과 팀이 큰 열정을 받을 것.
- 당신과 팀이 세계에서 제일이 될 수 있는 한발의 은총알을 가지고 있을 것.
- 거대한 미개척 시장의 기회가 있을 것.

2. 당신이 하고 있는 일이 위 3가지 진리를 공통적으로 가지고 있지 않다면 당신은 잘못하고 있는 것이다.

3. 당신은 유일한 하나의 일만 하라.
그 외의 것들은 집중을 방해하는 것들이다. 사이드 프로젝트는 하지 마라. 불필요한 회의를 하지 마라. 당신이 유일한 일을 하는데 있어서 방해되는 그 어떤 것들도 장애물이다. 당신의 유일한 일에 기여하지 않는 것에는 모두 No라고 말하라.

4. 제품이 전부이다.
항상 그래 왔고 앞으로도 그럴 것이다. 중요한 것은 당신의 제품이 얼마나 우수한 것인지 뿐이다. 그외 나머지는 소음에 불과하다. Fab에서, 웹사이트나 앱의 가상 제품이나, 우리가 시장에 팔 상품인 실물 제품, 그리고 우리의 운영이나 서비스는 사용자 경험 제품이다. 우리에겐 이 세가지를 좋게하는 것이 전부이다.

5. 당신의 제품이 좋은가에 대한 판단은 얼마나 많은 사용자가 당신의 제품을 사용하는지와 당신의 제품으로부터 얼마만큼의 가치를 얻을 수 있는지 두가지 뿐이다.

6. 초기 단계에서 당신의 성공을 결정하는 열쇠는 매력이다.
어떻게 하면 얼리 어댑터를 모을까? 또 그 매력을 최적화할 수 있을까?를 생각하면서 대부분의 시간을 보내라. 매력을 일으키기에 성공하면 매력은 더 큰 매력을 부른다.

7. 1년에 안에 관심(매력을 발산하는지)을 얻지 못하면 피벗해라. 제품의 개발주기가 극단적으로 짧아지고 있고, 사용자의 피드백은 빨리 도달하는 지금, 당신이 하는 유일한 일이 가치가 있는지를 아는데 있어서 1년이면 충분하다고 생각한다.

물론, 처음에는 잘되지 않을수도 있고 그 어느 누구도 알 수 없을 수도 있다. 그러나 기능은 반복 개발을 할 수 있지만, 비즈니스 모델에 대해서는 반복 개발은 할 수 없다. 많은 기업들이 (착각일수 있지만) 매력을 발산하는데 도움을 준다는 핑계로 새로운 마법의 기능들을 밑도끝도 없이 추가는 습관으로 인해 해당 기업들이 덫에 걸리거나 실패하는 것을 많이 봐왔다. 멈춰라. 해결해야 할 과제는 그 외에도 많이 있다.

1년 이내에 측정 가능한 현실적인 매력을 제시할 수 없다면 다른 문제로 이동해라. 맹세코, 만약 당신이 일년 동안 스타트업에서 근무중이면서 현실적인 매력을 아직 경험하지 못했다면, 만약 당신이 자신과 팀에 다음의 세가지 질문의 설문 조사를 하자. (1) 이것은 우리가 가장 열정을 가지고 해결해야할 과제인가? (2) 우리는 그 과제에 대해 정말 세계 No.1인가? (3) 거기에는 미개척 거대 시장이 있는가? 당신은 잘못된 일을 하고 있는 것을 알 것이다. 그래서 피벗을하는 것이다. 3개의 진리가 공통으로 포함된 유일한 일에 피벗을 하라.

Fab의 전신인 fabulis는 게이 커뮤니티를 대상으로 한 소셜 네트워크였다. 우리는 많은 멋진 기능을 반복해서 구축을 진행했다. 그러나 우리는 정말 우리가 해야할 유일한 일을 전혀 파악하지 못했다. 그 대신에 우리는 많은 것을 도전하고 뭔가 꽂힐것만 같은 것에 기대를 계속했다. 1년 후, 우리는 앉아서 정직하게 토론하고 우리의 유일한 일에 많은 열정을 쏟았다. 그래서 우리가 정말 세계에서 No.1이 되었고, 그리고 미개발된 거대한 시장을 이해했다. 그것은 디자인이었다. 그래서 우리는 fabulis에서 Fab로 피벗한 것이다.

8. 일단 피벗되면 그것에 집중하고 되돌아 보지 마라.
fabulis에서 Fab으로, 게이 소셜 네트워크에서 디자인으로 피벗했을 때 우리는 새로운 유일한 일에 모든 것을 집중하기 위해 10일안에 모든 것을 결정을 했고 팀 모두가 과거의 프로젝트에 참여하는 인원은 한명도 없도록 규칙을 정했다. 우리는 우리의 유일한 하나의 일에 집중하기 위해 공통 인식과 마지막 한 온스의 자원까지 필요했다. 우리는 이전의 웹 사이트와 앱을 즉시 폐쇄했다. 우리는 10일 동안 회사 전체를 피벗하고 미래에 집중하고 다시 돌아 보지 않았다.

9. 자신을 인식하라.
당신에게 유일한 것을 알아라. 즉, 자신이 정말 잘하는 유일한 것이다. 마찬가지로 당신의 유일한 것이 아닌 것도 알아라. 그것이 당신의 많은 부분이 못하는 것이라는 것도 알아야 한다.

10. 당신은 아니다. 첫부분.
성공하는 회사를 만드는 것은 당신의 능력인 것이 아니라, 당신 주위의 사람들에게 위대한 것을 제공할 수 있는 능력인 것이다. 우리가 fabulis에서 Fab으로 피벗했을 때, 우리의 창업자 중 한명인 Bardford Shellhammer의 독특한 유행을 만드는 재능을 살린 사업을 구축하기로 했다. Fab은 Bardford의 독특하고 다양한 색채 감각, 유머, 특이성, 그리고 온 세상이 이것을 도입해 사람의 삶을 향상해주는 제품에 대한 열정을 사용하면 세계에서 No.1이 될 수 있다는 개인적인 감각에서 Fab이 태어났다. 누군가의 재능에 초점을 맞춘 비즈니스를 만든다는 것은 용기와 더불어 자신을 더 잘 알아야 한다는 것이다.

11. 당신은 아니다. 두번째 부분.
당신이 아니라 당신의 고객이다. Fab에서 우리는 첫날부터 우리의 고객을 웃게하는 일에 초점을 맞춰왔다. 처음부터 우리는 매출을 기준으로 어떤 기능을 만들지, 어떤 제품을 판매할지에 대해 결정하지 않았으며 오히려 고객을 웃게하는 유일한 것에 초점을 맞췄다. 그래서 시간이 지나면 결국 많은 매출이 나올 것이다. 그 철학​​에 충실하는 것이 Fab 성공의 첫번째 열쇠였다.

12. 당신보다 더 나은 공동 창업자를 가져라.
난 운좋게도 훌륭한 재능을 가진 공동 창업자와 함께 Fab을 시작할 수 있었다. Nishith Shah, Sunil Khedar가 그들인데, 이전에 정품 바이럴 Facebook 앱 중에 하나를 만들었고 2008년에 나와 함께 socialmedia의 공동 창업자였다. Veerle Pierterssms 세계에서 가장 재능있는 그래픽 디자이너로 2009년에 그를 스카우트했다. 그의 디자인은 내 미적 감각은 물론 Bradford도 막족했다. Fab은 우리의 능력과 열정을 결합하면서 시작되었다. 그리고 아르바이트를 포함해 600명 이상의 사람들이 도와 우리를 더 나아가게 만들었지만, 여전히 오늘도 변함없이 그렇게 하고 있다.

13. 당신이 사랑하는 사람들과 일을 해라.
당신이 열정이 있는 사람들과 일을 해라. 당신에게 스릴을 느낄 수 있는 사람들. 당신이 신뢰할 수 있는 사람들. 당신이 매일 볼 수 있고 말할 수 있는 사람들. 나는 정말 감정적인 인간이다. 사랑은 비즈니스 성공에 필수품이다.

14. 당신이 공동 창업자들을 보이게, 또 그들이 당신을 볼 수 있도록 당신의 책상을 배치하라.
만약 당신이 매일 서로 보는 것이 즐길 수 없다면, 당신은 잘못된 사람들과 일을 하고있는 것이다.

15. 당신이 사랑하지 않는 사람들과 일하지 마라.
Bradford와 나에게는 원칙이있다. 우리가 Fab을 경영하고 있는 동안에는, 우리가 서로 사랑하는 사람들만 같이 일을 할 예정이다. 만약 누군가와 즐겁게 일을 할 수 없다면, 그것이, 직원이든, 파트너, 누구라도 우리는 함께 일을 하지 않을 것이다. 우리가 가치를 희생해가면서 사람들과 함께 일하면서 얻을 수 있는 단기적 이익은 아무것도 없다.

16. 창업자들은 스스로에게 큰 무언가를 소유해야 한다.
리드하는 것만으로는 충분하지 않고, 당신은 비즈니스나 브랜드에 큰 영향을 주는 무언가를 소유해야 한다. 당신은 정말로, 정말로 그것을 소유할 필요가 있다. 내 개인적인 신념은 모든 것이 제품에 관계하기 때문에 최고의 창업자는 프로덕트 매니저여야 한다는 것이다. Bradford와 나는 우리의 제품의 소유권을 나눴다. 나는 가상 제품(웹 사이트와 앱), 그는 실물의 제품(판매 상품)의 소유권을 가지고 있다. 오늘까지 나의 의견이나 리뷰, 승인없이 어떤 웹 사이트나 앱이 1픽셀의 반영도 없고, 마찬가지로 Bradford 역시 자신의 의견, 리뷰, 승인없이 Fab에서 승인 판매되고 있는 디자이너는 한 명도 없다. 이것은 중요한 것이다. 물론 우리는 많은 훌륭하며 신뢰할 수 있는 사람들을 고용하고 있지만, 창업자인 우리는 여전히 최종 제품을 소유하고 제어하고 있다. 나는 다른 방법이 상상할 수도 없다.

17. CEO로서 당신은 다른 사람이 할 수 없는 것도 할 필요가 있다.
대체로 그것은 피칭, 투자자의 선택, 이사회 관리, 집행부 코칭, 그리고 팀이 전략적인 큰 그림을 이해하고 그것을 실현시키도록 도와야 하고, 팀을 격려하고, 상승세를 이어가며, 집중하게 해야한다. 이것은 CEO만이 할 수 있는 것이어서 이들을 아웃소싱 할 수 없는 부분이다.

18. 당신과 논쟁시에도, NO라고 말할 수 있는 사람들과 일하라.

19. 집행부(회사 간부)를 고용 할 때에 가장 중요한 것은 우리의 문화에 친숙해질 수 있는지 여부이다. 당신이 하고 있는 일의 스타일과 속도를 즐길 줄 아는, 그리고 잘 이해해주는 그런 사람들과 일할 필요가 있다. 어떤 경험이나 현명함도 상관없다. 만약 그들이 당신의 스타일에 어울리지 않으면, 결코 일할 수 없을 것이다. 당신은 그들과 일하면서 불필요한, 추가적으로(의식적으로) 사랑이 필요하게 되고, 그들도 마찬가지일 것이다.

20. 일 할때는 악마처럼 싸우지만, 집에 갈 때는 여전히 서로를 사랑하라.

내용이 길어서 이후 번역은 다음에 또 올리겠습니다... ^^
Tags :

로그인/로그아웃/회원 가입



최근에 왼쪽 글에서 보듯이 Olivier Croisier의 트윗을 보고 로그인/로그아웃/가입에 대한 용어에 대해 내가 취했던 방법이나 생각을 곰곰히 해 보게 되었다. 그리고 웹이나 모바일 사이트를 만들면 꼭 들어가는 부분이 인증과 회원 관리이기도 해서 정리해두면 좋을 것 같아서 이 글을 쓴다.

먼저 사례를 살펴보면..

Quora에서 Sign In과 Login In의 차이에 대한 사람들의 생각을 살펴보면 좋은 힌트를 얻을 수 있다. 요약을 해 보면 아래와 같다.
  • 둘의 차이는 거의 없지만 "Sign in - Sign up"보다는 "Log in - Sign up"이 좀 더 이해하기 쉽다.
  • Google, Amazon, Yahoo, Bing, Twitter는 Sign Up/In/Out을 사용하고 Facebook은 Sign Up/Login을 사용하는 사례로 볼 때, Jakob Nielsen은 Sign In/Up/Out을 사용하는게 더 낫다.
  • 원래 메인 프레임 시대에 Log on/Log off가 사용되었고, PC와 인터넷의 발전으로 Sign In/Sign Out이 보다 일반적인 것이 되었다. 그리고 곧 Log In/Log Out으로 변해 갔다. Log나 Sign은 본질적으로 같은 의미이지만, 나에게는 Log는 긱스럽고 Sign은 현대적으로 들린다.

개인적인 생각은..

Log In/Out/Register보다 Sigin In/Out/Up을 주로 사용해 왔다. 이유는 영문의 통일성을 주는 것 같아서 그랬던 것 같다. 그리고 위의 사례에도 비추어봐도 내가 사용한 용어와 크게 차이를 보이진 않는 것 같은데 좀 더 직관적인 관점과 한국적인 관점에서 생각해본다면 Log In/Out/Register이 우리말인 로그인/로그아웃/회원 가입이 외래어이면서도 점 더 직관적으로 쉽게 와 닿는 것 같다. 그래서 Log In/Out/Register를 사용해야겠다는 생각도 해보게 된다.