Google Chrome은 2008년 말에 퍼블릭 베타로 처음 맛을 본 직후 군입대를 했다. 군자원(?)으로 사용한 것까지 치면 Chrome을 주 브라우저로 사용한지 벌써 6년이란 시간이 흘렀다. 개발에서 사용하는 도구 중 IE6 이후로 가장 오래 사용한 것 같다. (IE6는 7+년 사용했다. 암흑의 시간.) Chrome을 사용하는 기간동안 Webkit이 Blink로 전환되거나 표준과 관련된 갈등 등 여러 이슈가 있긴 했지만 최종 사용자 입장에서 큰 차이 없이 지속적으로 사용하고 있다. 물론 정치적 지지 차원공감해서 Mozilla Firefox를 사용하려고 했던 기간도 있었고, Safari로 전환해보려는 시도도 있었지만 번번이 실패하고 다시 Chrome으로 회귀하고 있다.

편리하고 익숙한 Chrome이지만 좋은 컴퓨터에서도 무겁게 느껴질 때도 있는데, 그럴 때마다 다른 브라우저에 대한 경험도 쌓고픈 생각이 자연스럽게 따라오게 된다. 하지만 간단하지 않은 이유 중 가장 큰 부분은 개발자도구다. 이전에도 개발자도구와 관련된 글을 썼었지만, 매번 새롭게 업데이트 된 기능은 항상 똑똑함에 감탄하고 화려함에 감동까지 밀려온다.

예전에 비해 메모리 문제는 개선되고 있긴 하지만, 부지런히 작업하고 있던 Chrome이 뜬금 없이 멈출 때가 있다. 지금 그렇게 굳어버려서 풀리는 것 기다리다가 떠나지 못하는 이유에 대해 한번 정리해보는 것도 좋지 않을까 싶어 떠나지 못하는 이유를 적어본다. 물론 Chrome에만 있는 기능이 아닐 가능성이 높다.

탭 복원 기능

Chrome에서 탭 복원 즉, 닫은 탭을 다시 여는 기능은 정말 강력한데, 종종 크래시로 탭을 날렸다는 이야기를 들으면 의아할 때가 있다. 탭을 잘못 닫았다면 Cmd + Shift + T를 누르면 된다. 창이 아예 닫히더라도 창이 다시 열리면서 그 창에 있던 모든 탭이 복원된다. 이건 크래시가 났어도 동작한다. Safari나 Firefox에도 있는 기능이거나 확장을 설치하면 되긴 하지만 Chrome처럼 크래시로 닫힌 창에 있는 탭까지 열어주는 기능을 찾지 못했다.

다중 프로파일 지원

Google Apps를 회사에서 사용한다면 개인 계정과 회사 계정을 분리된 세션에서 사용할 수 있다. 예전엔 이 기능이 없어서 시크릿(incognito) 모드를 활용했어야 했는데 각각의 계정을 Chrome에 등록하면 마치 별개의 브라우저를 운용하는 것처럼 사용할 수 있다. 브라우저 히스토리 등 모든 브라우저 기능이 각 계정에 맞춰 동작해서 편리하다.

개발자 도구

개발자 도구는 모든 브라우저가 각각 특색이 있어서 좋다 나쁘다를 얘기하긴 어려운 부분이다. 내가 유용하게 사용하는 부분은 다음과 같다. 다른 브라우저에서도 지원하는 기능이지만 Chrome이 익숙해서 그런지 Chrome에서의 동작 방식과 다르면 불편하게 느껴진다. 예전에 정리한 팁과 달라지거나 추가된 부분만 간단히 정리하면 아래와 같다.

  • 개발자 도구 위에 있는 탭 위치도 움직일 수 있게 개선됨 (드래그 하면 위치 변경)
  • Elements 패널에서 엘리먼트를 드래그 앤 드롭으로 위치 및 순서 변경할 수 있음
  • HEX, RGBA로 전환 가능한 컬러 픽커 지원, 값도 확인하기 편함
  • 각 엘리먼트에 대해서 Break on… 으로 하위 노드 또는 어트리뷰트 변경 추적 가능
  • JavaScript Console에서 Cmd + K로 버퍼 즉, 콘솔 내용을 지워줌 (쉘에서 그 기능처럼)
  • 가끔 JavaScript에서 break point를 걸고 싶을 때가 있는데 debugger;를 코드에 추가하면 그 위치에서 break되고 해당 코드를 확인할 수 있음
  • break 되었을 때 console에서 현재 스코프에 따라 값을 확인할 수 있음
  • console에서 객체값에 대해 오른쪽 클릭하면 global variable에 저장할 수 있음
  • 페이지 내에 있는 다른 frame의 console도 열 수 있음 (가끔 iframe에 써먹기 좋음)
  • Sources 탭에서 format 버튼으로 간단하게 minify한 코드도 정렬해서 볼 수 있음
  • Geolocation API 테스트를 위한 에뮬레이터를 내장
  • transition에 대해 animation 미리보기 지원
  • Network 탭에서 쉽게 스로트링 테스트 가능

생각 나는 것만 적었는데 목록이 길어지고 있다. 잔잔하고 편리한 기능이 많다. Chrome의 프로파일링 기능도 엄청 강력한데 이건 따로 정리해볼 생각이다.

다른 브라우저 개발자 도구에서 지원하는 기능 중에 탐나는 기능은 Firefox에서 XHR을 수정해서 전송할 수 있는 기능인데 크롬에서는 XHR을 재전송 하는 것은 가능하지만 XHR의 header를 수정하는 기능 등은 제공하지 않아 조금 아쉽다. (대신 Postman과 같은 도구를 활용하고 있다.)

이런 이유로 Chrome을 쉽게 떠나지 못하고 있다. 한명의 Chrome fanboy로, 올해는 또 어떤 새로운 기능이 나올지 기대도 된다. 한편으로는 다른 브라우저도 좀 더 사용해보고 둘러봐야겠다는 생각도 든다.

Ai Weiwei
호주 생활에서, 아니 내 인생 통틀어 미용실서 자른 머리 중에 워스트 1위에 빛나는 스타일.

2015년 목표를 다시 읽어보며 내년엔 무슨 계획으로 지낼까 고민하다가 먼저 올해는 무슨 일을 어떻게 했는지 기록을 남겨보기로 했다.

2015년에도 자잘하게 많은 일이 있었다. 신상에 있어 가장 큰 변화는 혼자 나와서 살기 시작한 점이다. 혼자 살아본 경험이 전무해서 한동안은 정말 긴장 잔뜩한 상태로 지냈지만 이제 조금씩 적응하고 있는 것 같다. 출퇴근에 시간을 많이 썼었는데 개인 시간을 더 많이 가질 수 있어서 한동안 이사 관련해 꽤 고생했지만 만족스러운 결정이었다. (호주 기준으로는 평범한 수준이라고는 하지만.)

회사에서는 계속 다양한 일을 하고 있다. 계속 PHP로 개발하고 있고, 올해는 Magento로 개발하는 일이 많았다. 회사에서 진행한 몇 자체 프로젝트는 PSR에 따라 개발도 했고 그에 따른 사내 교육도 진행했다. 연초엔 그래도 한산했는데 7월 즈음 유지보수로 들어온 클라이언트로 인해 야근에, 주말에 일하는가 하면, 단기간에 해결 안되는 수많은 이슈로 일감 관리가 전혀 안됐던 탓에 일이 쉽지 않았다. 게다가 시끄럽고 방해가 많은 사무실 환경까지 더하니 주체하기 어려울 정도로 스트레스를 많이 받았다. 흥미가 떨어지는 일에 강한 압박 받으면서 퇴근할 때 오늘 한 일이 한 단어로 정리가 안되는 날이 몇 달 반복 되었었는데 지금 생각해도 끔찍하다. 그 기간동안 정말 좋은 오퍼도 많이 받았는데 비자 탓에 고사할 수 밖에 없어서 더 힘들었던 것 같다. 그나마 지금은 진정세로 돌아서긴 했지만 여전히 기억하기 싫다. 그 기간 경험에서 내년엔 내 커리어를 어떻게 갖고 가야하나 고민을 자연스럽게 하게 되었다.

지난 해를 대비해서 생각하면 한 달 이상 시간을 쓰는 일에는 그닥 열심히 노력하지 못했던 것 같다. 꾸준하기보다 회사 일이 지친다는 이유로 그때그때 흥미있는 일에만 몰두하다보니 자잘하게 공부하고 했던 일은 많은데 그렇다고 배운 것으로 무언가 만드는 일이 없었던 점이 가장 후회된다. 연중에 코세라와 eDx에 참여했는데 결국 초반 몇 강의만 듣고 더이상 진도를 내지 못했던 것도 아쉽다. 올해 가장 관심 많았던 부분은 함수형 프로그래밍이었다.(@devthewild님의 영향이 크다.) 아직도 신기하고 재미있어서 내년에도 관련된 공부를 더 하고 싶다.

영어는 엄청 나아졌다고 스스로 생각했지만 최근 IELTS를 본 결과로는 정말 근소하게 나아졌다. 그나마 시험 준비하는 기간에 공부를 조금 한게 전부였는데 좀 더 시간을 투자하지 않은게 아쉽다. 그래도 올해는 클라이언트랑 직접적으로 커뮤니케이션 하는 일이 엄청 늘었는데 전화로 말하고 메일 쓰는 일이 엄청 늘어서 영어 실력은 늘지 않았어도 자신감은 조금 늘어난 것 같다. (물론 클라이언트랑 직접 커뮤니케이션 하는건 그닥 좋은 일이 아니지만.)

그래도 꾸준히 했던 일은 짧게라도 읽은 글이나 살펴본 내용은 블로그에 기록했다는 점이다. 직접 쓴 글보다 번역글이 더 많았지만 올해 총 76개 포스트를 남겼다. 연초 목표에 적어두진 않았어도 100개 정도를 목표로 잡았는데 한동안 미드 본다고, 그리고 이사 간다고 글을 뜸하게 썼던 기간이 있어서 달성하지 못했던 것 같다. 올해는 한참 전에 썼던 글이 갑자기 공유되어 평소같지 않았던 조회수도 경험해봤다. 연초 한국에서 작성한 IT 개발자, 호주에서 일하기를 비롯해서 라즈베리 파이 2를 구입한 이야기와 같은 글도 많이 읽히고 있고, 커밋 메시지에 관한 글어떻게 학술 논문을 읽어야 하는가 같은 번역글도 많이 공유되었고, 다양한 의견을 살펴볼 수 있던 기억이 난다. 내년에는 번역도 꾸준히 하지만 내 글도 더 많이 작성해야겠다는 생각을 했다.

이상한모임 활동도 계속 참여했다. 시즌2를 조직하면서 커뮤니티를 위한 개발을 몇가지 할 예정이었지만 내 역할을 제대로 못해 특히 아쉽다. 그리고 올해에는 크고 작은 행사를 매번 꾸리는데 고생하는 분들께 미안한 감정이 늘 가득했다. 작년과 비교하면 그저 슬랙에서 웃고 떠드는 일, 리트윗 하는 일 외에는 없었지 않았나 하는 생각도 든다. 실질적인 커뮤니티를 꾸리는데 온라인에서만 활동하는 것이 심적으로나 실질적으로나 그닥 좋은 방법이 아닌 것 같다. 내 욕심이 지나치게 많아서 그런 것인지 어쩐지 몰라도 내가 어떤 역할로 이 커뮤니티에 계속 참여할 수 있을지 고민이 된다.

6월부터 제이펍 베타리더스에 참여하게 되었다. 올해 중반부터 한국어로 된 책을 부지런히 읽고 있고 지금까지 6권 참여했다. 어려워서 제대로 읽지 못한 책도 있었지만, 책을 읽고 오탈자를 찾고 어색한 내용에 메모하는 과정이 새롭고 재미있었다. 베타리더로서 남긴 후기가 책에 함께 들어간다는 것은 신선한 경험이었고 앞으로도 읽을 책이 많이 기대된다.

올해도 건강관리를 그닥 열심히 하진 않았지만 이사한 이후로 아침, 점심, 저녁 음식을 직접 챙겨 먹다보니 식사량을 조절하기 시작했고 결과적으로 살을 많이 뺄 수 있었다. 직접적인 운동은 많이 하지 못했고, 자전거 출퇴근은 아직도 미루고 있다. 내년엔 둘 중 하나는 습관으로 만들어서 운동량을 늘려야겠다.


2015년에는 제대로 이룬 것이 없는 기분이지만 그래도 완전 절망스러운 결과는 아니구나 생각이 든다. 내년엔 조금 더 세세한 계획을 갖고 더 부지런히 도전하며 지내야겠다.

조은님과 강성진님의 포스트를 읽고 번역에 관한 회고를 간략하게나마 남긴다. 전문적으로 하는 번역은 아니였지만, 생각보다 많은 시간을 들인 일 중 하나였고, 그 결과로 올해 작성한 블로그 포스트 대부분이 번역글로 채워졌다. 원문의 길이도 다양했고 그 분야도 다양한 편이였는데 읽고 나서 유익하다 싶었던 글은 대부분 번역했던 것 같다.

올해 번역을 유독 많이 한 것은 여러 가지 이유가 있었다. 올해 초에 썼던 목표대로 경험을 공유한다는 생각으로 짧게라도 읽는 글을 정리한다는 느낌을 갖고 시작했다. 모국어로 사유를 확장할 수 있는 컨텐츠가 적다는 것은 슬픈 일이다. 한국어 구사자는, 특히 개발자라면 최신 정보를 알기 위해 사소한 글이라도 영어를 읽어야만 하는 상황에 자연스레 놓이게 된다. 그래서 내가 사소하게 읽는 글이라도 간단하게 국문으로 옮겨두면 나도 이해하는데 도움이 되고, 이 글이 필요한 다른 사람도 도움이 되지 않나 하는 생각을 하게 되었다. 또한 호주에서 지내며 영어 공부한다는 핑계로, 그리고 한국어로 긴 글을 별로 쓸 일이 없어 문장이 많이 서툴어지고 있던 점도 있어서 겸사겸사 번역에 시간을 쓰게 되었다. 영어도, 한국어도 잘 못하지만 처음부터 잘하는 사람 없다는 생각으로 무작정 시작했다.

몇 번역을 제외하고는 대부분 짧은 글이였다. 처음엔 짧은 글도 번역에 꽤 오랜 시간이 걸렸다. 그래도 반복적으로 하다보니 비슷한 표현도 많이 나오고, 내용도 쉬운 글을 위주로 번역했기 때문에 점점 번역에 걸리는 시간이 줄어드는 경험을 할 수 있었다. 짧은 글은 지치지 않고 끝낼 수 있어 자연스럽게 성취감과 자신감도 따라왔다. 공유에 따라 피드백도 바로바로 받을 수 있어서 꾸준하게 할 수 있는 좋은 동력이 되었다.

명세와 같이 중요한 문서나 깊은 통찰이 있는 글을 번역하는 일은 분명 멋진 작업이지만 별로 많이 하지 못했다. 아직 분량이 많아지면 겁이 나기도 하고 “공식적인” 느낌의 글을 옮기는 것은 묘하게 부담이 느껴진다. 그래도 짧은 글에서 점점 긴 글로, 더 깊이 있는 내용을 다루는 글을 번역해 점점 근육을 키워가는 것을 목표로 했고, 예전에 비해 글을 번역해야지 하는데 고민이 많이 줄었다는 점 등 그 목표를 잘 따른 한 해라고 생각한다.

좋은 번역이었나에 대해서는 답을 하기 어렵다. 시간을 들여 좋은 품질로 번역하는 것보다는 그냥 글을 읽는 것처럼 번역해 그 시간을 줄이는 것을 더 고려했었다. 또한 직역보다는 내가 이해하는 범위 내에서 의역을 많이 했다. 용어를 선택하는데 있어서 지나친 초월 번역이 되지 않도록 노력했고 트위터나 슬랙을 통해서 물어봤고, 또 그런 과정에서 많이 배울 수 있었다. 내년엔 필요할 때만 물어서 용어를 찾는 것이 아니라 용어집을 만들어 정리하고 번역 원칙에 따르는 것도 꼭 해봐야겠다. 또한 번역하는 과정에서 영어나 한국어나 실력이 평범한 수준이라 아쉬웠던 적이 한 두 번이 아니다. 내년에는 번역의 질이 높아질 수 있도록 시간도 충분히 투자하고, 또한 영어, 한국어 수준이 높아질 수 있도록 노력해야겠다.

가장 철저하게 따른 원칙은 저작권 준수다. 저작권이 명시되어 있다면 저작권에 따라 병기했고 따로 명시되지 않았다면 꼭 저자에게 메일로 문의해 명확한 허락을 받고 번역, 게시했다. 단 한 번도 이 원칙을 따르지 않은 경우가 없었는데 단 한 명도 안된다고 이야기 한 적이 없었으며 오히려 고맙다는 피드백도 받을 수 있었다. 메일을 주고 받는 과정에서도 저자와 다양한 이야기를 나눌 수 있어서 좋았고 콜드 메일을 보내는데 자신감도 생겼다.

내가 맨 처음 번역글을 작성할 때 찾았던 초보 번역자들에게 보내는 몇 가지 조언은 매번 번역에 어려움이 있거나 지침이 필요할 때, 거친 얘기를 들어서(번역이 왜 이렇게 구리냐, 이런 유치한 것도 번역하냐 등등) 마음이 힘들 때마다 꺼내 읽는 글이다. 특히 비웃는 자를 비웃어 주라는 이야기가 특히 힘이 되었다. 😀

누군가 이 글을 보고 새해 목표 목록에 번역하기를 추가한다면 참 기분 좋을 것 같다. 내년에도 좋은 글 많이 마주하고 번역할 수 있었으면 좋겠다.

더 읽을 거리

Todd Motto의 글 Directive to Directive communication with “require”를 번역한 글이다. 짧은 글이지만 디렉티브의 계층 관계에서 require를 활용해 값을 주고 받는 방법을 살펴볼 수 있다. 다른 디렉티브의 컨트롤러에 정의된 메소드를 어떻게 접근해서 사용할 수 있는지 탭 디렉티브를 작성하는 예제를 통해 설명한다.


디렉티브 간 소통은 여러 방법이 있지만, 계층 관계를 갖고 있는 디렉티브를 다룰 때는 디렉티브 컨트롤러를 활용해 서로 소통할 수 있다.

이 글에서는 탭 디랙티브를 작성한다. 탭을 추가하기 위한 다른 디랙티브의 함수를 활용하며, 디렉티브 정의 개체의 require 프로퍼티를 사용해 만들려고 한다.

HTML을 먼저 정의한다:

<tabs>
  <tab label="Tab 1">
    Tab 1 contents!
   </tab>
   <tab label="Tab 2">
    Tab 2 contents!
   </tab>
   <tab label="Tab 3">
    Tab 3 contents!
   </tab>
</tabs>

이 시점에서 tabstab 두 디렉티브를 만들 것을 예상할 수 있다. tabs를 먼저 만들면:

function tabs() {
  return {
    restrict: 'E',
    scope: {},
    transclude: true,
    controller: function () {
      this.tabs = [];
    },
    controllerAs: 'tabs',
    template: `
      <div class="tabs">
        <ul class="tabs__list"></ul>
        <div class="tabs__content" ng-transclude></div>
      </div>
    `
  };
}

angular
  .module('app', [])
  .directive('tabs', tabs);

tabs 디렉티브에서는 transclude를 사용해 각각의 tab을 전달하고 개별적으로 관리하도록 구성하고 있다.

tabs 컨트롤러 내에서 새로운 탭을 추가할 때 사용할 함수가 필요하다. 이 함수를 사용해 부모/호스트 디렉티브에 동적으로 탭을 추가할 수 있게 된다:

function tabs() {
  return {
    ...
    controller: function () {
      this.tabs = [];
      this.addTab = function addTab(tab) {
        this.tabs.push(tab);
      };
    },
    ...
  };
}

angular
  .module('app', [])
  .directive('tabs', tabs);

이제 컨트롤러에 addTab 메소드가 연결되었다. 하지만 탭을 어떻게 추가할 것인가? 자식 tab 디렉티브를 추가하고, 이 디렉티브가 컨트롤러의 기능으로서 필요로 한다:

function tab() {
  return {
    restrict: 'E',
    scope: {
      label: '@'
    },
    require: '^tabs',
    transclude: true,
    template: `
      <div class="tabs__content" ng-if="tab.selected">
        <div ng-transclude></div>
      </div>
    `,
    link: function ($scope, $element, $attrs) {

    }
  };
}

angular
  .module('app', [])
  .directive('tab', tab)
  .directive('tabs', tabs);

require: '^tabs'를 추가하는 것으로 부모로 tabs 디렉티브의 컨트롤러에 포함했으며 이제 link 함수를 통해 접근할 수 있게 되었다. link 함수의 4번째 인자인 $ctrl을 주입해서 작성한 컨트롤러의 참조를 받아오자:

function tab() {
  return {
    ...
    link: function ($scope, $element, $attrs, $ctrl) {

    }
  };
}

여기서 console.log($ctrl);을 넣어보면 다음과 비슷한 객체를 볼 수 있다:

{
  tabs: Array,
  addTab: function addTab(tab)
}

addTab 함수를 활용해서 새로운 탭을 생성할 때, 부모 디렉티브의 컨트롤러로 정보를 보낼 수 있게 되었다:

function tab() {
  return {
    ...
    link: function ($scope, $element, $attrs, $ctrl) {
      $scope.tab = {
        label: $scope.label,
        selected: false
      };
      $ctrl.addTab($scope.tab);
    }
  };
}

이제 새로운 tab 디렉티브를 사용할 때마다 이 $ctrl.addTab 함수를 호출하고 tabs 컨트롤러 내에 있는 this.tabs 배열에 디렉티브 정보를 전달한다.

3개의 탭이 존재한다면 $ctrl.addTab 함수가 3번 호출 될 것이고 배열은 3개의 값을 갖고 있게 된다. 그 후 배열을 반복해서 살펴보고 제목과 선택되어 있는 탭이 있는지 확인한다:

function tabs() {
  return {
    restrict: 'E',
    scope: {},
    transclude: true,
    controller: function () {
      this.tabs = [];
      this.addTab = function addTab(tab) {
        this.tabs.push(tab);
      };
      this.selectTab = function selectTab(index) {
        for (var i = 0; i < this.tabs.length; i++) {
          this.tabs[i].selected = false;
        }
        this.tabs[index].selected = true;
      };
    },
    controllerAs: 'tabs',
    template: `
      <div class="tabs">
        <ul class="tabs__list">
          <li ng-repeat="tab in tabs.tabs">
            <a href="" ng-bind="tab.label" ng-click="tabs.selectTab($index);"></a>
          </li>
        </ul>
        <div class="tabs__content" ng-transclude></div>
      </div>
    `
  };
}

selectTabtabs 컨트롤러에 추가된 것을 확인할 수 있을 것이다. 이 함수는 특정 탭의 컨텐츠를 보여주기 위해 초기 색인을 지정할 수 있게 한다. this.selectTab(0);를 호출하면 작성한 코드에 따라 배열의 인덱스를 확인해 첫번째 탭의 컨텐츠를 표시하게 된다.

Angular의 컴파일링 과정에 따라 controller는 가장 먼저 인스턴스가 생성되고, link 함수는 디렉티브가 컴파일되고 엘리먼트에 연결될 때 한 번 호출된다. 즉, 초기화 된 탭을 볼 수 있을 때, 디렉티브 컨트롤러를 $ctrl와 그 메소드를 사용하기 위해 주입되어야 한다:

function tabs() {
  return {
    ...
    link: function ($scope, $element, $attrs, $ctrl) {
      // 첫번째 탭을 가장 먼저 보여줌
      $ctrl.selectTab(0);
    },
  };
}

하지만 다음처럼 어트리뷰트로 초기 탭을 지정할 수 있다면, 개발자에게 더 많은 선택권을 제공할 수 있다:

<tabs active="2">
  <tab>...</tab>
  <tab>...</tab>
  <tab>...</tab>
</tabs>

이 코드는 배열 인덱스를 동적으로 2로 지정하며 배열에서 3번째 엘리먼트를 보여주게 된다. link 함수에서는 어트리뷰트의 존재를 $attrs가 포함하고 있는데 이 인덱스를 바로 지정하거나 $attrs.active가 존재하지 않거나 잘못된 값일 경우 (false으로 평가되니 어쨌든 이므로 안전하겠지만) 초기 인덱스를 다음처럼 폴백(fallback)으로 지정할 수 있다.

function tabs() {
  return {
    ...
    link: function ($scope, $element, $attrs, $ctrl) {
      // `active` 탭 또는 첫번째 탭을 지정
      $ctrl.selectTab($attrs.active || 0);
    },
  };
}

그리고 require를 이용해 새로운 tab 정보를 부모 디렉티브에 전달하는 라이브 데모는 아래에서 확인할 수 있다:

{{< //jsfiddle.net/toddmotto/4comjcdm/embedded/result,js,html >}}

PHP는 언어적인 지원은 물론, 환경이나 커뮤니티도 계속 발전하고 있다. 최근 프레임워크 운용 그룹(Framework Interop Group, FIG)에서 제안하는 PSR 문서를 보면 알 수 있듯, 표준화된 라이브러리를 만들기 위해 라이브러리/패키지 개발에 대한 합의도 활발하게 진행되고 있어서 예전 그 난장판이던 분위기와는 사뭇 다르다. PSR에서 다뤄지는 내용은 미래에 사용할 기능이 아니라 지금 현재 PHP에서 당장 활용할 수 있는 기술이다. 더이상 미룰 수 없고, 미뤄서도 안된다는 이야기다.

구석기 PHP는 농담 짙은 표현이지만 이젠 마치 과거의 유물과도 같은 코드에 그만 집착하고 현대에 맞는 코드를 작성했으면 한다. 이 글은 예전 방식으로 PHP를 개발하고 있다면 자주 접했을 만 한 문제를 정리하고 있다. 이 글에서 PSR의 내용을 직접적으로 다루지는 않지만, PSR를 준수하는 것으로 여기서 말하는 현대적인 개발과 과거의 PHP 개발은 어떤 부분이 다르고, 어떻게 편리한지 확인해보자.

구석기 PHP

함수, 변수, 클래스의 전역적인 공해

파일을 불러오고 나면 각각의 파일이 갖고 있던 경계(스코프)가 전역으로 확장되고 어디서나 사용할 수 있는 함수, 변수, 클래스가 만들어지게 된다.

<?php 
// lib/haruair/function.php
function HelloWorld() {
  return "HelloWorld";
}
?>
<?php 
// lib/wordpress/function.php
function HelloWorld() {
  return "HelloWorld. I'm wordpress btw.";
}
?>
<?php
// app.php
include_once('./lib/haruair/helloWorld.php');
include_once('./lib/wordpress/helloWorld.php');

// PHP Fatal error:  Cannot redeclare HelloWorld()
?>

같은 함수명이나 클래스명을 사용하면 다시 선언할 수 없는 문제가 발생한다. 특히 워드프레스 개발을 하다보면 플러그인 내에 동일한 함수나 클래스명을 사용하고 있어 이런 문제가 발생하는 경우가 자주 있다. 대부분 function_exists()와 같은 함수를 이용해 미리 확인하고 선언하는 방식으로 처리되어 있는데 여전히 좋은 방법은 아니다.

그래도 함수나 클래스의 이름이 중복되는 경우에는 문제가 있는걸 바로 자각할 수 있지만, 동일한 명칭의 전역 변수가 있고 각각의 파일에서 그 변수를 활용하고 있다면 그 누구도 결과를 예상할 수 없게 된다. 이런 경우는 어느 하나의 이름을 모두 변경해야 하거나 언제 발생할지 모르는 에러를 감내해야 한다.

기본으로 모든 파일 로드하기

이전 방식의 개발에서는 다음과 같은 lib.php를 만들어 의존성을 갖는 모든 파일을 불러와 활용하는 경향이 있다. 이 파일 하나를 불러오면 각각 파일의 함수, 변수, 클래스를 모두 불러와서 사용할 수 있게 된다.

<?php
// lib.php
include_once('./lib/A/Orders.php');
include_once('./lib/B/Account.php');
include_once('./lib/B/AccountManagement.php');
include_once('./lib/B/AccountSomething.php');
include_once('./lib/C/Report.php');
include_once('./lib/E/Admin.php');
?>

이런 개발 방식은 오랫동안 큰 대안 없이 활용되고 있고, 지금까지도 많은 코드에서 발견되는 방식이다. 각각 필요에 따라 include하는 경우도 있지만, 각각의 파일끼리도 의존성이 있는 경우도 많기 때문에 하나의 파일에서 모두 불러오는 형태로 많이 사용한다. 다음 코드를 살펴보자.

<?php
// some-page.php
include_once( BASE_DIR . '/lib.php');

function HelloWorld() {
  return speak("Hello World");
}

// `speak()` 함수가 어느 php 파일에서 나온지 알 수 없다.
?>

IDE를 활용하면 쉽게 speak()가 선언된 부분을 찾을 수 있겠지만 코드만 봐서는 이 speak() 함수가 어디에서 나온 함수인지 알 수 없다. 이런 문제로 인해 대다수의 프레임워크나 CMS에서는 함수명에 접두사를 붙여 사용하는 등의 방식으로 해결했지만 함수를 호출할 때마다 접두사를 붙여 호출하는 일은 누가봐도 지저분한 일이다.

특히 이런 방식으로 개발된 코드는 함수와 실제 로직과의 결합도가 높아서 코드를 재활용하기도 어렵다. 그 결합도를 낮추기 위한 시도로 변수에 함수명을 넣고 실행하는 방법도 활용되지만 여전히 코드가 장황하고 지저분해지는 경향이 크다.

코딩 스타일의 차이

함께 개발하는 개발자가 모두 동일한 코딩 스타일을 준수하는 것은 중요하다. 때로는 공개된 라이브러리를 활용하게 되는 경우도 있는데 이런 라이브러리가 동일한 컨벤션을 준수하고 있지 않는다면 자연스럽게 불편함을 겪게 된다.

<?php
include_once('./some_pdf_gen/lib.php');
include_once('./someCalculatorLibrary/content/library/cal.php');

include_once('./my/lib.php');

$orders = array(
  new My_Product(112, 2.5, 2),
  new My_Product(2303, 30, 1),
  new My_Product(4923, 30, 2)
);

$pdf = new AcmeSomePdfGen();
$calculation = new some_calculation($orders);
$total = $calculation->get_total_price();

$pdf->setTemplate("<div>Total: $total</div>");
$pdf->download();
?>

인위적으로 만든 예시지만 충분히 있을 법한 코드다. 일관성이 없는 코드는 개발자에게 고스란히 스트레스로 돌아온다.


현대인의 PHP

이제 앞서 본 코드와 어떻게 다른지 살펴볼 것이다. PHP 5.3 이후로 사용할 수 있게 된 namespace와 autoloader를 활용하는 것으로 지저분한 문제를 대부분 해결할 수 있다. 이 중요한 두 가지 기능을 사용하는데 있어 어떻게 사용하고 활용하는지 PSR 문서로 정리되어 있다. PSR을 모두 설명하고 있지 않지만 어떤 방식으로 문제를 해결하는지 확인하자.

namespace 활용하기

네임스페이스를 다음과 같이 선언하는 것으로 클래스를 네임스페이스 아래로 배정할 수 있게 된다.

<?php // lib/haruair/helloWorld.php
namespace Haruair;
class HelloWorld {
  function say() {
    return "HelloWorld";
  }
}
?>
<?php // lib/wordpress/helloWorld.php
namespace WordPress;
class HelloWorld {
  function say() {
    return "HelloWorld. I'm wordpress btw.";
  }
}
?>

이러면 다음 코드와 같이 한 파일에서 사용하는데 전혀 문제가 없다.

<?php // app.php
include_once('./lib/haruair/helloWorld.php');
include_once('./lib/wordpress/helloWorld.php');

$haruair = new Haruair\HelloWorld();
$wordpress = new WordPress\HelloWorld();

echo $haruair->say(); // HelloWorld
echo $wordpress->say(); // HelloWorld. I'm wordpress btw.
?>

네임스페이스를 활용하면 Haruair\Order\ProductHaruair\Cart\Product가 동일한 Product라는 이름의 클래스라도 하나의 파일에서 두 클래스 모두 처리할 수 있게 된다.

autoloader 활용하기

php에서 미리 선언한 함수나 클래스를 사용하려면 당연하게 includerequire 같은 내장 함수를 활용했어야 했다. 하지만 spl_autoload_register 함수를 선언하면 파일을 필요로 할 때 불러오는 방식으로 구현할 수 있다. 다음 코드를 보자.

<?php
include_once('./src/haruair/event/ticket.php');
include_once('./src/haruair/event/attendee.php');
include_once('./src/haruair/event/coupon.php');

$ticket = new Haruair\Event\Ticket;
$attendee = new Haruair\Event\Attendee;
$coupon = new Haruair\Event\Coupon;
?>

이제 직접 include 하는 것이 아니라 autoloader를 활용해서 불러오도록 한다.

<?php
spl_autoload_register(function ($class) {

    // 프로젝트에 따른 네임스페이스 프리픽스
    $prefix = '';

    // 네임스페이스 프리픽스를 위한 기본 디렉토리 경로
    $base_dir = __DIR__ . '/src/';

    // 네임스페이스 프리픽스에 해당하는지 검사
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        // 찾지 못했으므로 반환. 만약 다른 오토로드 함수가 등록되어 있다면 순차적으로 실행함.
        return;
    }

    // 네임스페이스를 제외한 상대 클래스명 찾기
    $relative_class = substr($class, $len);

    // 네임스페이스 프리픽스를 기본 디렉토리 경로로 치환, 네임스페이스 구분자를 디렉토리 구분자로
    // 전환하고 .php를 끝에 추가함
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    // 파일이 존재하면 불러옴
    if (file_exists($file)) {
        require $file;
    }
});

$ticket = new Haruair\Event\Ticket;
$attendee = new Haruair\Event\Attendee;
$coupon = new Haruair\Event\Coupon;
// autoloader를 호출한다.
?>

$ticketnew Haruair\Event\Ticket이 할당될 때, “Haruair\Event\Ticket” 문자열을 인자로 받는 spl_autoload_register 함수가 실행이 된다. 그래서 함수 내 정의된 방식대로 해당 문자열을 처리해 파일을 불러오게 된다. 이 예제 함수에서는 소문자로 전환한 후, 각각의 네임스페이스를 디렉토리 구조로 변환하고 끝에 .php를 붙여 해당 파일을 불러오는 식으로 작성되어 있다.

여기서 사용된 함수는 약식 구현이고 모두가 공용으로 사용할 수 있도록 PSR에서 PSR-0, PSR-4로 표준화된 문서를 제공하고 있다. Composer를 사용한다면 더 간편하게 활용할 수 있다. PSR-4의 구현 예시도 참고하자.

코딩 스타일 일치

PSR-1 Basic Coding StandardPSR-2 Coding Style Guide를 통해 표준적인 문법을 문서화하고 있다. 이 두 문서를 따라 개발하면 자연스럽게 앞서 다룬 네임스페이스와 autoload를 활용할 수 있게 된다. 특히 composer로 내려받을 수 있는 패키지는 이 두 문서를 준수할 것을 권장하고 있으므로 새로운 코드나 라이브러리, 패키지를 추가하더라도 일관적인 코딩 스타일을 유지하는데 도움이 될 것이다.


얕은 수준에서 비교한 글이지만 여기서 다룬 기능은 지금 당장에라도 활용할 수 있는 방법이다. 이 기법을 활용하는 것은 단순히 몇가지 기술을 배우는 것에 그치지 않고 더 나은 개발 기법을 학습하는데 도움이 된다. 아직 PSR이나 composer와 같은 도구가 생소하다면 이 글을 읽고서 꼭 살펴봤으면 좋겠다.

더 읽을 거리

여전히 PHP가 천덕꾸러기라고 생각하는 사람도 많다. 하지만 다른 언어에서만 볼 수 있었던 좋은 도구와 라이브러리, 의존성 관리도 지원하기 시작했고, PSR을 기준으로 표준도 활발하게 논의되고 있어 예전의 PHP 개발과는 확실히 분위기가 다르다. 한국 내 커뮤니티에서 laravel, symfony와 같은 프레임워크를 쓰는 경우나 XE와 같이 이런 프레임워크를 기반으로 개발한 웹어플리케이션이 보이기 시작했지만 여전히 대부분 “Classic” PHP로 개발하고 있는 것은 분명 아쉬운 부분이다. 좋은 도구가 있는데도 아무도 활용하지 않는다면, 그럴수도 있지 하고 지나치기엔 너무나도 슬픈 일이다. 좋은 기능을 도입하지 않는 사람들이 바로 PHP를 천덕꾸러기로 방치하고 있는 사람들이다.

Classic PHP의 모습

PHP를 사용해 예전 방식으로만 개발하는데 수많은 이유가 있을 수 있다. 물론 여기서 얘기하려는 편리한 새 기능이 내년, 혹은 그 후에 추가될 기능이라면 관심을 뒤로 미뤄도 할 말이 없다. 하지만 autoloadnamespace 문법, composer를 사용하는 것 등은 지금 당장 사용할 수 있는 것이기 때문에 더이상 미룰 수 없고 또한 미뤄서는 안된다. 최신 기술이 아니라 이미 널리 사용되고 있고, 이제는 사용하지 않으면 안되는 기술이다. 지금 배워서 지금 사용해야 한다. 만약 지금 안쓰고 있다면 당신만 안쓰고 있는 것이다. 회사에서 사용하지 않고 있다면 먼저 배워서 알려줘라. 그만큼 중요하다.

그래서 PHP 개발자라면 2016년에는 놓치지 말고 해야 할 것들을 정리하려고 한다. 여기서 다루는 PHP 이야기는 먼 미래의 꿈이 아니라 현재 사용 가능하며, 또 해야만 하는 것들에 대한 이야기다. PHP 개발을 하고 있는데도 이 내용 중 하나라도 놓치고 있는게 있다면 꼭 알아보고 2016년엔 꼭 써먹어야 한다. 글 내내 중요하다는 이야기를 반복해서 하는 것은 정말 중요하기 때문이다. 그리고 이 글에서는 깊은 이야기를 다루진 않고 피상적인 부분만을 정말 간단하게 이야기하려고 노력했다. 이 포스트에 걸려있는 링크와 키워드로 더 깊은 내용을 찾아봤으면 좋겠다.

PHP 업그레이드 하기

PHP는 6 버전을 건너뛰고 7.0을 출시해서 현재 7.0이 최신 버전이다. PHP의 버전은 지속적으로 지원 패치를 제공하는 버전이 있고 보안 문제에 대해서만 패치를 제공하는 버전이 있다. PHP 버전 지원 페이지에서 지원 상황을 확인할 수 있다.

지금 사용하고 있는 PHP의 버전은 몇 버전인지 확인하자. 5.4를 사용하고 있다면 2015년 10월 이후로 보안 패치도 제공되지 않는, 유통기한 지난 버전을 사용하고 있는 것이다. 유통기한 지난 우유를 계속 마실 것인가? 만약 지금 사용하는 버전이 5.3이라면 이미 당신의 웹사이트는 그 누구도 안전하다고 말할 수 없다. 5.5 버전도 앞으로 6개월 후, 즉 2016년 7월이면 보안패치를 제공하지 않는다. 지금 적어도 7.0, 최소한 5.5로 변경해야 한다. 만약 레거시로 인해 업데이트 이후 문제가 발생한다고 방치하고 있다면, 사실 그 사이트는 이미 위험한 웹사이트다. 언제, 어느 순간에 DDoS 공격에 활용될 지 아무도 모른다. 악성코드 배포처로 활용되거나, 최악의 경우 내부의 데이터를 볼모로 협박 메일을 받을지도 모른다.

0순위가 되어야 할 보안 문제에도 의사결정권자가 마음을 움직이지 않고 오래된 버전을 고수한다면 속도가 더 빠르다는 점을 강조하자. 새버전의 PHP는 구버전에 비해 속도도 점점 빨라지고 메모리 사용량은 점점 줄어들고 있다. 5.6도 과거 버전에 비해 많이 빨라진 속도를 보여줬지만 7.0은 더 빨라졌다.

버전을 올리기만 하면 더 좋은 기능을 쓸 수 있는 것은 물론, 속도가 빨라지고 보안성이 높아진다. 이 단순한 일을 하지 않는건 게으름 외에는 답이 없다. 레거시가 걱정이라면 changelog를 찾아보고, 최소한 테스트라도 해보자. 서버호스팅을 사용하고 있다면 상위 버전의 PHP를 설치하고 웹호스팅을 사용하고 있다면 호스팅 업체에 문의하자. 아직도 5.3만 지원하는 호스팅이라면 당장 옮겨야 당신의 웹사이트가 안전하다.

Composer 사용하기

Composer는 PHP를 위한 의존성 관리 도구다. Python에서 pip, nodeJS에서 npm, Ruby에서 bundle, .Net에서 Nuget을 사용해본 적이 있다면 바로 그 역할을 하는 도구다. 리눅스를 사용해본 경험이 있다면, 필요한 도구를 “어디선가” 내려받는 apt-get이나 yum 같은 명령어를 최소한 복사-붙여넣기로 사용해봤을 것이다.

위로 든 예를 단 하나라도 써보지 않아 무슨 말을 하는지 모르겠다면, 내가 필요로 하는 기능의 PHP 라이브러리나 패키지를 스마트폰 앱스토어 같은 곳에서 다운로드 받는다고 생각해보자. 각각의 기능을 다운로드 받아 원하는 기능만 조합하는 방법으로 웹사이트, 웹서비스를 개발할 수 있다.

PHP 웹사이트에서 이메일을 보낼 때 mail() 함수로 보내고 있다면, 매번 지저분한 header를 직접 작성하고, HTML을 직접 변수에 넣어 보내는 지저분한 일을 해본 경험이 있을 것이다. 거기에 첨부파일도 넣어 보내본 경험이 있다면 얼마나 쉽게 난장판이 되는지 알 수 있다. composer를 사용한다면 이런 문제를 깔끔하게 해결할 수 있는 멋진 PHP 패키지를 설치해서 활용할 수 있다. nette/mail로 메일을 쉽게 구성하고, league/plates와 같은 깔끔한 템플릿 엔진을 단 한 줄의 설치 명령어로 바로 사용할 수 있게 된다.

Composer 로고

composer를 사용하라고 하는 이유는 단순히 이 도구를 사용하는 과정을 배우는 것으로도 더 나은 개발을 시작할 수 있는 좋은 출발점이 되기 때문이다. composer를 제대로 사용하기 위해서는 기본적으로 namespaceautoload에 대해 이해해야 한다. 더 나아가 객체지향과 같은 개발 페러다임을 이해하는데 좋은 시작점이 되고 의존성을 어떻게 관리하는지, 테스트를 어떻게 수행해야 하는지 등 현대적인 개발에 있어 필수적인 부분을 학습하는데 중요하다. 최근 작성되는 PHP와 관련된 글을 보면 기본적으로 composer를 사용하는 것으로 가정하고 작성되기 때문에 PHP 개발자에게 있어서 필수적으로 배워야 할 도구다.

PSR 준수하기

PHP 난개발로 인해 가장 고통 받았던 사람들은 다름 아닌 PHP 프레임워크/라이브러리 개발자다. 범용적인 기능으로 만들어도 자신의 라이브러리에서만 사용할 수 밖에 없던 이유는 공통된 규칙이 없기 때문이었다. 모두 각자의 방식대로 만드는게 일상이었던 PHP 환경에서, 프레임워크나 라이브러리를 만들던 사람들이 모여 프레임워크 운용 그룹(Framework Interop Group, FIG)을 만들었고, PHP의 표준적인 개발을 위한 PSR 문서를 만들었다.

PSR 문서는 PHP-FIG에서 확인할 수 있다. autoload, 인터페이스의 사용, 코딩 스타일 등 현재 수락된 문서와 진행중인 문서를 확인할 수 있다. 이 문서에서 제안하는 규칙을 따르는 것으로 같은 스타일의 코드를 유지하는데 도움이 된다. PHP 개발자를 채용할 때, “우리는 PSR을 준수해서 개발하고 있습니다.” 라는 한 마디로 어떤 스타일을 따르는지 설명할 수 있는 것이다.

앞서 언급한 composer도 PSR을 준수해서 만든 도구다. PSR에서 제시하는 방식대로 코드를 작성한다면 composer에서 다른 개발자가 작성한 코드를 내려받아 사용하는 것과 같이 당신의 라이브러리도 누구나 쉽게 사용할 수 있다. 모두에게 공개된 packagist는 서버 코드 또한 공개되어 있어서 사내 전용 packagist를 구성해 사용할 수도 있다. 이 모든 일이 PSR을 준수하고 composer를 사용하는 것으로 가능하다.

보너스: 현대적인 개발 패러다임 학습하기

PSR과 composer가 일궈놓은 환경은 이전까지 활용하기 어려웠던 디자인 패턴이나 개발 패러다임을 PHP에서 사용하도록 하는데 큰 도움을 주고 있다. Factory, Strategy와 같은 디자인 패턴의 활용, 단위 테스트나 행위 주도 테스트를 통한 개발, 서비스 코드 간의 의존적인 환경을 줄이기 위한 의존성 주입이나 ORM과 같은 데이터베이스 추상화 등은 더이상 다른 멋진 언어에서만 존재하는 것이 아니라 PHP에서도 현재 가능한 이야기다.

지금까지 대부분의 프레임워크는 자신들의 코드에 맞게 작성한, 그 프레임워크를 사용하지 않고서는 사용할 수 없는 코드만 제공해왔다면, 현대적인 PHP 개발에서는 누구든 쉽게 필요에 따라 꺼내서 쓸 수 있는 수많은 레고 블럭을 제공한다고 생각하면 된다. 개발 패러다임을 학습하는 것으로 이 수많은 패키지를 더 쉽게, 다시 활용할 수 있는 코드로 만드는데 도움이 된다. 다른 사람의 구현을 이해하는데도 도움이 되고 확장 가능하고 지속 가능한 코드를 작성하는데도 도움이 된다.

코드에서 문제가 발생할 때마다 print_r()exit(), 그리고 새로고침 키로 디버깅을 한 경험이 있을 것이다. 지금도 그렇게 개발하고 있어도 이해할 수 있다. 이제는 문제가 나타났을 때, 에러를 발생하고, 예외 처리를 하고, monolog/monolog와 같은 패키지로 깔끔하게 로그를 남겨 확인하면 된다. 복잡하고 크고 어려운 문제를 한번에 해소하려고 하는 것은 쉽지 않은 일이기 때문에 이런 작은 변화부터 시작되어야 한다. 다른 언어에서는 흔하게 사용하는 패러다임은 이미 많은 개발자가 편하게 활용할 수 있도록 수많은 패키지로 만들어 제공되고 있다. 배우고, 살펴보고, 활용하자.


이 글은 공상과학이 아니다. PHP 개발자라면서 여기서 다룬 이야기를 단 하나라도 이해하지 못했다면 정말로 반성하고 공부해야 한다. (취미로 하는 일이고, 집에 돈이 많다면 상관 안하겠지만.) 회사에서 PHP를 사용하는데 이런 이야기가 전혀 없었다면 사내 메일로 이 글을 뿌리고, 인트라넷에 공유하고, 출력해서 화장실 칸마다 붙이고, 당장에 스터디를 꾸려 배워야 한다. 이 글을 읽고 현대적인 PHP에 대해 공부하고 싶어졌다면 감사하게도 PHP The Right Way 한국어판이 있어 이 글에서 시작하는 것으로 충분하다. 좋은 커뮤니티도 학습에 있어 중요한 요소다. 모던 PHP 유저 모임에 가입해 공유되는 다양한 글을 읽어보고 세미나에도 참여해보자.

이 글을 읽은 PHP 개발자라면, 2016년엔 꼭 복붙된 PHP 코드와 include로 범벅된 PHP 코드에서 벗어나고, 더 나은 추상화와 질서정연한 코드 속에 즐겁게 개발할 수 있기를 기도한다.


더 읽을 거리

이상한모임에서 대림절 달력으로 크리스마스 전까지 12월 내내 하루 한 권씩 각자 올해 읽은 책을 공유합니다. 매일 공유되는 독후감을 이상한모임 대림절 달력 페이지를 통해 확인할 수 있습니다.


책을 설명하기 전에 아래 영상을 먼저 보자. 하얀 옷을 입은 사람이 공을 몇 번이나 주고 받는지 숫자를 세야 한다.

대니얼 카너먼의 책 <Thinking Fast and Slow>1을 연초에 구입했었는데 연말이 다 되어서야 끝까지 읽을 수 있었다. 이 책은 직관의 편향성을 주제로 하고 있는데 빠른 생각과 느린 생각 (책에서는 System 1과 System 2) 둘을 설명하고 이 두 가지가 어떻게 유기적으로 영향을 보이는지 다양한 실험 결과를 보여준다.

빠른 생각은 자동적으로 동작하고, 고민하지 않고, 의식하지 않고 나오는 생각을 의미한다. 느린 생각은 고민하고, 문제를 해결하거나 연산하는 등의 과정을 거쳐 나오는, 노력이 필요한 생각을 말한다. 빠른 생각은 화난 사람 얼굴을 봤을 때 화났다고 아는 것과 같이 직관적인 생각이고, 192 + 385 같은 문제를 봤을 때 결과를 생각하는 경우가 느린 생각이다. 사람은 이 두 가지 방식으로 사물과 문제를 판단한다. 그래서 이 책에서는 이 두 가지 방식이 어떻게 동작하는가 살펴보는 것을 주요 내용으로 하고 있다.

이 책은 내내 인지과학에 관한 이야기를 다루고 있다. 예를 들면, 빠른 생각이 상황에 맞게 자동으로 추측해서 생각을 채워 이야기를 완성하게 되면 다른 가능성에 대해 판단할 필요가 없어지게 된다. 그런데 이 빠른 생각에서 잘못된 판단을 내렸다면 느린 생각이 활동을 시작해 다른 대안을 찾아야 하는데 느린 생각이 다른 일로 바쁘거나 게으른 상태가 되면 그 판단이 쉽게 바뀌지 않는 것이다. 처음 어떤 인상을 갖는가에 따라서도 이런 편향적인 사고가 나타날 수 있다. 그런 이유로 직접 보지도 않고 엄청 좋아하거나 무조건 싫어하는 경향도 나타날 수 있다.

이런 다양한 사례를 줄줄이 나열하고 있어서 다소 따분하게 느껴질 수도 있다. 그래도 앞서 본 영상에서 느린 생각이 주로 동작하기 때문에 빠른 생각의 주요 역할인 다른 움직임에 대한 파악이 약화되는 것을 경험할 수 있었던 것과 같이 실제로 살펴볼 수 있는 많은 연구 사례를 다루고 있다. 책 내용을 적자면 끝도 없이 적을 수 있는데 그러면 재미 없으니 꼭 책을 보도록 하자. 막연하게 “애플 제품이 이유 없이 막 끌린다”라거나 “MS는 어찌 되었든 까야한다” 식의 직관 편향을 접해봤다면 (아니면 본인에 해당한다면) 재미있을 내용을 많이 다룬다.2

자기계발서가 아니기 때문에 무엇을 어떻게 해야한다고는 얘기하는 책이 아니다. 하지만 좁게는 자신에게 나타나는 편향성이 어떤 방식으로 동작하고 있고 내가 어떤 부분이 강한 지, 약한 지 판단해볼 수 있고, 넓게는 사람들이 어떤 방식으로 인지하는가에 대한 연구를 읽는 과정으로 더 깊게 살펴볼 수 있는 시각을 제공한다. 만약 자신이 프로그래밍이나, 디자인, 기획 등으로 사람이 사용하는 무언가를 만드는 일을 한다면 꼭 읽어봤으면 좋겠다.

  • 번역서도 있다. 대니얼 카너먼, 이진원(역), 『생각에 관한 생각』, 김영사, 2012. 
  • 직접 목격하지 않았는데도 무엇을 완전 좋아하거나 아예 싫어하는 경향을 후광 효과(Halo Effect)라고 한다. 
  • Published on December 2, 2015

    TypeScript를 사용한다면 다음과 같은 참조를 많이 봤을 것이다.

    /// <reference path="../../typings/tsd.d.ts" />
    

    TypeScript 1.5부터 추가된 tsconfig.json을 프로젝트에 넣으면 레퍼런스를 일일이 적지 않고도 알아서 인터페이스를 불러온다. 별다른 설정 없이 tsconfig.json을 생성하는 것으로도 모든 디렉토리를 기본값으로 참조하게 된다. 기본적인 파일은 다음 내용으로 작성하면 된다.

    {}
    

    추가적인 설정을 하고 싶다면 MS TypeScript의 tsconfig.json을 참고하자.

    명시적으로 참조할 파일을 지정하고 싶다면 files 프로퍼티에 목록으로 작성한다. files에 명시적으로 파일명을 기록하면 명시적으로 기록되어 있는 ts 파일만 불러와서 컴파일하는 점에 유의해야 한다.

    {
      "files" : [
        "./src/app.ts",
        "./typings/tsd.d.ts",
        "./typings/angularjs/angular.d.ts",
        "./typings/jquery/jquery.d.ts"
      ]
    }
    

    위 설정처럼 모두 나열해서 작성하면 해당 파일만 참조한다. 위 작성된 목록을 보면 알겠지만 아직 와일드카드(*)로 경로를 지정하는 방식이 지원되지 않고 있다.

    Atom에서는 fileGlobs에 와일드카드로 경로를 지원하는데 아직 atom-typescript에서만 사용 가능한 기능이다. atom에서 fileGlobs를 와일드카드로 작성했다면, 자동으로 files의 목록이 갱신되는 것을 확인할 수 있다.

    {
      "filesGlob": [
        "./**/*.ts",
        "!./node_modules/**/*.ts"
      ],
      "files": [ /* 이 부분은 atom-typescript에 의해 자동으로 생성된다. */
        "./globals.ts",
        "./linter.ts",
        "./main/atom/atomUtils.ts",
        "./main/atom/autoCompleteProvider.ts",
        "./worker/messages.ts",
        "./worker/parent.ts"
      ]
    }
    

    atom-typescript의 tsconfig.json를 보면 사용할 수 있는 추가적인 기능을 확인할 수 있다.

    tsconfig.json은 원래 atom-typescript에서 사용하던 방식인데 TypeScript에 PR되어 일부 반영된 상태다. 아직 fileGlobs 구현이나 files, exclude에서 경로 단위로 설정하는 기능은 아직 구현이 없지만 PR은 열려있다고 하니 누군가 조만간 개선하지 않을까 생각이 든다 🙂

    TypeScript는 MS에서 개발한 JavaScript 슈퍼셋 언어다. 이 TypeScript를 사용하면 정적 검사를 활용할 수 있어 개발에 많은 편의를 제공한다. 물론 기존에 있던 JavaScript 라이브러리에 대해서도 정적 검사를 수행하려면 해당 라이브러리도 정의 파일, 다시 말해 타입 검사를 위한 인터페이스를 제공해야 한다. 그래서 나온 프로젝트가 DefinitelyTyped인데 TypeScript의 타입 정의를 제공하는 리포지터리 서비스다. 사용하는 라이브러리의 인터페이스가 이 리포지터리에 등록되어 있다면 손쉽게 내려받아 그 정의를 사용할 수 있다.

    이 서비스는 tsd라는 TypeScript 정의 관리 도구로 사용한다. tsd 페이지에서 설치 방법과 제공 정의 목록을 확인할 수 있다. tsd로 설치한 라이브러리는 별다른 설정이 없으면 tsd.json에서 메타 정보가 관리되고 해당 파일은 typings에 저장된다. tsd로 설치된 정의는 typings/tsd.d.ts 파일을 참조하는 것으로 활용할 수 있다.

    설명이 필요 없을 정도로 사용 방법은 간단한데 몇 가지 혼동이 오는 부분이 있어서 적어보면, tsd install <package>로 설치를 하면 해당하는 패키지의 정의를 내려받는 것이지 실제 패키지를 내려 받는 것은 아니다. 해당 패키지는 jspm, npm, bower 또는 직접 내려받아야 한다. 패키지와 타입은 전혀 별개라는 점을 처음에 이해하지 못해서 고생을 했다.

    그리고 tsd link라는 명령어가 생각처럼 동작하지 않아서 한참을 살펴보게 되었다. (설명을 너무 대충 본 탓.) 설명을 읽어보면 package.jsonbower.json에 정보가 있는 패키지를 DefinitelyTyped에서 자동으로 찾아 정의를 받아줄 것처럼 보이지만 실제로는 다음에 충족되는 패키지에 대해서만 동작한다.

    1. 해당 패키지에 대한 타입 인터페이스를 .d.ts와 함께 제공
    2. 해당 패키지의 configuration에서 "typescript": { "definition": "dist/foo.d.ts" } 형태로 해당 타입 인터페이스를 명시한 경우

    이런 패키지를 bower나 npm을 통해 설치한 후, tsd link를 입력하면 typings/tsd.d.ts에 아래처럼 참조를 등록해준다. 위 두 가지에 충족되지 않으면 마법같이 연결되는 일은 일어나지 않는다 😛

    /// <reference path="jquery/jquery.d.ts" />
    /// <reference path="../bower_components/angular/angular.d.ts" />
    

    tsd의 모든 명령어는 DefinitelyTyped/tsd에서 확인할 수 있다.

    어린 시절 일기를 꾸준히 써야 한다고 주입받은 사람이라면 어딘가에 삶을 기록해야 한다는 강박감이 생긴다. (실제로 기록하고 있지 않더라도.) 난 공부는 못하더라도 선생님 말씀은 엄청나게 잘 듣는 타입의 학생이었기 때문에 기록하는 삶을 살기 위해서 열심히 노력했다. 일기도 매번 작심삼일이지만 꾸준하게 쓰려고 노력했다. 컴퓨터를 배우고서는 컴퓨터에도 일기를 썼었다. 그땐 그 글이 평생 가리라 생각하고 1GB 하드 드라이브, 3.5인치 플로피 디스켓에 저장했었다. 당연히 그때 쓴 글은 어디서도 찾아볼 수 없는 글이 되었다. 그 하드는 죽어서 2GB로 교체했고 플로피 디스켓은 더는 읽을 수 없었다.

    인터넷을 맨 처음 만났을 땐 인터넷에 글을 쓴다면 더 오랫동안 글을 갖고 갈 수 있겠구나, 라는 생각이 자연스럽게 들었다. 그리고 이름도 기억 안 나게 망해버린 서비스에 글을 썼다.1 그렇게 글을 날려 가면서도 이곳저곳 글을 많이 작성했다. 서비스를 이용해서 글을 작성하면 기술적인 문제로 날려버리는 일은 거의 발생하지 않고 서비스가 망하지 않는 이상 평생토록 저장할 수 있다. 물론 당시 많은 서비스가 접혀서 여러 번 날렸다.

    2000년 홈페이지 방명록 사진
    과거에도 관리 안한다고 욕먹었던 나란 사람

    여러번 서비스를 옮겨가면서 글을 썼지만, 문제는 나 자신에게도 있었다. 너무 유치하고 어린 이야기만 가득하다고 느껴 지워버린 일이 여러 번 있었다. 대부분 서비스는 몇 번 클릭으로 계정을 지우거나 글을 날려버릴 수 있었다. 되돌릴 방법 없이 쿨하게 모든 데이터를 지워버릴 수 있던 탓에 내가 무엇을 어떻게 생각하고 지냈는지 들춰 볼 일기장이 없어지고 말았다.

    내 글을 누적하는데 내적/외적 문제가 늘 발생했지만 가장 오랫동안 폭파하지 않고 꾸준하게 사용한 곳은 바로 여기다. 아직도 과거의 글을 보면 오글거리고 유치해서 삭제 버튼에 손이 가는 편이지만, 아이러니하게 글을 올려둔 호스팅과 도메인을 유지하는 데 돈을 쓰기 시작한 이후로 내 글에 좀 더 애정이 생겼다. 그렇게 이 블로그가 가장 오랫동안 꾸준하게 글 쓰고 관리한 곳이 되었고 매일매일 그 꾸준함의 기록을 경신하고 있다.

    왜 쓰고 왜 공유하나

    꾸준하게 쓰는 것이 앞서 말한 강박감에 의해 시작된 것이라면 무엇을 쓰고 공유할지에 대해서는 호주에 올 준비를 할 때 든 생각 때문이다. 워킹 홀리데이로 호주에 오기 전에 책도 여러 권 읽어보고 후기도 매일같이 찾아서 읽어봤지만, 다들 농장에서, 공장에서, 또는 리조트나 호텔에서 일한 이야기만 있었지 호주 취업시장이 어떻고 어디서 무엇을 알아봐야 하는지에 대한 글은 찾아볼 수 없었다. (한글로 검색해서 그런지 모르겠지만.) 어느 계절에 어느 지역 농장에서 체리를 따면 주천 불을 벌 수 있다더라, 성과제로 운영되는 농장에는 농사의 신이 존재해서 하루에 삼사백 불을 번다더라, 어느 리조트에 들어가는 건 까다롭지만 일 안 하는 날엔 리조트 시설을 무료로 이용할 수 있다더라. 이런 이야기 외에는 찾기 힘들었다.

    어디든 “IT로 취업하려고 하는데….”하고 물어보면 하나같이 “현지인도 어려운데 가능하겠냐”는 부정적인 대답만 들을 수 있었다. 그래도 흔하지 않을 뿐 누군가는 그렇게 하고 있겠지 하고 어떻게 만든 기회인데, 워킹 홀리데이로 와서 어떻게든 도전해보자고 생각하고 있었다. 호주로 출국하기 얼마 전에 활동하던 개발 커뮤니티에 이 이야기를 올렸는데 워킹 홀리데이로 시드니에서 웹 프로그래밍으로 일을 시작해 스폰서 비자로 전환했다는 분이 있었다. 몇 줄 안 되는 댓글이었지만 완전히 불가능한 것이 아니란 말에 걱정을 좀 덜 수 있었다. 그런 과정을 겪고 나니 단순히 호주 이야기 외에도 내가 겪는 모든 일을 글로 남기면 누군가 나처럼 고민하는 사람에게 도움이 되지 않을까 생각하고 바다에 편지 띄우는 심정으로 글을 쓰기 시작했다.

    그런 이유로 글을 쓰긴 시작했지만, 남에게 도움이 되는 것보다 먼저 나 스스로 더 많은 도움이 되고 있었다.

    어떤 글로 시작할까

    요즘 쓰는 글을 크게 두 갈래로 나눠보면 하나는 정리해두고 잊기 위해 작성하고, 다른 하나는 경험이나 지식을 오래 기억하기 위해 작성하는 글이다.

    정리해두고 잊기 위한 글은 두 세 문단으로 쓸 수 있을 정도로 사소한 글로 큰 노력 없이 간단하게 작성할 수 있다. 예의를 차리지 않아도 될 정도로 짧은 글이기 때문에 용건만 간단히 작성하고 나중에 다시 쓸 일이 있을 때 1분 이내로 읽어서 바로 사용할 수 있으면 된다. 이런 글을 작성할 때는 읽고 나서 더 찾아보고 싶은 경우를 위해서 링크나 키워드를 포함하는 것이 중요하다. 마주한 문제를 내가 표현하는 방식으로 정리해둔 글은 내가 다시 검색할 때 효용이 크다. 내 표현대로 작성했기 때문에 글을 작성하고 잊더라도 검색엔진에서 생각나는 키워드로 검색했을 때 쉽게 찾을 수 있다. 사소한 글은 색인 카드를 작성하는 느낌으로 쓸 수 있다. 이 과정을 겪게 된 이유, 이후엔 어떻게 되었는지 한 줄 덧붙이면 다른 사람에게도 도움이 되는 글이 된다.

    다른 하나는 바둑에서 대국을 끝내고 복기하는 것과 같은 기분으로 과정을 정리하는 글을 작성한다. 경험에 대해 작성한다면, 어떤 상황에서 어떤 결정을 내렸고, 그 결정으로 인해 어떤 결과가 나왔는지 활자로 정리하는 과정에서 경험을 객관화하는데 도움이 된다. 객관화된 경험은 비슷한 상황에서 결정을 내리는 데 활용하기 좋으며 명료하게 정리한 과정에서 내공으로 쌓이게 된다. 알게 된 지식에 대해 자신만의 표현으로 논리정연하게 정리하면 더 오랫동안 기억할 수 있다. 이런 글을 작성하기 전에는 어떤 내용을 쓸지 짧게 정리한 후에 시작하면 도움된다. 다 작성하고 나서는 퇴고를 꼼꼼하게 한다. 이런 글에 시간을 얼마나 쓰는가는 이 글이 자신에게 얼마나 중요한가에 따라 좌우한다.

    이 두 가지 글쓰기는 그 경계가 모호한 편이다. 사람들이 읽고 좋다고 많이 공유하는 글은 복기하는 식의 글이지만, 사소한 글을 평소에 많이 쓰지 않으면 긴 호흡의 글을 작성하기 쉽지 않다. 나도 긴 글을 작성하는 데 늘 어려움이 있어서 사소한 글을 많이 쓰는 편이지만 점점 호흡이 늘고 있지 않나 생각하고 있다. 호흡은 둘째 치고 꾸준함을 유지하기 위해서라면 작게 시작하는 것이 중요하다. 꾸준해지고 싶다면 작게 시작해야 한다.

    꾸준함 그 이후는

    꾸준함이 어느 정도 궤도에 올랐다고 생각하면 의도적인 수련이 필요하다. 글을 쓰는 시간을 더 짧게 잡고 작성한다거나, 더 넓은 외연과 깊은 식견의 글을 작성한다거나, 연재 형식으로 글을 작성한다거나 말이다. 스스로 좋은 동기 부여가 될 수 있는 목표를 찾는 것이 좋은데 물론 그 전에 꾸준함을 먼저 챙기는 것이 좋겠다. 평소에 달리지도 않았던 사람이 내일 당장 마라톤을 뛰겠다고 결정하는 것만큼 황당한 일이다.

    꾸준함을 유지하면서 내가 지키려는 원칙도 있는데 그 원칙 중 0순위는 작성한 글을 삭제하지 않는 것이다. 얼마 전 엄청나게 공유된 오픈소스 쓰셨던데 그러고도 개발자입니까?도 내 블로그에서 조회 수에서 큰 지분을 가진 글인데 공유될 때마다 다시 읽어보면 그때 그 분노를 추스르지 못했던 내 상황이 자꾸 생각이 나서 삭제할까 고민을 가장 많이 하는 글 중 하나다. 그래도 다 내 경험이고 그때 일했던 시기를 다시 기억할 수 있는, 몇 남지 않은 통로다. 만약 과거에 이 글을 삭제를 했다면 이때 경험을 영영 상기할 수 없게 되었을지도 모른다. 참조가 없으므로 GC로 정리되서 말이다.

    잘 쓰기보다 꾸준함이 먼저

    멋진 통찰이 가득한 블로그를 보면 이 블로그를 쓰는 사람은 참 대단하다 생각하며 RSS에 구독하고 있고 나도 언젠가 그런 블로그처럼 멋진 글을 올려야지 생각한다. 누구를 닮아야지 하고 롤모델을 세우는 것도 중요하지만, 종종 내 부족함에 대해서만 고민하게 되고 그 사람처럼 멋진 글을 못 쓰니까 글을 못 쓰겠다는 생각이 들 때가 있다. 그래서 어떻게든 꾸준함이 습관이 되기 전까지는 철저하게 나를 중심으로 글을 써야 한다. 내가 공유하고 싶은 글, 내 생각엔 중요하다고 생각하는 글을 써야 한다. 그러면 꾸준함의 궤도에 오르기 쉽고 그 이후로는 자신이 원하는 방향대로 항해할 수 있다. 꾸준하게 하지도 않는데 잘하길 먼저 고민하면 그건 너무 욕심이 아닐까 싶다.

    아직 나도 많이 부족하고 재미없고 말도 안 되는 글을 올리지만 오랜만에 내 블로그를 둘러보면서 든 생각을 두서 없이 정리해봤다. 자신 있게 블로그를 자랑할 수준은 안 되지만 예년보다 꾸준히 한 편이고 그 과정에서 배우고 느낀 점이 많았다. 내년에는 올해보다 더 꾸준하게 글을 쓸 수 있으면 좋겠다.

  • 그 서비스 중에 생각난 이름이 있어서 검색해봤는데, 세상에, 아직도 서비스하고 있었다. 12년 전에 쓴 추리소설이 아직도 존재한다. 세상에. 
  • 색상을 바꿔요

    눈에 편한 색상을 골라보세요 :)

    Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.