[TYPESCRIPT] Variadic Tuple Types를 실무에서 쓰는 경우

함수를 만들다 보면 인자 개수가 고정되지 않는 경우를 자주 만나게 됩니다. 로그 함수나 공통 래퍼, 미들웨어처럼 호출 방식은 비슷한데 상황에 따라 인자가 달라지는 코드들입니다.

 

이런 코드는 보통 빠르게 다음 형태로 작성됩니다.

  • rest parameter를 쓰고 any[]로 처리한다
  • 타입은 나중에 보강하자는 전제로 둔다
  • 호출부에서 잘 쓰면 된다고 생각한다

 

문제는 이 상태가 오래 유지된다는 점입니다. Variadic Tuple Types는 이런 코드에서 인자 구조를 완전히 잃지 않기 위해 등장한 기능입니다.

 

가변 인자에서 타입이 사라지는 지점

다음 코드는 실제 코드베이스에서 자주 보게 됩니다.

 


function log(...args: any[]) {
  // ...
}

 

호출하는 쪽에서는 편합니다. 하지만 이 시점부터는 타입이 해줄 수 있는 일이 거의 없습니다.

  • 첫 번째 값이 무엇을 의미하는지 알 수 없고
  • 순서가 중요한지 아닌지도 알 수 없고
  • 잘못된 호출도 그대로 통과됩니다

 

가변 인자 자체가 문제라기보다는, 구조가 타입에서 완전히 빠져버리는 것이 문제입니다.

 

앞쪽이 고정된 경우

실무에서 가장 흔한 형태는 앞쪽 인자는 의미가 정해져 있고, 뒤쪽만 가변인 경우입니다.

 


type LogArgs = [
  level: 'info' | 'error',
  ...messages: string[]
];

function log(...args: LogArgs) {
  const [level, ...messages] = args;
}

 

이 정도만 해줘도 상황은 꽤 달라집니다.

  • 첫 번째 인자는 로그 레벨이라는 게 바로 보이고
  • 그 이후는 메시지라는 점이 자연스럽게 드러납니다

 

유연함은 유지하면서, 의미 있는 정보만 남긴 상태입니다.

 

래퍼 함수에서의 사용

Variadic Tuple Types는 기존 함수를 감싸는 코드에서 특히 유용합니다.

 


function withTiming<Args extends unknown[], R>(
  fn: (...args: Args) => R,
): (...args: Args) => R {
  return (...args: Args) => {
    const start = Date.now();
    const result = fn(...args);
    console.log(Date.now() - start);
    return result;
  };
}

 

이렇게 작성하면 원본 함수의 인자 구조가 그대로 유지됩니다. 래퍼를 씌웠다는 이유로 호출부 타입을 다시 정의할 필요가 없습니다.

 

실무에서 공통 로직을 추가할 때 이 차이는 꽤 크게 느껴집니다.

 

미들웨어나 체인 구조

미들웨어처럼 앞뒤로 같은 인자가 흘러야 하는 코드에서도 비슷한 문제가 반복됩니다.

 


type Middleware<Args extends unknown[]> =
  (next: (...args: Args) => unknown) =>
    (...args: Args) => unknown;

 

이렇게 정의해두면 중간 단계에서 인자를 빼먹거나 형태를 바꾸는 실수를 하기 어려워집니다. 체인이 길어질수록 이 차이는 더 분명해집니다.

 

실무에서 자주 어긋나는 경우

  • 모든 함수를 가변 튜플로 일반화하는 경우
  • 고정 구조인데도 가변으로 표현한 경우
  • 도메인 로직 안까지 이 패턴을 가져간 경우

 

이런 상황에서는 타입이 코드를 설명해주기보다는 읽는 데 걸림돌이 되는 경우가 많습니다.

 


 

Variadic Tuple Types는 가변 인자를 더 자유롭게 쓰기 위한 기능이라기보다는, 이미 존재하는 인자 규칙을 타입에 남기기 위한 기능에 가깝습니다.

 

함수 래퍼나 미들웨어처럼 인자가 그대로 전달되어야 하는 경계 코드에서는 이 기능이 꽤 도움이 됩니다. 반면 도메인 로직 안쪽까지 적용하면 얻는 것보다 불편해지는 경우가 더 많습니다.

 

가변 튜플은 필요한 위치에서만 쓰고, 그 범위를 넘기지 않는 쪽이 실무에서는 가장 무난합니다.