🗃️

プリミティブ型を使うのをやめる

2024/02/04に公開

目次

  1. 概要
  2. プリミティブ型とは
  3. サンプルコード
  4. プリミティブ型を使うのをやめる
  5. まとめ

概要

今回は普段皆さんが使用しているプリミティブ型を使うのをやめることのメリットについてサンプルコードを交えてまとめてみました。

プリミティブ型とは

プリミティブ型とは皆さんが良く使用しているnumber型やstring型、boolean型などの型のことです。
サバイバルTypeScript では以下のように記載してあります。

プリミティブ型は次の7つがあります。

  1. boolean型(論理型): trueまたはfalseの真偽値。
  2. number型(数値型): 0や0.1のような数値。
  3. string型(文字列型): "Hello World"のような文字列。
  4. undefined型: 値が未定義であることを表す型。
  5. null型: 値がないことを表す型。
  6. symbol型(シンボル型): 一意で不変の値。
  7. bigint型(長整数型): 9007199254740992nのようなnumber型では扱えない大きな整数型。

上のプリミティブ型以外は、JavaScriptにおいてはすべてオブジェクトと考えて問題ありません。配列や正規表現オブジェクトなどもすべてオブジェクトです。

サンプルコード

今回は何かのアプリケーションのユーザーについてのコードをサンプルにします。

class User {
  constructor(
    readonly id: number,
    readonly name: string
  ) {}
}

このUserクラスはユーザーIDとユーザーの名前をプロパティとして持っています。

例えば、ユーザーIDからDBのユーザーテーブルのレコードを取得したい場合があるとします。
DBのユーザーテーブルのidカラムは主キーで1以上の整数とします。

const findUser = (id: number): User => {
  const user = db.user.find({
    where: {
      id: id,
    },
  });

  return new User(user.id, user.name);
};

この関数はプリミティブ型の1つであるnumber型のidを引数に取りますが、ユーザーIDは「1以上の整数」です。
もし「1以上の整数」以外のnumber型の値が引数となった場合、想定外の例外が発生するかも知れません。

// WARN:想定外の例外が発生!!!
const User = findUser(0);
const User = findUser(2.7);
const User = findUser(-10);

しかし、この関数は引数のidが「1以上の整数」であることを保証していないため、この関数を使って書いたコードは想定外の例外を防ぐことが出来ない危険なコードとなるかも知れません。

また、仕様上おかしな値がシステムに混入することを避けることも出来ません。

プリミティブ型を使うのをやめる

Userクラスのidプロパティに対してプリミティブ型を使うのをやめてみましょう。

class User {
  constructor(
    readonly id: UserId,
    readonly name: string
  ) {}
}

class UserId {
  constructor(
    readonly value: number
  ) {
    if (value < 1) throw new Error('error:idが1より小さいです。');
    if (!Number.isInteger(value)) throw new Error('error:idが整数ではありません。');
  }
}

新たにUserIdクラスを追加してUserクラスはnumber型のidではなく、UserIdクラスのidをプロパティとして持つようになりました。

また、findUser関数でもプリミティブ型を使用することをやめます。

const findUser = (id: UserId): User => {
  const user = db.user.find({
    where: {
      id: id.value,
    },
  });

  return new User(new UserId(user.id), user.name);
};

この関数では「1以上の整数」以外のnumber型の値を引数として取ろうとすると例外が発生します。

const User = findUser(new UserId(0));
// error:idが1より小さいです。
const User = findUser(new UserId(2.7));
// error:idが整数ではありません。
const User = findUser(new UserId(-10));
// error:idが1より小さいです。

この例外の発生はあらかじめ想定していた例外のため、try/catchなどで例外を捕捉し想定したエラーハンドリングに繋げることが可能です。
また、ユーザーIDの仕様上おかしな値がシステムに混入することを避けることが出来ます。

さらにプログラマーはUserIdクラスのコードを読むことでユーザーIDが「1以上の整数」であるという仕様を理解することが出来ます。

まとめ

プリミティブ型を使うのをやめることのメリットについては以下の3点が挙げられると思います。

  1. プリミティブ型では表現出来ない仕様を表現出来る
  2. 仕様上おかしなデータがシステムに混入することを防ぐことが出来る
  3. コードを読むことで仕様を理解することが出来る

より安全で可読性の高いコードを書く上でプリミティブ型では不十分な場合があるので、積極的にプリミティブ型を使うのをやめてみましょう。

また、型とは別の観点でサンプルコードを読むと、UserIdクラスはユーザーIDの仕様をまとめて管理しています。
そのためユーザーIDの仕様に関するコードの重複や分散を防ぐといったメリットもあります。
このような考え方はオブジェクト指向プログラミングやDDDにおけるエンティティや値オブジェクトといった概念に繋がります。

もしこれらの概念に興味のある方がいたら勉強してみてください。

参考書籍

https://www.oreilly.co.jp/books/9784814400331/
https://www.shoeisha.co.jp/book/detail/9784798150727

Discussion