MVP

요즘 취미삼아 자바스크립트를 공부중입니다.

전공자도 아니고 그냥 평범하게 직장생활 하면서 가볍게 장난감 다루듯이(?) 이것저것 만져보면서 놀고 있습니다.. ㅎㅎ

취미가 개발공부(?)인 것이죠..

이쪽과는 전혀 무관한 직장생활을 하면서 짬짬이 공부를 하다보니 자연스레 효율적인 학습법에 대해 관심을 갖게되었고, 그러다가 우연히 MVP(Minimum Viable Product, 최소기능제품)라는 개념을 알게되었습니다.

대충 훑어본 바로는 그냥 지금 알고있는 지식 선에서 최소한의 쓸모있는 제품(?)을 만들면서 공부하는 학습법이었던 것 같습니다.

사실 학습법이라기보다도 뭔가 더 거창한 개념이었던 것 같은데 그냥 이해되는대로 이해했습니다.(??)

그래서 지난번 포스트에 언급했던 JSBEN.CH 를 흉내내는 코드를 만들어보았는데요.
(최소기능제품인데 기능적으로 가치가 전무한 것이 함정입니다.. 다만 학습한 개념들을 최대한 십분 활용하는데 주안점을 두었습니다)

혹시 시간 여유가 있으신 분이면 가볍게 훑어봐주시고 피와 살이되는 조언을 해주신다면 대단히 감사드리겠습니다.

지난번 포스트 : 자바스크립트 성능 최적화에 대한 의문이 들었다

여담이지만 자바스크립트 강의는 이 곳 velog를 만드신 김민준님의 강의를 듣고있어요.
강의의 분량이 짧은 편에도 불구하고 핵심적인 내용은 모두 담고있는 것 같아 만족하면서 학습중입니다. (1.5배속으로 두 번 들었습니다 ㅎㅎ)

코드

"use strict"

// 각 함수별 반복할 횟수
const LOOP_COUNT = 30000;

/*
Usage : SpeedMeter(function1, function2, ...);
Return : 
  [
    {
      name : 함수명,
      time : 반복실행시간,
      percent : 상대백분율,
      isErr : 함수의 에러여부,
      errMessage : 에러내용
    },
    ...
  ]
*/
function SpeedMeter(...rest) {
  const result = [];

  // 넘겨받은 인자 중 함수 외의 것들 필터링
  rest = rest.filter(one => typeof one === "function");

  for (let one of rest) {
    let errMsg="";
    const start = Date.now();

    try {
        for (let i = 0; i < LOOP_COUNT; i++) {
            one();
        }
    }
    catch (e) {
        errMsg = e.message;
    }

    const end = Date.now();

    // 테스트 결과 입력
    result.push({
        name:one.name || "noname",
        time:end - start,
        isErr:!!errMsg,
        errMsg
    });
  }

  const arrTimes = result.map(({time}) => time);
  // 가장 긴 실행시간을 찾아 기준점으로 삼는다.
  const max = Math.max(...arrTimes);

  // 상대백분율 입력
  result.forEach(
    (one) => {
      one.percent = one.time / max;
    }
  );

  return result;
}

/*
Sample Code

배열 복사에 대한 3가지 방법

1. test1 : slice() 함수 사용
2. obj1.test2 : spread 연산자 사용
3. test3 : concat() 함수 사용
4. test4 : 고의적으로 만든 에러 함수
*/
const origin = [29, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63];

const test1 = () => {
    let arr = [];
    arr = origin.slice();
}

const obj1 = {
  test2: function() {
    let arr = [];
    arr = [...origin];
  }
}

const test3 = () => {
  let arr = [];
  arr = [].concat(origin);
}

const test4 = () => {
    // 정의되지 않은 arr2에 할당시도. 고의적 오류 throw
    arr2 = [].concat(origin);
  }

const result = SpeedMeter(test1, obj1.test2, test3, test4);

for (let {name, time, percent, isErr, errMsg} of result) {
    console.log(`
    함수명 : ${name}
    실행시간 : ${time}ms
    상대 백분율 : ${percent * 100}%
    에러 여부 : ${isErr}
    에러 메시지 : ${errMsg}`);
}

수정 #1

페이스북 프론트엔드개발그룹에서 서재원님의 조언을 받아 일부 수정했다.

...은 연산자가 아니다

spread 연산자라는 표현을 사용했으나 이는 잘못된 표현이라고 한다.

(참고) 연산자인 것과 연산자가 아닌 것

spread elements라는 것이 정확한 표현이라고 하나, MDN 문서를 참고해 spread 구문이라 변경하였다.

가장 긴 시간을 찾는 부분

위 코드가 이전, 아래 코드가 변경한 코드이다.
언뜻 보기에 reduce를 통해 매요소마다 순차적으로 Math.max를 실행하는 것이 더 계산량이 많아보일 수 있으나,
실제로는 아래 코드가 보기에도 좋고 성능상으로도 미묘하게 더 좋았다.

개선 전

//result의 각 요소들의 time만 골라내어 배열을 새로 만든다.
const arrTimes = result.map(({time}) => time);
// 가장 긴 실행시간을 찾아 기준점으로 삼는다.
const max = Math.max(...arrTimes);

개선 후

// 가장 긴 실행시간을 찾아 기준점으로 삼는다.
const max = result.reduce(
  (acc, now) => Math.max(acc, now.time), 0);

불필요한 let을 const로 대체

for (let one of arr) 구문에서는 let을 const로 대체해도 무관하다.

코드

"use strict"

// 각 함수별 반복할 횟수
const LOOP_COUNT = 30000;

/*
Usage : SpeedMeter(function1, function2, ...);
Return : 
  [
    {
      name : 함수명,
      time : 반복실행시간,
      percent : 상대백분율,
      isErr : 함수의 에러여부,
      errMessage : 에러내용
    },
    ...
  ]
*/
function SpeedMeter(...rest) {
    const result = [];

    // 넘겨받은 인자 중 함수 외의 것들 필터링
    rest = rest.filter(one => typeof one === "function");

    for (const one of rest) {
        let errMsg = "";
        const start = Date.now();

        try {
            for (let i = 0; i < LOOP_COUNT; i++) {
                one();
            }
        } catch (e) {
            errMsg = e.message;
        }

        const end = Date.now();

        // 테스트 결과 입력
        result.push({
            name: one.name || "noname",
            time: end - start,
            isErr: !!errMsg,
            errMsg
        });
    }

    // 가장 긴 실행시간을 찾아 기준점으로 삼는다.
    const max = result.reduce(
        (acc, now) => Math.max(acc, now.time), 0);

    // 상대백분율 입력
    result.forEach(
        (one) => {
            one.percent = one.time / max;
        }
    );

    return result;
}

/*
Sample Code

배열 복사에 대한 3가지 방법

1. test1 : slice() 함수 사용
2. obj1.test2 : spread 구문 사용
3. test3 : concat() 함수 사용
4. test4 : 고의적으로 만든 에러 함수
*/
const origin = [29, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63, 27, 28, 838, 22, 2882, 2, 93, 84, 74, 7, 933, 3754, 3874, 22838, 38464, 3837, 82424, 2927, 2625, 63];

const test1 = () => {
    const arr = origin.slice();
}

const obj1 = {
    test2: function () {
        const arr = [...origin];
    }
}

const test3 = () => {
    const arr = [].concat(origin);
}

const test4 = () => {
    // 정의되지 않은 arr2에 할당시도. 고의적 오류 throw
    arr2 = [].concat(origin);
}

const result = SpeedMeter(test1, obj1.test2, test3, test4);

for (const {
        name,
        time,
        percent,
        isErr,
        errMsg
    } of result) {
    console.log(`
    함수명 : ${name}
    실행시간 : ${time}ms
    상대 백분율 : ${percent * 100}%
    에러 여부 : ${isErr}
    에러 메시지 : ${errMsg}`);
}