データ構造とマスタ定義についてのおはなし

現在主に、PHPのリファクタリング業務をやっているTDの堀田です。
マインクラフトをやらせたら一日中地面を平らにして道を引く作業をしているタイプです。

早速本題に入っていきますが、まずは説明から。

マスタって何?

次のような構造のデータを処理しろっていう話、プログラマにはよくある話ですよね。
まあプログラマじゃない人も見ていると思うので、よくあるんです。

[type:1 name:ライオン]
[type:1 name:トラ]
[type:2 name:ゾウ]
[type:2 name:ウサギ]

このtypeを、1 = 肉食、2 = 草食...といった形で分類・ラベリングします。
簡単に言ってしまえばこの番号と名前のデータがマスタです。
ちなみにライオン、トラ、というデータにも番号を振れば、それもマスタになりますね。

経緯

このラベリングなんですが、意外とどんな方法でも振り分け処理できてしまいます。
1から100までとか、足し算とかの結果出力プログラムを計算してそのまま書いてしまうなんて笑い話は多いですが、これもfor文(繰り返し処理)のなかにif文(分岐)で書くの割とあるあるですよね。

本当にやめて欲しい。

え、ダメなの?と思った人、とりあえず次の項目まで読んでください。お願いします。

少し話が逸れましたが、この振り分け処理は定義も合わせてどこにまとめるのか決めなければいけません。
調べてみたんですが、各選択肢でこう設計するべきな記事はあったけれどどの選択肢にするべきという記事があまり見当たらなかったんですよね。

概念的なものなので検索しづらいということもあるとは思うんですが、悩んでいる人がこの記事を見つけたら少しでも助けになればいいと思います。

何となく作っていたという人も引き出しが増えれば嬉しいです。

種別

(1) ON/OFF、男女
変更が想定されないような数種類までのデータ

(2) 都道府県
変更がほぼ想定されない数十種類程度までのデータ

(3) カテゴリ、ラベル、ステータス
変更や追加が想定される多種類のデータ

経緯でも触れましたが、増える可能性が少しでもある場合は(1)でない方が無難です。
男女のような概念でも多様化が進む時代なので、とりあえず(2)に分類した方がいいかもしれません。

そして、(1)以外はif文で書くと、増やすたびに全部の使用箇所を調べてプログラムを書き直さなければいけなくなります。
都道府県を書き直すことはあまりないと思いますが、番号がずれたら全部修正するの大変ですよね。
面倒かもしれませんが、番号と名称の対応は最初にまとめてどこかに定義しておきましょう。

方法

ここからは完全にプログラム設計のおはなしになります。
ただラベリングするだけなのにたくさん方法があるんだなあと思ってください。

◆ データベース(マスタ)
ユーザーに自由に定義してもらう場合には、この方法でほぼ一択です。
変更削除がある場合、マスタというよりもはやテーブルですが拡張性は非常に高くできます。

[長所]
・種別(3)のような可変データに関しても柔軟に対応できる
・IDと名前以外に最終変更者や備考などの情報も持たせられる
[短所]
・表示のためにJOINまたは取得回数が増え、適切な設計をしないとDB負荷と処理速度に影響が出る

◆ データベース(IF,CASE文)
この場合が最適になることは非常に少ないかと思います。
種別(1)の場合でも、本当に要件が増えないのか、拡張性は考えておいたほうがいいです。

[長所]
・取得したデータをそのままフロント側(JSフレームワーク)へ渡したい場合に、プログラムで再処理する必要がない
[短所]
・見通しが悪いため、他と併用すると対応漏れが発生しやすい
・変更時にはSQLも修正が必要になる

◆ プログラム(定義ファイル)
この場合が最適になることも非常に少ないかと思います。
データベースが利用できないけれど運用者に管理してもらう必要があるような場合でしょうか。

[長所]
・ファイルで管理するのでコード内に文言が直接記載されない
・エンジニア以外にコード外で対応表(ファイル)を管理してもらうことができる
[短所]
・読み込みエラーとなった場合などの処理が必要になる
・ファイル形式や管理方法によっては、オペレーションミスでシステムに影響が出る可能性がある

◆ プログラム(クラス定数)
DBテーブルに対応する、アクセス処理などを実行するクラス(モデル)に定数でマスタを記載する方法です。
実際に表示する単語はリストで定義またはデータ取得処理実行時に付与します。

[長所]
・処理と同じ場所に記載あるため、マジックナンバーの抑止を期待できる
・処理時点でも最終的な表示内容の確認ができるため、理解しやすい
[短所]
・コード中に文言(場合によっては日本語)の定義が含まれる
・定義変更にプログラマの対応が必要になる
・記述が増えすぎると見通しが悪くなるため、大量の情報を保持するには向かない

◆ プログラム(Enum型クラス)
クラス定数とほぼ同じですが、定義を分けることになります。
複数箇所で同じステータスや分類を使う必要がある場合、共通のチェックが必要な場合にはこちらを検討した方がいいと思います。

[長所]
・クラス定数を更にパッケージ化して、複数クラスで共通の定義を使用できる
・想定外の値が入らないように処理を厳密・規則的にできる
[短所]
・コード中に文言(場合によっては日本語)の定義が含まれる
・定義変更にプログラマの対応が必要になる
・種類が多い場合、どこに定義があるのか、どのEnum型クラスに当てはまる値なのか、何を意味しているのかが直感的に分かりづらい

◆ プログラム(ビュー)
「表示の問題はフロント側(レンダリング)で解決すべき」
めちゃくちゃ正論っぽいんですが、残念ながらフレームワークの設計などによっては採用できません。
あくまで「表示のみ」のデータ種別に使うことになると思います。

[長所]
・役割として正しい部分で処理しているため、表示の調査時にファイルをたどる必要がなくなる
・サーバーサイドで単語や文言について思案する必要がなくなる
[短所]
・プログラム内の分岐処理と一箇所で定義を共有するのが難しい(スコープが広くなりすぎる)
・直感的に意味が分からないため、データや処理を理解するのにフロント側のファイルを確認しなければいけない
・CSVなどファイル生成が必要となる場合、出力機能がないと採用できない

◆ JS(フロント/テンプレートエンジン)
データ取得処理と表示用のファイルが別サーバーに置かれていたり、API利用など構造によってはここを(併用で)選択することになると思います。
最近では整備が進んだため対応ブラウザや環境による挙動の差は気にしなくていい程度だと思いますが、エンドユーザーのことを考えた上で採用が必要です。

[長所]
・大量のデータ、種別をやりとりする場合にサーバーや通信の負荷を減らすことができる
[短所]
・IDの管理がサーバー側とフロント側で二重に必要となる

◆ CSS(フロント)
要素に値を持たせて、属性セレクタで表示切り替えします。
状態によって色やアイコンも変えたいけれど、テンプレートエンジンが入っていないためclassの切り替えも複雑になる、という場合に使えるのではないかと思います。
こちらもエンドユーザーの環境によるので、表示内容が重要な場面には向かないと思いますがセットされたデータを変更するだけで表示切り替えできるので構成はシンプルです。

[長所]
・大量のデータ、種別をやりとりする場合にサーバー側の負荷を減らすことができる
・表示の切り替え処理をプログラムで管理する必要がない
[短所]
・IDの管理がサーバー側とフロント側で二重に必要となる
・結果によって追加でアラートを表示などの処理が必要であればあまりメリットがない

まとめ

きっかけとなった課題としては各処理ステータスを表すマジックナンバー*1をどうにかすることだったので、対応にはクラス定数を採用しました。
社内でも割と時間がかかる議論になったんですが、これといった決着はつかずとりあえず今回はこれが妥当といった感じ。

表示切り替えでCSS用のクラスを切り替えるのは割とあるので、最後の管理方法も個人的にはありなんじゃないかなと思います。

他にもこういった考え方や方法もあるなどあれば、コメント頂けると嬉しいです。

*1:マスタ番号の数字を直接指定していて、人間が読んでも何をしているのか判断が難しいプログラム