🍏

【型定義】良いコード悪いコードで学ぶ設計入門 まとめ 13章 モデリング

2023/11/20に公開

はじめに

よくおすすめされている著書、「良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方」を読んだのですが、本に書いてあったモデリングのところを実際に活用することができたので、本の内容の紹介と、活用したところを紹介します。

モデリングとは

そもそも、モデリングについて軽く紹介します。
調べて出てきたのは以下でした。

目的に合わせて、具体的な情報から共通する部分をみつけ、また不要な情報を取り除くことで注目すべき情報を抽象的に定義したもの。

こんな感じです。
「抽象的に定義したもの」が、クラスだったり、型だったりする感じのイメージです。
私がフロントエンドエンジニアというのもあり、普段クラスより型の方が使う場合が多いので、今回は型を例に進めていきます。

良いモデルの考え方

目的駆動で定義されていること

1つのものでも、いろいろな用途で使われます。

例えば、ユーザー情報だったら、写真を投稿する時にも使われますし、投稿に対するリプライにも使われます。
目的ではなく、"1つのもの"に対してモデル定義すると、Userという型ができますね。
User型は、写真を投稿する時のユーザー情報も、リプライする時のユーザー情報も詰め込まれることになります。詰め込まれている情報の種類が、2種類あるということになります。

このような状態で、リプライする時のユーザー情報の項目が1つ増えたとしましょう。その時、User型を修正しますね。
User型には、リプライする時のユーザー情報の他に、写真を投稿する時のユーザー情報もあるので、修正する時に、どこを修正するのか分かりづらくなります。そして、うっかり写真を投稿する時に使うユーザー情報の項目を変えてしまうという可能性もあります。

こういった事が起こり、大変なことになる可能性が高いので、目的別で分けた方が良いということです。

1つのモデルが単一の目的で定義されていること

「目的駆動で定義されていること」と似たような考え方です。
1つのモデルを、複数の目的で定義してしまうと、複雑なコードになってしまいます。

1つ例を考えてみました。
例えば、インスタには、ユーザーがあります。
そして、ユーザー検索結果で使いたいユーザーの情報と、ポスト投稿で使いたいユーザーの情報があるとしましょう。
これらの複数の目的を、1つの型にまとめてしまうと、複雑になってしまいます。
検索結果で使いたい情報と、投稿で使いたい情報は違うので、
検索結果で使う場合に、投稿で使いたい情報は不要になるので、わざわざ排除する処理が必要になります。結構めんどくさいですよね。
また、どの情報が、どちらに使いたい情報なのか、分かりづらくなることもあると思います。

なので、1つのモデルにつき、1つの目的に定義しましょう。

モデルの見直し方

モデルの見直し方も本に書いてありました。
内容は以下です。

  • モデルが達成しようとしている目的を洗い出す
  • 単一目的単一モデルにしてモデリングする
  • 目的駆動の命名をする
  • モデルに目的外の要素が混ざっている場合、見直す
    一言で言うと、目的駆動でやれってことですねえ
    また、なんとなく必要そうな要素を適当に入れたりしてしまうこともあると思うんで、そういったものが無いように見直すというのも大事ですね。

実際に活用した例

ここから、実際に活用した例を紹介します。
1つの画面で使用する、講師情報の型定義をしたとき、以下の2種類の講師情報が必要でした。

  • シフト表に表示される講師情報
  • シフト表内の講師検索モーダルの検索結果で表示される講師情報

実際どんな型を定義するか、考えてみましょう。

before: はじめに書いたコード

はじめは、「講師情報」という1つの物に気を取られてしまい、型を1つにまとめて、オプショナルでなんとかしてしまおうと思ってました。

こんな感じ

export type Instructor = {
  id: number;
  name: string;
  role: 1 | 2 | 3 | 4;
  onlyShiftProperty?: boolean; // シフト情報だけで使う
  onlySearchResultProperty?: string; // 検索結果だけで使う
};

今思えば、なかなかクソ型定義な感じですね。。

複数の目的が1つの型にまとめられていることに気づく

ここで、複数の目的が1つの型にまとめられていることに気づきました。

after: 考え直して書いたコード

「講師情報」という1つの物体にとらわれず目的で考え直したところ、、、
こんな感じになりました。

export type ShiftInstructor = {
  id: number;
  name: string;
  role: 1 | 2 | 3 | 4;
  onlyShiftProperty: boolean;
};

export type SearchResultInstructor = {
  id: number;
  name: string;
  role: 1 | 2 | 3 | 4;
  onlySearchResultProperty: string;
};

beforeの場合だと、1つの型につき、onlyShiftPropertyが必要な場合と、onlySearchResultPropertyが必要な場合がどんな条件なのか、頭で考えてしまっていたところ、
afterの場合だと、型を2つに分けているので、そんなめんどくさいことを考える必要がなくなりました。
オプショナルの型がundefinedの時の処理も必要なくなるんで、コード量も減るでしょうね。

共通部分が多いので、omitとかpickとか&を使うのも良いかなと思いましたが、omitとかpick元の型の内容が変わったときに影響してしまうのが嫌なので、別々で定義した方が個人的に好きです。

こういうのは本読んだ方がいい

考え方とか設計手法的なものは、ググったり技術記事をさらっと読むだけだとあんまり定着しないのですが、本を読むと割と頭に残る感じがすると思いました〜

なんとなく本読んで終わりじゃなく、実際に活用できてよかったです。

Discussion