Test-Driven Development
"테스트 주도 개발(Test-driven development TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다. 이 기법을 개발했거나 '재발견' 한 것으로 인정되는 Kent Beck은 2003년에 TDD가 단순한 설계를 장려하고 자신감을 불어넣어준다고 말하였다." - 위키피디아

 

테스트 주도 개발 (=TDD)은 테스트 코드를 먼저 작성하고, 작성된 테스트 케이스들을 이용하여 반복적으로 테스트하며 개발하는 것을 말합니다. 처음 TDD를 도입하려고 했을 때에는 시간이 부족하다는 혼자만의 판단으로 '나중에, 나중에...'를 계속 되새겼지만 실제 TDD를 도입해본 결과 분명히 일정한 양의 반복적인 재발비용이 발생하는 것은 사실이나 업무의 양이 두배, 혹은 수배나 되는 많은 시간이 (물론 익숙해지기 까지의 일련의 과정과 많은 삽질이 필요합니다 ㅡ_ㅡ;;) 가중되는 것은 아니었습니다.

 

 

 

TDD Cycle Red Green Refactor

처음 TDD를 진행하며 가장 막연했던 것은 Sanity Test가 끝난 후 '무엇을 테스트 하며 어떻게 구성하냐'였습니다. 제가 레퍼런스로 삼았던 Testing Vue.js Applications.js (Manning | Edd Yerburgh ) 책에서는 여기에 관하여 경험자로서 그리고 Vue.js 코어팀의 Test 관련 담당자로서 자신의 철학을 잘 설명해주고 있습니다. (아직 번역서는 없습니다.)

 

출처: Testing Vue.js Applications.js

 

여기서 저자는 프론트엔드 개발 시 이상적인 테스트 스위트의 구조를, 다음 세 단계의 Unit tests, Snapshot tests, e2e tests의 피라미드 형태로 보여줍니다. 전체의 테스트 스위트 중 해당 테스트가 차지하는 비율은 60% / 30% / 10%입니다.

 

이 세가지의 테스트는 동일하게 어플리케이션을 테스트하는 것이지만 관심사나 테스트 방식, 동작이 전혀 다릅니다.

 

유닛 테스트컴포넌트 컨트랙트로 동작해야 하는 기능들을 테스트하며 테스트 스위트 중 가장 많은 비율을 차지합니다. 이 유닛 테스트 과정에서 서버에 요청을 보내고 응답을 받는 일련의 과정은 테스트에 포함시키지 않았습니다. 가장 큰 이유 중 하나는 테스트 실행시간과 프론트엔드 개발의 관심사를 벗어난 부분이기 때문입니다. 제공된 API는 검증의 책임이 백엔드 서비스 / 로직에 있습니다. 그와 함께 현재의 네트워크 연결상태와 응답까지 걸리는 시간테스트 때마다 늘 증가하는 것은 생산성을 매우 많이 떨어뜨리게 만드는 행위이므로 이는 테스트 대상에서 제외합니다.

 

스냅샷 테스트는 말 그대로 컴포넌트 / 레이아웃의 현재의 상태를 저장(스냅샷)하여 다음번 테스트 실행시 원래의 스냅샷과 비교하여 오류 상태를 알려줍니다. 이전의 테스트가 존재하지 않다면 첫 실행시 스냅샷은 단순 저장만 합니다. 그러한 이유로 컴포넌트, 레이아웃의 모든 개발과정이 끝난 후 mannual testing을 거친 후 테스트 코드를 작성 해야합니다. 그렇지 않다면 계속해서 업데이트 되는 항목들이 생겨 올바른 테스트가 되질 않고 불필요한 시간을 소비하게 됩니다.

 

마지막 e2e 테스트는 모든 유닛 테스트와 스냅샷 테스트를 마친 후 어플리케이션 프로세스의 여정을 가상으로 테스트합니다. 말이 조금 생소하게 다가올지 모르나 엔드유저가 어플리케이션에 체류하면서 어떻게 사용할것인지 예상되는 방법들을 정리하여 브라우저를 띄운 상태에서(이는 자동 실행됩니다.) 해당 절차대로 어플리케이션을 동작시키는 테스트입니다.

 

간단히 TDD가 무엇이며 어떻게 구성되었는지 알아보았습니다. 이렇게 열거해놓고 보니 꽤나 복잡하게 느껴지실 겁니다. 하지만 TDD에 있어서 가장 중요한 것은 따로 있습니다.

 

일단 제멋대로 구현하고 있는 자신의 손과 sanity test, 즉!!! 컴포넌트를 정상적으로 테스트 프레임워크에서 동작시키도록 만드는 것이 가장 중요합니다. 앞서 설명한대로 TDD는 우선 테스트 코드를 작성하는 것으로 시작합니다. 많은 TDD 관련 서적과 정보를 찾아보면 아래와 같은 문구를 발견하실 수 있습니다.

 

'반드시 실패하는 테스트 코드를 먼저 작성한다.'

 

그렇다고 성공하는 테스트 코드를 실패하게끔 작성하라는 뜻은 아닙니다. 위의 문구는 구현부가 없기 때문에 반드시 실패하는 테스트 코드가 처음 작성될 것이라는 의미를 강조한다고 생각하시면 됩니다. TDD의 시작은 테스트 코드부터 작성을 하고 그것을 성공하게 만든 후 리팩토링을 하는 과정을 반복적으로 수행하는 것입니다. 위의 과정을 통해서 해당 테스트 코드로 다듬어진 구현부가 완성되는 것입니다.

 

그런데 테스트 주도 개발에 익숙하지 않은 개발자라면 우선 손가락이 먼저 움직이게 됩니다. 저 역시도 구현부를 만들다 도중에 테스트 코드를 작성하기도 하고 구현이 끝난 후 테스트 코드를 작성해가며 애를 먹었습니다. 우선은 많이 익숙해지기 전까지는 테스트 코드를 먼저 작성하는 습관을 들이는 것이 중요합니다.

 

그러한 습관이 들기 전에 마주할 커다란 벽은 해당 컴포넌트를 테스트할 환경을 구성하는 일입니다. 컴포넌트를 jest라는 프레임워크에 mount 하고 외부 라이브러리를 사용한다면 그에 대한 처리를, 해당 컴포넌트가 다른 컴포넌트의 하위 컴포넌트로 마운트 시점부터 propsData를 가지고 있어야 한다면 해당 데이터를 세팅해주어야 정상적으로 컴포넌트의 테스트 코드를 작성할 수 있습니다. 이런 일련의 과정이 처음에는 굉장히 무의미해 보이고 소비하는 시간 대비 생산성이 떨어질 때왜 이런 소모적인 일을 해야하는지, 이걸 반복적으로 해야되나하는 의구심과 불만이 커지게됩니다. 저 역시도 마찬가지였지만 지금 두개의 프로젝트에서 TDD를 도입하여 개발을 진행해본 결과,

 

'그것은 가치있다!'

 

로 노선을 바꾸게 되었습니다. 우선은 이렇게 함으로서 외부 라이브러리 동작방식에 대하여 생각하고 공부하는 시간이 많아졌습니다. 컴포넌트에 필요한 것을 생각하는 시간이 늘었고 동작방식이 이상하거나 개선해야 할 사항을 다시 한번 점검하게 되는 계기를 마련해 주었습니다.

 

테스트 주도 개발을 하며 늘어난 것 중 하나는 예외처리였습니다. 예상밖의 응답값 혹은 동작이 발생하였을 때 예외처리를 어떻게 할 것인가, 또 사용성 유지를 위하여 에러를 발생시키는 것이 나은지 아니면 프로세서의 처음으로 돌려보내는 것이 맞을지, 아니면 알림으로 엔드 유저에게 해당 부분의 처리를 가이드해줄지, 이러한 예외처리에 대한 부분이 코드 곳곳에 늘어나게 되었습니다.

 

한가지 더 늘어난 것 중 하나는 항상 유닛 테스트를 하기 때문에 회귀에러를 많이 줄일 수 있다는 것입니다. 물론 회귀에러가 줄어든 것이지 없어지지는 않습니다.

 

마지막으로 클래스 / 함수 / 메서드 등의 구현부가 조금 더 SOLID하게 변하게 되었습니다. 각각의 테스트를 진행하며 각각의 디스크립션을 작성하다 보면 해당 기능의 역할과 책임이 분명해집니다. 물론 개발을 하기 전에 기획서 혹은 요구사항 명세를 보겠지만 해당의 지시 혹은 문서는 말 그대로 서비스적인 관점에서 작성됩니다. (절대적이지 않으며 단순히 저의 경험에서 나온 결론입니다.) 엔드 유저 관점에서는 관심이 없는 부분이지만 개발할 때에는 꼭 지켜져야 하는 부분, 예를 들면 해당 데이터의 응답 혹은 연산이 비용이 많이 드는 것이라면 메모이제이션으로 해당 값을 캐싱한다는 계획 같은 것은 개발자가 구현하며 추가해야 하는 기능들입니다. 물론 경험 많은 개발자들은 기획 리뷰를 하며 이러한 부분들을 바로 캐치할 수 있겠지만 테스트 코드를 작성하며 반복적인 리팩토링 작업을 하다보면 해당 기능을 구현하는 부분의 문제점과 반드시 극복해야하는 개발적인 이슈들이 눈에 잘 띄게 됩니다.

 

출처: pinterest

그렇다면 앞으로 모든 프로젝트는 TDD를 실행한다?!

 

그것은 프로젝트의 성격과 개발자 및 조직의 선택사항입니다. TDD가 훌륭한 하나의 개발 방법론이지만 이는 각자의 상황과 환경, 시간적인 부분이 충분히 고려되어야 할 것입니다. 위에서도 말씀 드렸지만 개발 시간은 일정부분 증가할 수 밖에 없습니다. 모델 인터페이스가 변경되거나 컴포넌트의 중요한 로직이 변경된다면 무조건 테스트 코드의 수정이 필요합니다.

 

한가지의 추가 사항 혹은 수정사항이 생길 때 최악의 경우, 그와 관련된 거의 대부분의 테스트를 손봐야 하는 경우도 생깁니다. 그렇기 때문에 TDD를 도입할 때에는 여러가지 상황이 고려되어야 할 것입니다. 아직 무엇도 확정된 것이 없고 주요 기능만 러프하게 주어진 상황에서 프로토타이핑을 할 경우 TDD를 도입하는 것이 맞는가를 생각해 본다면, 물론 답은 정해지지 않았지만, 저는 그렇지 않다라고 얘기할 것 같습니다.

 

어찌됐건 TDD는 좋은 개발 방법이자 코드베이스를 조금 더 깔끔하고 우아하게 관리하는 습관인 것 같습니다.

 

 

 

 

[자료 구조 활용] 최근 검색어 기능 구현 예제

 

* 해당 예제는 복기의 일부 입니다. 소개되는 소스를 그대로 적용하면 여러분의 프로젝트에 많은 허들을 만들 수 있습니다.

 

이전 포스팅에서 자료구조 중 스택에 대해 알아봤습니다. 하지만 스택이나 큐 외에도 실제 프로젝트에서는 해당 스펙(기획안)에 따라서 그에 맞는 자료구조와 기능 들이 필요합니다.

 

만약 최근 검색어를 담아두는 기능을 구현한다고 가정해 봅시다. 이의 구현을 위해서는 다음과 같은 기능이 필요할 것입니다.

 

1. 사용자가 어플리케이션을 처음 사용하지 않고 검색한 내용이 있다면 그 내용을 바탕으로 초기화가 이루어져야 한다.

2. 최근 검색어는 모든 내용을 저장하지 않고 반드시 주어진 길이에 맞는 갯수만큼의 데이터만을 가지고 있는다.

3. 이미 검색된 내용을 다시 한번 선택 / 저장한다면 그 내용이 가장 최신으로 이동되어야 한다.

4. 해당 데이터를 이용하기 위하여 배열 형태로 자료를 받아야 한다.

 

이 외에도 여러가지 기능들이 필요하겠지만(ex. 해당 검색어의 만료시점을 파악하여 삭제 등) 가장 기본적인 기능들을 열거하면 위의 네가지 정도의 핵심 기능이 필요할 것입니다.

 

위의 내용을 종합해보면 데이터는 집합이어야 하며 가장 최신의 데이터데이터의 가장 처음 인덱스에 위치해야만 하며 중복된 기존 검색어는 삭제되며 가장 최신의 데이터가 되어야 합니다. 더불어 데이터의 최대 크기를 초과하면 가장 마지막 데이터(가장 먼저 생성된 데이터)는 삭제되어야 합니다.

 

이를 구현하기 위하여 앞으 예제를 조금 다듬어 클래스를 제작하겠습니다.

const keywords = new WeakMap();
const max = new WeakMap();

class SearchedKeywords {
  constructor (cached, len) {
    if (len === undefined || len === 0) {
      throw new Error('최근 검색어의 최대값은 필수 매개변수 입니다. 검색어 저장 최대값을 설정해주세요.');
    }
    keywords.set(this, (arguments[0] === undefined || arguments[0] === null) ? [] : cached);
    max.set(this, len);
  }
}

 

오... 아름다운 템플릿... 새로운 기능을 영접합니다. 발행해 놓으니 스타일이 달라지네요 ㅡ_ㅡ 아시는 분은 댓글 좀...

 

먼저 WeakMap을 이용하여 해당 클래스의 프라이빗 멤버를 설정한 후, SearchedKeywords 클래스의 내부에서 멤버변수로 사용합니다. 이 멤버들은 private합니다.

 

그러면 이번 포스팅에서 제법 귀찮은 기능을 구현하겠습니다. 바로 unshift입니다. 자료를 새롭게 저장해야하는데 문제는, 중복 데이터가 허용이 안되는 '집합'이며 기존의 중복된 원소가 있다면 새롭게 저장되어야 한다는 것입니다.

 

unshift (element) {
  const arr = keywords.get(this);
  const num = max.get(this);
  const duplicatedIdx = arr.findIndex(el => {
    return el === element;
  })
  if (duplicatedIdx > -1) {
    const tmp = arr.slice();
    this.clear();
    tmp.forEach(el => {
      if (el !== element) {
        arr.push(el)
      }
    });
  }
  arr.unshift(element)
  if (arr.length > num) {
    arr.pop();
  }
}

 

이 메서드에서는 데이터의 최대값이 필요하며 중복된 값을 체크하여 해당 원소를 제거한 후 가장 처음에 삽입해야 합니다. ㅡㅡ;; 뭔가 말만으로는 복잡하지만 위의 구현부를 확인하시면 이해하시기에 무리는 없을겁니다.

 

해당 인스턴스의 배열과 최댓값을 변수에 담고 중복값이 있는지 체크합니다. 만약 중복값이 존재한다면 임시 저장소(=tmp)에 해당 데이터를 담고 중복값이 아닌 원소들만 원래의 배열(=arr)에 담습니다. 그렇지 않다면 (중복값이 없다면) 단순히 배열에 새로운 데이터를 담고 최대값을 체크하여 필요시 삭제해줍니다.

 

나머지는 이전 포스팅의 예제와 비슷합니다.

 

다음은 이번 예제의 스펙에 맞게 구현한 클래스의 전체 코드입니다.

 

const keywords = new WeakMap();
const max = new WeakMap();

class SearchedKeywords {
  constructor (cached, len) {
    if (len === undefined || len === 0) {
      throw new Error('최근 검색어의 최대값은 필수 매개변수 입니다. 검색어 저장 최대값을 설정해주세요.');
    }
    keywords.set(this, (arguments[0] === undefined || arguments[0] === null) ? [] : cached);
    max.set(this, len);
  }
  // toString, toArray
  unshift (element) {
    const arr = keywords.get(this);
    const num = max.get(this);
    const duplicatedIdx = arr.findIndex(el => {
      return el === element;
    })
    if (duplicatedIdx > -1) {
      const tmp = arr.slice();
      this.clear();
      tmp.forEach(el => {
        if (el !== element) {
          arr.push(el)
        }
      });
    }
    arr.unshift(element)
    if (arr.length > num) {
      arr.pop();
    }
  }

  pop () {
    const arr = keywords.get(this);
    const ret = arr.pop();
    return ret;
  }

  peek () {
    if (this.isEmpty()) return undefined;
    const arr = keywords.get(this);
    return arr[arr.length - 1];
  }

  size () {
    const arr = keywords.get(this);
    return arr.length;
  }

  isEmpty () {
    const arr = keywords.get(this);
    return arr.length === 0;
  }

  clear () {
    while (!(this.isEmpty())) {
      this.pop();
    }
  }

  toString () {
    const arr = keywords.get(this);
    return arr.toString();
  }

  toArray () {
    const arr = keywords.get(this);
    return arr;
  }
}

 

요즘은 프레임워크를 사용하여 해당 컴포넌트에서 모든것을 처리하는 일이 많습니다. 하지만 관심사가 동일한, 여러 부수적인 기능이 필요한 부분이 있다면 하나의 클래스로 관리하는 것이 더 나을 것입니다.

[LIFO / Stack class]

Last In, First Out!!!

스택 구조는 bottom에서부터 top으로 쌓이는 자료형 입니다. 그렇기 때문에 추가와 삭제가 제일 마지막 원소에서 이뤄집니다.

이는 자바스크립트에서 제공하는 내장 메서드 중에서 push와 pop이랑 같은 역할을 합니다.

아래는 WeakMap을 이용한 Stack 클래스입니다.

// 스택 클래스

const items = new WeakMap;
class Stack {
  constructor () {
    items.set(this, []);
  }

  push (element) {
    const s = items.get(this);
    s.push(element);
  }

  pop () {
    const s = items.get(this);
    const r = s.pop();
    return r;
  }

  peek () {
    const s = items.get(this);
    return s[s.length - 1];
  }

  size () {
    const s = items.get(this);
    return s.length;
  }

  isEmpty () {
    const s = items.get(this);
    return s.length === 0;
  }

  clear () {
    while (!this.isEmpty()) {
      this.pop();
    }
  }

  toString () {
    const s = items.get(this);
    return s.toString();
  }
}
  • WeakMap은 이곳에서 아주 자세히 소개하고 있으니 링크를 따라가 보세요. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/WeakMap

  • 부수적이지만 items를 WeakMap의 인스턴스로 만들면 스택 안의 데이터를 임의로 수정하거나 삭제할 수 없습니다. Symbol()을 활용 및 참조하여 private하게 만들 수도 있지만 그렇게 되면 데이터를 들여다보고 수정 및 삭제가 가능하므로 WeakMap을 사용합니다.

  • const items = Symbol('items'); this[items] = []의 형태로 사용이 가능합니다.

[Vue.js Style Guide 적용기]


이런저런 이유로 1년 가까이 블로그 활동을 접었다가 다시 시작합니다.


그동안 이직도 했고 개인적인 이유로 공부는 하면서도 그에대한 정리가 없었습니다. 이제 연말이 가까워지니 다시금 프론트엔드 개발자, 조직과 사회의 구성원으로서 삶(?!)을 정리해 보며 게을러진 저를 채찍질하기 위해서 운동과 블로그 활동을 재개하려 합니다.

물론 언제까지 포스팅 하려는 의지가 계속될지 모르겠지만... 다시 시작하는 의미로 게시물과 카테고리 정리를 하고나니 블로그가 너무 볼것이 없어 이번 프로젝트에서 코드 리팩토링을 하며 적용한 Vue.js 스타일 가이드에 대해 간단한 수행기를 포스팅합니다.


 Angular와 다른 프레임워크, 언어와는 다르게 (자바스크립트 제외) Vue.js의 공식 사이트에서는 강력한 한글 지원을 확인할 수 있습니다. 비록 업데이트 되는 내용이 모두 반영되는 것은 아니지만 한국 개발자들에게는 무척이나 반가워 할 일입니다.

스타일 가이드는 혼자서 개발할 때에는 그렇게까지 중요한 부분이 아니라고 여겨질 수 있지만 프로젝트를 진행하며 볼륨이 커져갈 때에는 그 필요성이 절실하게 느껴질 때가 생깁니다. 

서로가 작성한 코드의 가독성을 높이고 명시적인, 또는 암묵적인 룰을 지켜 운영과 유지보수를 제대로 수행하기 위해서 스타일 가이드를 준수하면 보다 수월하게 프로젝트를 진행할 수 있을 것입니다.

아직은 Vue.js 자체에서 강제하는 정도의 수준은 아니지만 밑의 문구를 보면 조만간 ESLint와 다른 자동화 프로세스를 걸쳐 스타일 가이드를 적용할 수 있는 팁을 제공할 예정이라고 하니 미리 그 내용에 대해 알아볼 필요가 있을것 같습니다.

Soon, we’ll also provide tips for enforcement. Sometimes you’ll simply have to be disciplined, but wherever possible, we’ll try to show you how to use ESLint and other automated processes to make enforcement simpler.

공식 사이트에서 제공하는 문서를 보면 Vue.js의 스타일 가이드는 4가지의 우선순위를 A, B, C, D의 레벨로 나눠 세부 규칙을 명시하고 있습니다.

A - 필수,
B - 강력 권고, 
C - 권고, 
D - 사용시 주의

(* 번역본이 있지만 최대한 원문을 번역하여 포스팅 합니다. 원문의 느낌이 개인차가 있기 때문이지 번역이 잘못된 것은 절대 아닙니다.)

공식 사이트의 스타일 가이드를 읽어내려 가던 중 습관처럼 사용했던 네이밍 규칙이 필수로 규정되어있어 조금 놀랐습니다. (다른 분들이 안그러셨다면 저의 게으름을 반성합니다.) 문서를 보면 명시 되어있지만 A 레벨오류 방지 차원에서 컴포넌트의 네이밍 규칙을 필수로 권하고 있습니다. 

root App 컴포넌트를 제외하고 컴포넌트의 이름은 반드시 다수의 단어로 사용을 해야합니다. 

이것은 해당 문서의 링크를 따라가보면 그 이유를 자세히 알수 있습니다. 요약하자면 현재, 그리고 앞으로 생성될 HTML 요소들의 이름이 단수의 단어로 규정 되었거나 규정되기 때문에 커스텀 엘리먼트인 컴포넌트의 이름을 다수의 단어로 강제하여 커스텀 엘리먼트와 네이티브 엘리먼트 사용 오류를 방지하기 위함 입니다.

자세한 것은 링크에서 확인하시기 바랍니다.


쭈욱 읽어가다 보면 너무나 당연한 규칙들, 예를 들어 new 키워드를 사용한 Vue 인스턴스 생성이 아니라면 팩토리 함수를 사용하여 data를 사용해야한다는, 이미 너무나도 잘 알고있는 당연한 내용부터, 이런것도 있었는지 잘 모르던 내용까지, 여러가지 스타일 가이드 예제가 간단하지만 이해하기 쉽게 잘 설명되어 있습니다.


이 중에서, 스타일 가이드를 확인하고 적용하면서 처음 알게된 내용 세가지만 간단하게 정리하겠습니다. (다른 분들께 공유와 저의 망각을 위함 입니다...) 


1. Props definitions


Parent에서 Child로 데이터를 넘겨줄 때 사용하는 pops는 단순히 item의 이름만 명시하여 사용하고 있었지만 프로토타이핑인 경우를 제외하고는 가능한 구체적으로, 해당 데이터의 타입(type)과 필수여부(required), 값 반환 혹은 검증에 필요한 함수 제공을 규정하고 있습니다. 


위의 규칙은 필수이지만 성능 이슈나 향후 오류 방지의 효과를 기대하기 때문도 있지만 데이터의 타입이나 필수값, 기본값 등을 체크하여 자체적인 데이터 검증 등을 통해 해당 데이터를 이름만으로 추측하는 것이 아니라 사용 목적을 분명히 하기 위함으로 보입니다. (물론 뇌피셜입니다.) 추가적으로 해당 문서에서 설명이 조금 빠져있지만 props의 default 혹은 validator 사용을 찾아보시길 권합니다. (* 참조 링크)


2. Avoid v-if with v-for

Vue.js에서는 v-if 사용시 v-for와 함께 사용하는 것을 엄격히 금하고 있습니다. (물론 저는 사용했습니다...) 명시된 문서에는 두가지의 일반적인 예를 들어 각각의 경우 상황에 맞게 대체하여 코딩할 수 있는 예시를 제공합니다.

1. 리스트 안의 반복되는 아이템을 필터링하기 위해서 사용하였다면 해당 리스트 자체를 computed 프로퍼티로 계산하여 리스트를 반환하고 반복 아이템을 렌더링 한다.

2. 어떠한 조건을 충족하지 못할 때 리스트의 아이템 렌더링을 회피하기 위한 목적일 경우, v-if 문을 해당 리스트의 컨테이너 엘리먼트로 옮긴다.

이렇게 제시하는 이유는 해당 설명에 접힌채로 명시 돼 있습니다. 자세한 설명을 자세하지 않게 간단히 설명하자면 v-for가 v-if 문 보다 우선순위 우위에 있기 때문에 해당 조건문의 값이 변경이 되거나 그러하지 않은 경우에도 리스트 구문이 렌더링 될 때마다 v-for의 내부 함수가 실행되기 때문입니다.

이를 지키지 않으면 매번 연산되는 횟수(step)가 늘어나고, 곧 성능 저하로 이어지기 때문에 스타일 가이드 상의 무엇보다 더 엄격히 지켜져야 할 것입니다. 보다 자세한 설명은 스타일 가이드를 참조하시기 바랍니다.

3. v-if / v-else-if / v-else without key

이번 규칙은 '레벨 D'지만 많이들 그냥 넘어가는 부분이라 (물론, 그 '많이'에 제가 포함되어 있습니다.) 정리합니다. v-조건문 사용에 있어서 key 어트리뷰트는 필수는 아니지만 Vue.js에서 v-if문을 처리하는 방식 때문에 같은 요소(예를 들면 v-if와 v-else가 동일한 요소인, div 엘리먼트에 각각 명시되어 있는 경우)에서 v-조건문을 사용할 경우 key 어트리뷰트를 사용하여 두 엘리먼트를 key 값으로 분리할 것을 권고하고 있습니다. 


By default, Vue updates the DOM as efficiently as possible. That means when switching between elements of the same type, it simply patches the existing element, rather than removing it and adding a new one in its place.


위에서 설명하듯 Vue.js는 DOM을 가능한 가장 효율적인 방법으로 업데이트하기 때문에 만약 v-조건문이 명시된 엘리먼트와 같은 엘리먼트가 이미 존재한다면 그것을 node 상에서 지우거나 새롭게 추가하는 것이 아니라 단순하게 패치(patch)하여 의도하지 않은 사이드 이펙트를 불러올 수 있습니다. 그러니 낮은 단계의 권고 수준이라도 v-조건문을 같은 엘리먼트에 사용할 때에는 주의를 기울여야 할 것입니다.


위의 세가지는 스타일 가이드를 적용하며 수정 하였던 많은 규칙들 중 저에게 중요한 부분이라 정리한 것이고 성능과 가독성을 위해서, 또는 향후 발생할 수 있는, 의도치 않는 오류를 피하기 위하여 반드시 스타일 가이드를 일독해 보시기를 권합니다.

[Javascript] 메모이제이션은 무엇인가?!

 

피보나치 수열은 또 뭐여... 토끼랑 번식?!

저급 개발로 페이지를 뚝딱뚝딱 찍어내며 공장의 부속품처럼 일할 때에는 반복문과 조건문만 알면 다 되는구나 굉장히 이상한 생각을 한적이 있었다. 지금 누군가 그런말을 한다면 생각을 바꾸라는 말부터 하겠다.
 
아무튼 오늘은 메모이제이션에 관하여 알아보려 한다.
 
메모이제이션(memoization)은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 동적 계획법의 핵심이 되는 기술이다. 메모아이제이션이라고도 한다.
위는 위키에 정의된 메모이제이션에 관한 글이다. 함께 나온 예제를 보며 이해를 하려고 피보나치 수열을 보다보면 어느새 토끼와 번식에 관하여 읽고있는 자신을 보게 될 것이다. 그래서 나같은 위인들을 위하여 초등학생들도 쉽게 접근할 수 있는 다른 예제를 만들어 보았다.
 
1부터 입력한 인덱스까지의 수를 순차적으로 반복하여 누적된 값을 출력해주는 간단한 계산 프로그램을 만든다고 가정하자. 계산을 하다보면 이미 계산했던 값들을 다시 반복해서 사용할 일이 있다. 메모이제이션 패턴은 이런 계산된 값들을 저장하고 필요할 때 저장된 값을 가져오는 방식을 말한다. 물론 아래의 코드는 메모이제이션의 가장 저수준 레벨을 구현한 것이다. 피보나치 수열을 recursive하게 구하는 대신 이해하기 쉬운 코드로 설명을 하기 위함이지 이러한 코딩 자체를 메모이제이션이라고 부르기에는 무리가 있을 수 있다. 정말 고수준의 메모이제이션을 보면 반복문 혹은 재귀함수를 사용하면서 그 안에서 이미 계산되거나 호출된 함수의 리턴값을 저장해서 사용하는 것을 볼 수 있을것이다. 
 
자 지루한 설명은 끝내고 아래의 코드를 보면서 개념 파악을 해보자.
 

 

상단의 assert 함수는 테스트를 위한 것이라 무시해도 된다. (이전 포스팅에서 간단한 테스트 스위트를 가져왔다.) Result 탭을 확인하면 1에서부터 입력한 값까지의 연속된 수의 누적된 합을 반환해주는 계산기가 보일것이다. default 값은 10으로 우선 버튼을 클릭해보자. 그러면 1부터 10까지의 수를 합한 55가 출력될 것이다. (가우스가 풀이했던 방식을 이용하면 더 쉬울 수 있지만 이건 예제를 위한거니까...) 자 다시 10을 입력한 상태에서 버튼을 눌러보자. 그러면 자바스크립트 코드의 반복문이 아닌 캐쉬에서 해당 값을 반환해주는 것을 알 수 있다.

 

이렇게 쉽다. 하지만 이건 기본개념이자 말그대로 저수준의 코딩이다. 이걸로 메모이제이션을 파악했다라고 느끼고 있는 분들에게 단호하게 말하고 싶다. 다른 블로그 참고해라. 이건 말그대로 메모이제이션을 가장 초딩적인 시각에서 개념 설명을 하기위한 예제이다.

 

날이 좋다... 퇴사하기 좋은 날이다... 아... 집에 가고만 싶다...

자바스크립트의 테스트 스위트를 만들어 보자.

초간단이지만 이것도 시간이 든다... 바쁘면 다른 테스트 툴을 쓰기를 격하게 권장한다...


오늘은 간단한 테스트 스위트를 만들어 보며 왜 이런 쓸데없는 짓 함수의 name 프로퍼티에 뭐가 저장되는지 눈으로 확인해보자.
assert()라는 value와 desc라는 파라미터를 가진 간단한 테스트 스위트를 작성할 것이다.
value는 테스트를 평가할 값이고 desc는 이름과 마찬가지로 디스크립션이다. 아래의 JSFiddle을 확인하자.



자바스크립트를 확인하고 Result 탭을 확인해보자. 

모든 함수에는 name 프로퍼티가 생성되며 이를 눈으로 확인할 수 있을 것이다. 그런데 표현식의 기명함수는 우리가 예측한 것과 약간은 다르다. 왜 변수명이 아닌것인가?! 


알아(서) 맞춰 보자.




[자바스크립트] 로깅 함수

테스트를 위한 코드를 작성할 시간 따위는 주지 않는 세상이지만...


요즘은 술도 많이 마시지만 책도 많이 보고있다.
파이썬 한권 자바스크립트 두권째... 물론, 내가 알아야 할 부분까지만 읽지만 (비록 1 ~ 2 챕터 분량만 남기지만...) 요새는 바쁜 것 없이 바쁜 나날이다.


코드 디버깅을 위해서 우리는 무수히 많은 console.log, console.dir을 찍어대며 '도대체 내가 왜, 무슨 부귀영화를 누리자고 이짓을 하나'를 백만번쯤 외치고 또 외쳤을 것이다. 


지금부터 소개할 것은 그것을 대체하기는 개뿔... 우아한 방법으로 로깅 마저도 크로스 브라우저로 구현한 코드이다.


아래의 코드는 대부분의 브라우저에서 작동하는 로깅 함수이다.





1. 대부분의 브라우저에서 정상 작동하는 console.log를 이용하여 메시지 로깅을 시도한다. 이때 try / catch문을 사용한다.

2. catch문 안에 또 다른 예외구문을 만들어 console.log와 유사한 기능을 하는 opera.postError를 시도한다.

3. 위 두가지 방법을 시도했음에도 try에 걸리지 않는다면 alert으로 메시지를 출력한다.


지금 이 글을 읽는 몇몇은 이런 의문이 들것이다. 저걸 왜?? 저럴 시간에 한줄이라도 더 쳐!! 뭐... 이런 류... 

하지만 사용하지 않는다고 방법을 놓치고 있다면 안된다. 개발자는 그런 매력적인 직업... ㅡ_ㅡa

대단히 재미있는 파이썬, 그런데 pygame 설치가...

오랜만의 포스팅, 매일 시간이 없다는 변명은 그만...


요즘 마음도 그렇고 무언가 새로운게 없나하는 마음에 이것저것 알아보다 선물로 받기만하고 먼지를 먹고있는 책을 발견했다.

[헬로! 파이썬 프로그래밍]을 보면서 나의 지능지수에 맞게 잘 설명된 책이라서 이번엔 삽질이 없을거라 생각했는데...


맙소사!!! pygame이 설치가 안된다!!!

stack over flow와 여러 커뮤니티를 다니며 conda도 설치해보고 이 버전 저 버전의 설치 프로그램들을 설치하고 지우고 별에별 뻘짓을 다하다가 드디어 가장 쉬운 방법을 터득하였다.
나와같이 언어의 입문에서 가장 어려운 것이 '환경설정'인 이들을 위하여 tip을 공개한다.

뭐... 일단 Homebrew가 설치되었다면 반은 성공이다. 
(설치 유무를 알기 위해서는 brew 명령어를 실행해보자)
그런다음 아래와 같은 명령어들을 입력하면 pygame을 설치할 수 있다. 물론 지금 현재를 기준으로 말이다.

brew install mercurial brew install sdl sdl_image sdl_mixer sdl_ttf smpeg portmidi pip install hg+http://bitbucket.org/pygame/pygame


오케이!! 그럼 다시 책을 보자.

2. 불리언 타입


아... 괜히 하루에 한번씩 포스팅을 한다했다... 피곤하다...


오늘은 데이터 타입 중에서 조금 중요한 요소를 짚고 넘어가자. (어제 잠시 정신이 나가서 언급 못한 부분이 있다...) 앞서 "1. 데이터 타입"에서는 'null'은 빈 객체를 참조하므로 typeof는 Object를 반환한다고 말했다. 그러면 null과 undefined를 == 연산자로 비교해보면 어떤 결과가 출력될까?! 닥치고 예제를 만들어 보았다.


Result를 확인하고 검정색 span 영역을 클릭해보면 깜짝 놀랄것이다. 왜지?? 왜죠?! 아니!!! 이상하지만 null과 undefined는 같다고 출력된다. undefined가 null에서 파생됐기 때문에 표면적으로는 동일한 것으로 정의한단다... (아오...)

그런데 정말 null에서 undefined가 나와서라는 이유 하나만으로 true값을 출력시키는 건가?! 이유는 나중에 설명하겠다. (안 그러면 포스팅 오늘 못끝냄... 궁금할껄?!)

자 null값 얘기하다가 다른 이야기로 빠졌지만 다시 돌아가자. 오늘은 주인공은 Boolean이다. 이미 프로그래밍을 다수 경험한 사람들이라면 이 Boolean이 어플리케이션 코드 내에서 얼마나 많이 쓰이는지 잘 알것이다. ECMAScript에서 Boolean은 true와 false의 두가지 리터럴 값만 가진다. 하지만 모든 데이터 타입을 불리언 값으로 표현할 수 있다.

**노란책에 나온 표 퍼옴 / 출처 - JavaScript for Web Developers**

데이터 타입true로 변환되는 값false로 변환되는 값
Booleantruefalse
String비어있지 않은 문자열 모두""(빈 문자열)
Number0을 제외한 모든 숫자, *무한대 포함0, NaN
Object모든 객체Null
Undefined해당 없음Undefined



자 뜬금없지만 오늘은 여기까지. 중요한 점은 위의 표는 반드시 숙지하고 있어야 나중에 코드에서 오타가 아닌 무언가 잘못된 것을 찾는 세상에서 제일 거지같은 경험을 한번이라도 덜 할수 있을거라 확신한다. 제발... 당신의 코드는 당신만 보는게 아니다. 누군가는 당신의 코드를 What the *uck! 내지는 ㅆ*! *ㅂ 하면서 쳐다볼 수 있다. 지금 이 시간에도... (섬뜩...) 

'Front' 카테고리의 다른 글

[자바스크립트] 초간단 테스트 스위트 만들기!!  (0) 2017.06.07
[자바스크립트] 로깅 함수  (0) 2017.06.01
JavaScript[ECMAScript] 1. 데이터 타입  (0) 2016.07.07
Ionic 하이브리드 앱  (0) 2016.06.28
Angular Material  (1) 2016.06.17

JavaScript[ECMAScript] 정리


정리한다, 정리한다, 마음속으로 외치며 그날의 피로만 정리하던 나를 반성한다...


1. 데이터 타입


ECMAScript에는 다섯 가지의 '기본 데이터 타입(*Primitive Data Type)'이 존재한다. 종류는 아래와 같다.


- Undefined : 정의되지 않은 변수 (변수를 선언한 후 값을 할당하지 않은 경우)

- Null : 빈 값 (변수를 선언한 후 'null' 이라는 값을 할당한 경우)

- Boolean : 불리언 (참, 거짓)

- Number : 숫자

- String : 문자열


그리고 '복잡한 데이터 타입'인 객체(Object)가 존재한다.

(*Array, Function, Date, RegExp와 같은 데이터는 Object이다.)


ECMAScript는 느슨한 타입을 사용한다. 우리가 변수를 선언할 때 변수의 데이터 타입과 상관없이(데이터 타입도 명시하지 않는다) 같은 문장에서 ','로 구분하여 한꺼번에 선언, 초기화할 수 있는 이유는 바로 느슨한 타입을 사용하기 때문이다. 그러면 데이터의 타입을 확인하기 위해서는 어떻게 해야할까?!


typeof 연산자를 사용하면 느슨한 타입의 변수들이 어떠한 데이터 타입인지를 알수있다.


JSFiddle의 Result를 확인해보자. 해당 변수에 어떠한 값을 초기화했는지 확인할 수 있으며 클릭하면 어떤 데이터 타입인지 alert으로 알려준다.


여기서 주목할 것은 typeFour의 null이다.

typeFour를 클릭하면 alert 창에는 object라고 표기되어 있다. 왜 Object를 반환하는 것일까?! null은 빈 객체를 참조한다. 그러므로 null 값을 할당하고 object가 나온다고 놀라지 말자. 만약 데이터 타입에 그저 비워져있는 값이 필요하다면 typeOne처럼 값을 할당하지 말아야 한다. 반대로 해당 변수가 빈 객체를 가리키게 하려면 해당 변수에는 반드시 null 값을 할당해줘야 한다.

! 아... 시간이 벌써... 자야겠다...

@ 아무래도 자바스크립트 메뉴를 따로 만들어야겠다.

$ 내일부터는 하루에 하나씩 정리하기로 한다.



'Front' 카테고리의 다른 글

[자바스크립트] 로깅 함수  (0) 2017.06.01
JavaScript[ECMAScript] 2. 불리언 타입  (0) 2016.07.08
Ionic 하이브리드 앱  (0) 2016.06.28
Angular Material  (1) 2016.06.17
AngularJS $watch  (0) 2016.06.16

+ Recent posts