[GAS]Enumってなに?(中級編)

EffectiveGoogleAppsScript

どうも。つじけ(tsujikenzo)です。このシリーズでは、Enum(イーナム)について初級・中級の2回に渡ってお送りします。

2021年最後の投稿となりました。本年はたくさんの方の応援があり、年間170本の記事をUPしました。

来年もたくさん考察したいと思います。

前回のおさらい

さて、前回は、Enum初級編をお送りしました。そもそもEnumとはなにか、呼び出し方などを学びました。

今日は2回目で、Enum中級編についてお届けします。

今日のアジェンダ

  • Enumを定義しよう
  • Enumを凍結しよう
  • Enumを強制しよう

Enumを定義しよう

前回、ファイルのコンテントタイプを呼び出すEnumは、このように実装されているだろう、というイメージをシェアしました。

const MimeType = {
  JPEG:'JPEGとは何かを定義するありとあらゆる値',
  GIF:'GIFの定義',
  PDF:'PDFの定義'
}

console.log(MimeType.JPEG); // => 'JPEGとは何かを定義するありとあらゆる値'

Enumを使う理由は、プロジェクト全体で、定義が複数あったり、バラつきを防ぐためです。

JPEGの定義があちこちにあると、プロジェクトは破綻しますよね。

ここでは、注文情報に必要な、送料のEnumを定義してみましょう。

Enumは、プロジェクト全体から呼ばれるため、グローバル領域に定義することが多いでしょう。

/** グローバル領域 */

  /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const Soryo = {
    HOKKAIDO: 1800,
    TOHOKU: 1500,
    KANTO: 1200,
    KANSAI: 1200,
    KYUSYU: 1500
  };

function myFunction211231_01() {

  /** Enum Soryoから送料を返す関数 */
  const getShippingCost = area => Soryo[area];

  // 注文情報
  const purchaseOrder = {
    productName: 'サバ味噌缶',
    price: 1000,
    tax: 0.8,
    customerName: 'etau',
    shippingCost: getShippingCost('KANTO')
  }
  
  // 送料の確認
  console.log(purchaseOrder.shippingCost); // => 1200

}

しかしながら、このままだと、Enumはいともかんたんに書き換えられてしまいます。

/** グローバル領域 */

 /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const Soryo = {
    HOKKAIDO: 1800,
    TOHOKU: 1500,
    KANTO: 1200,
    KANSAI: 1200,
    KYUSYU: 1500
  };

function myFunction211231_02() {

  //Enumを書き換える
  Soryo.KANTO = 0;

  // Enum Soryoから送料を返す関数
  const getShippingCost = area => Soryo[area];

  // 注文情報
  const purchaseOrder = {
    productName: 'サバ味噌缶',
    price: 1000,
    tax: 0.8,
    customerName: 'etau',
    shippingCost: getShippingCost('KANTO')
  }

  // 送料の確認
  console.log(purchaseOrder.shippingCost); // => 0

}

Enumを凍結しよう

組み込みオブジェクトのObjectオブジェクトには、オブジェクトを凍結する、Object.freeze()メソッドが提供されています。

書き換えができないように、定義したEnumを凍結してしまいましょう。

/** グローバル領域 */

  /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const Soryo = {
    HOKKAIDO: 1800,
    TOHOKU: 1500,
    KANTO: 1200,
    KANSAI: 1200,
    KYUSYU: 1500
  };

  //オブジェクト凍結する
  Object.freeze(Soryo);

function myFunction211231_03() {

  //Enumを書き換える
  Soryo.KANTO = 0;

  /** Enum Soryoから送料を返す関数 */
  const getShippingCost = area => Soryo[area];

  // 注文情報
  const purchaseOrder = {
    productName: 'サバ味噌缶',
    price: 1000,
    tax: 0.8,
    customerName: 'etau',
    shippingCost: getShippingCost('KANTO')
  }

  // 送料の確認
  console.log(purchaseOrder.shippingCost); // => 1200

}

Enumを強制しよう

本来Enumとは、プロジェクト全体で、唯一無二の値の定義であるはずです。

以下のように、同名のプロパティや値が存在することは、あまり好ましくありません。

  /** グローバル領域 */

  /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const Soryo = Object.freeze({
    HOKKAIDO: 1800,
    TOHOKU: 1500,
  });

function myFunction211231_04() {

  /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const ShippingCost = Object.freeze({
    HOKKAIDO: 1800,
    TOHOKU: 1500,
  });

  //同名のプロパティ、値が存在することは、本来のEnumの役割を果たしていない
  console.log(Soryo.HOKKAIDO === ShippingCost.HOKKAIDO); // => true(どちらも1800)

}

開発規模が大きくなればなるほど、「送料は必ずEnumから取得してください」というコーディングルールを守らずに、サボるメンバーが出ます。

  /** 
   * 送料を列挙するEnum
   * @enum {number} 
   */
  const Soryo = Object.freeze({
    HOKKAIDO: 1800,
    TOHOKU: 1500,
  });


  //人はサボろうとする
  const po1 = { purchaseOrderId: 'po001', shippingCost: 1800 };

そこで、プロパティの値にSymbol型を定義します。

  /** 
   * 送料を列挙するEnum
   * @enum {Symbol} 
   */
  const Soryo = Object.freeze({
    HOKKAIDO: Symbol(1800),
    TOHOKU: Symbol(1500),
  });

Symbol型は唯一無二の値ですので、送料をEnumから取得していなければ、例外を吐くように実装できます。

/** グローバル領域 */

  /** 
   * 送料を列挙するEnum
   * @enum {Symbol} 
   */
  const Soryo = Object.freeze({
    HOKKAIDO: Symbol(1800),
    TOHOKU: Symbol(1500),
  });

function myFunction211231_05() {

  //サボったコード
  const po2 = { purchaseOrderId: 'po002', shippingCost: 1800 };

  //Enumから取得していなければ例外を吐く
  if (po2.shippingCost === Soryo.HOKKAIDO) {
    console.log('ちゃんとEnumから取得してますね');
  } else {
    throw new Error('送料は必ずEnumから取得してください');
  }

}  // => Error: 送料は必ずEnumから取得してください

Symbol型のラベル値を使用するさいは、型を変換するなど、ひとてま掛かりますが、バグを早期に発見するために役立つかもしれません。

/** グローバル領域 */

  /** 
   * 送料を列挙するEnum
   * @enum {Symbol} 
   */
  const Soryo = Object.freeze({
    HOKKAIDO: Symbol(1800),
    TOHOKU: Symbol(1500),
  });

function myFunction211231_06() {

  //サボったコード
  const po2 = { purchaseOrderId: 'po002', shippingCost: 1800 };

  //Enumから取得していなければ例外を吐く
  try {

    if (po2.shippingCost === Soryo.HOKKAIDO) {
      console.log('ちゃんとEnumから取得してますね');
    } else {
      throw new Error('送料は必ずEnumから取得してください');
    };

  } catch (e) {
    console.log(e); //[Error: 送料は必ずEnumから取得してください]
  }

  //Enumから取得する
  const po3 = { purchaseOrderId: 'po003', shippingCost: Soryo.HOKKAIDO };

  if (po3.shippingCost === Soryo.HOKKAIDO) {
    console.log('ちゃんとEnumから取得してますね');
  } else {
    throw new Error('送料は必ずEnumから取得してください');
  }; //ちゃんとEnumから取得してますね


  //中身のラベルの確認
  console.log(Soryo.HOKKAIDO.description); //1800
  console.log(typeof Soryo.HOKKAIDO.description); //string

  //使う時はNumberに
  console.log(typeof Number(Soryo.HOKKAIDO.description)); //number
}

まとめ

以上で、「Enumってなに?(中級編)」をお送りしました。

開発規模がちいさいうちは、Enumオブジェクトの凍結だけでもかまわないと思います。

Symbol型の実装は、開発メンバーが増えたり、規模がおおきくなったさいなどに検討してみましょう。

参考資料

このシリーズの目次

タイトルとURLをコピーしました