Loading...
본문 바로가기
👥
총 방문자
📖
0개 이상
총 포스팅
🧑
오늘 방문자 수
📅
0일째
블로그 운영

여러분의 방문을 환영해요! 🎉

다양한 개발 지식을 쉽고 재미있게 알려드리는 블로그가 될게요. 함께 성장해요! 😊

es-toolkit

[es-toolkit] 기여 도전하기 1일차

by 꽁이꽁설꽁돌 2025. 11. 26.
728x90
반응형

오늘은 세팅을 진행하였다.

 

폴더안에는 메서드와 그에 대한 테스트 코드로 이루어져 있다.

 

 

일단 간단하게 테스트 코드 용어부터 정리해보자

 

vitest 테스트 코드 실행 시 컬럼 의미

% Stmts 전체 statement(구문) 중 테스트된 비율
% Branch if, switch 등 분기(branch) 중 테스트된 비율
% Funcs 함수 중 테스트된 비율
% Lines 전체 코드 줄 중 테스트된 비율
Uncovered Line #s 테스트되지 않은 라인 번호 목록

 

 

그후 눈에 뛰는 메서드 하나를 나는 분석해 보았다.

생각보다 본 함수는 심플하다

import { toFinite } from './toFinite.ts';

/**
 * Converts `value` to an integer.
 *
 * This function first converts `value` to a finite number. If the result has any decimal places,
 * they are removed by rounding down to the nearest whole number.
 *
 * @param {unknown} value - The value to convert.
 * @returns {number} Returns the number.
 *
 * @example
 * toInteger(3.2); // => 3
 * toInteger(Number.MIN_VALUE); // => 0
 * toInteger(Infinity); // => 1.7976931348623157e+308
 * toInteger('3.2'); // => 3
 * toInteger(Symbol.iterator); // => 0
 * toInteger(NaN); // => 0
 */
export function toInteger(value: any): number {
  const finite = toFinite(value);
  const remainder = finite % 1;

  return remainder ? finite - remainder : finite;
}

 

유한수로 바꾼 뒤 소수일 경우에 소수점을 제거하는 로직인 것 같다.

 

한 뎁스 더 이동해보자

import { toNumber } from './toNumber.ts';

/**
 * Converts `value` to a finite number.
 *
 * @param {unknown} value - The value to convert.
 * @returns {number} Returns the number.
 *
 * @example
 * toFinite(3.2); // => 3.2
 * toFinite(Number.MIN_VALUE); // => 5e-324
 * toFinite(Infinity); // => 1.7976931348623157e+308
 * toFinite('3.2'); // => 3.2
 * toFinite(Symbol.iterator); // => 0
 * toFinite(NaN); // => 0
 */
export function toFinite(value: any): number {
  if (!value) {
    return value === 0 ? value : 0;
  }

  value = toNumber(value);

  if (value === Infinity || value === -Infinity) {
    const sign = value < 0 ? -1 : 1;
    return sign * Number.MAX_VALUE;
  }

  return value === value ? (value as number) : 0;
}

 

1. falsy 한 값 NaN, false, [], null, undefined에 대해 0을 반환한다.

2. 숫자로 변경한다.

3. 무한수에 대해서는 가장 큰 유한 수로 바꾼다.

4. return value === value ? (value as number) : 0; NaN에 대해서는 false를 반환한다.

 

그렇다면 isNaN 메서드를 쓰지 않은 이유가 무엇일까?

 

기존 isNaN 문제점

1. isNaN('abc') -> true를 반환한다. (문자열일 경우 정확하지 않음)

Number.isNaN(NaN) // true
Number.isNaN("abc") // false

 

2. 내부적으로 타입 체크 + 비교 두 번 하므로 핫 패스(핵심 반복 로직)에 넣기에는 상대적으로 느리다.

 

 

한 뎁스 더 이동해보자

import { isSymbol } from '../predicate/isSymbol.ts';

/**
 * Converts `value` to a number.
 *
 * Unlike `Number()`, this function returns `NaN` for symbols.
 *
 * @param {unknown} value - The value to convert.
 * @returns {number} Returns the number.
 *
 * @example
 * toNumber(3.2); // => 3.2
 * toNumber(Number.MIN_VALUE); // => 5e-324
 * toNumber(Infinity); // => Infinity
 * toNumber('3.2'); // => 3.2
 * toNumber(Symbol.iterator); // => NaN
 * toNumber(NaN); // => NaN
 */
export function toNumber(value: any): number {
  if (isSymbol(value)) {
    return NaN;
  }

  return Number(value);
}

 

JavaScript는 숫자 변환 실패 = NaN이라는 규칙을 갖고 있고 위의 조건 때문에 다음과 같이 심볼의 경우 NaN을 반환하는 것 같다.

그냥 js 메서드인 number로 심볼을 변환하면 오류와 함께 어떠한 값도 반환하지 않는다.

 

/**
 * Check whether a value is a symbol.
 *
 * This function can also serve as a type predicate in TypeScript, narrowing the type of the argument to `symbol`.
 *
 * @param {unknown} value The value to check.
 * @returns {value is symbol} Returns `true` if `value` is a symbol, else `false`.
 * @example
 * isSymbol(Symbol.iterator);
 * // => true
 *
 * isSymbol('abc');
 * // => false
 */
export function isSymbol(value: any): value is symbol {
  return typeof value === 'symbol' || value instanceof Symbol;
}

 

isSymbol(v) 가 true면
if 블록 안에서 v는 symbol 타입으로 확정됨.

 

아래와 같은 심볼이 object인 경우에 판단이 안되기 때문에 이렇게 함수를 만든 것 같다.

typeof Object(Symbol("abc")) // "object"

 

 

자 어느정도 살펴보았으니 하이라이트인 테스트 코드를 살펴보자

양이 꽤 되서 내가 이해가 안되는 부분 위주로 가져와 보았다.

describe('toInteger', () => {
  it(`should preserve the sign of \`0\``, () => {
    const values = [0, '0', -0, '-0'];
    const expected = [
      [0, Infinity],
      [0, Infinity],
      [-0, -Infinity],
      [-0, -Infinity],
    ];

    [0, 1].forEach(index => {
      const others = values.map(index ? Object : identity);

      const actual = others.map(value => {
        const result = toInteger(value);
        return [result, 1 / result];
      });

      expect(actual).toEqual(expected);
    });
  });

 

toInteger(value)가 “+0”과 “–0”을 정확하게 구분해서 반환하는지 확인하는 테스트가 포함되어 있다.

 

 

오늘 느낀점

생각보다 메서드 자체의 이해가 그렇게 어렵지는 않은 것 같은데

예외 케이스에 대한 고민들이 정말 많이 느껴졌다..

테스트 커버리지가 적은 부분에서 찾아보면 좋을 것 같다.

 

반응형

'es-toolkit' 카테고리의 다른 글

[es-toolkit] 기여 도전하기 2일차  (0) 2025.11.28