見出し画像

type-festのExact型ユーティリティを使った型安全なリファクタリングフローの確立

まえがき

TypeScriptはコンパイル時に型チェックを行うことで、堅牢なアプリケーション構築を支援する強力なツールを提供しています。事前に定義されたインターフェイスに厳密に従う必要があるオブジェクトを扱う場合、完全な型安全を保証することは特に重要です。ここで「type-fest」のExactユーティリティが活躍します。この投稿では、Exactを使用してより厳格な型チェックを強制する方法を探ります。これは、保守性を向上させ、バグを減らすためのコードのリファクタリングに特に役立ちます。


Exactユーティリティの紹介

type-festExact型ユーティリティは、引数に与えたデータの型が期待する型と完全に同じ形状であることを保証します。余計なプロパティがないことが重要です。これは、使用されていないまたは必要のない余分なプロパティを持つオブジェクトを割り当てる一般的なミスを防ぐ場合に特に便利です。


デモコードとその説明

まずはデモコードのセットアップから見ていきましょう:

import { list } from "radash";
import type { Exact } from "type-fest";
import { ulid } from "ulidx";

type ReservationSlot = {
  id: string;
  name: string;
  isAssigned: boolean;
};

function createReservationSlot<T extends Exact<ReservationSlot, T>>(data: T) {
  return data;
}

上記のコードでは、ReservationSlot型を定義し、Exact型を使用してデータがReservationSlot型と正確に一致することを保証するcreateReservationSlot関数を実装しています。この関数は型ガードとして機能し、追加のプロパティが含まれていないことを確認します。


安全でないデータと安全なデータの生成例

Exact型の有用性を示すために、安全でないデータと安全なデータのサンプルを生成します。

安全でないデータの生成

export function generateUnsafeData(): ReservationSlot[] {
  return list(1, 3).map((d) => ({
    id: createId(),
    name: `予約枠-${d}`,
    type: "ReservationSlot",
    isAssigned: false,
  }));
}

ここでのgenerateUnsafeData関数は、ReservationSlot型の一部ではないtypeプロパティを誤って追加しています。これは、新しいプロパティが不注意に導入される典型的なリファクタリングシナリオです。

安全なデータの生成

export function generateSafeData(): ReservationSlot[] {
  return list(1, 3).map((d) =>
    createReservationSlot({
      id: createId(),
      name: `予約枠-${d}`,
      isAssigned: false,
    })
  );
}


一方、generateSafeData関数はcreateReservationSlotを使用して、各オブジェクトがReservationSlot型に正確に一致することを保証します。createReservationSlotに追加のプロパティを持つオブジェクトを渡そうとすると、TypeScriptエラーが発生し、システムを通じてエラーが伝播するのを防ぎます。

まとめ

type-festExactユーティリティを使用すると、オブジェクトがその定義された型に厳密に遵守することを保証する強力な方法が提供されます。このアプローチは型安全性を大幅に向上させ、クリーンで予測可能なコードベースを維持するのに役立ちます。TypeScriptのリファクタリングワークフローにExactを統合することで、オブジェクトの形状不一致に関連する一般的なバグを防ぎ、コードをクリーンで保守可能な状態に保つことができます。


あとがき

Exactを利用すると、純粋関数に切り出したほうが型安全性に気が付くフローを生むので、自然とテストしやすくなり、テストも書きやすくなり、品質が向上します。

最後に、デモコードになります。簡単ですが、以上です。



import { list } from "radash";

import type { Exact } from "type-fest";
import { ulid } from "ulidx";

function createId() {
  return ulid();
}

type ReservationSlot = {
  id: string;
  name: string;
  isAssigned: boolean;
};

function createReservationSlot<T extends Exact<ReservationSlot, T>>(data: T) {
  return data;
}

export function generateUnsafeData(): ReservationSlot[] {
  return list(1, 3).map((d) => ({
    id: createId(),
    name: `予約枠-${d}`,
    type: "ReservationSlot",
    isAssigned: false,
  }));
}

export function generateSafeData(): ReservationSlot[] {
  return list(1, 3).map((d) =>
    createReservationSlot({
      id: createId(),
      name: `予約枠-${d}`,
      type: "ReservationSlot",
      isAssigned: false,
    })
  );
}



この記事が気に入ったらサポートをしてみませんか?