2015년 다시보기

😢 이 페이지는 다음 주소로 변경될 예정입니다.
Ai Weiwei
호주 생활에서, 아니 내 인생 통틀어 미용실서 자른 머리 중에 워스트 1위에 빛나는 스타일.

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

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

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

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

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

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

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

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

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


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

2015년 번역 회고

😢 이 페이지는 다음 주소로 변경될 예정입니다.

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

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

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

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

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

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

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

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

더 읽을 거리

Angular에서 디렉티브 간 `require`를 사용해 소통하기

😢 이 페이지는 다음 주소로 변경될 예정입니다.

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가 존재하지 않거나 잘못된 값일 경우 (false0으로 평가되니 어쨌든 0이므로 안전하겠지만) 초기 인덱스를 다음처럼 폴백(fallback)으로 지정할 수 있다.

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

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