TECHNICAL BLOG

2021/5/26 # フロントエンド # 入門 2021/5 初めての設計で学んだ4つのTips

未経験でこの業界に入ってから約2年半が経ち、プログラミングにもだいぶ慣れてきましたが、これまで設計について深く考えながら開発をしたことはありませんでした。

今回は業務で初めて設計について学んだので、設計で僕が普段気をつけているポイントをまとめたいと思います。

名前設計

変数や関数には意味・処理内容が伝わる名前をつけるべきであるとよく言われるように、名前の付け方はとても重要です。

プログラミングをする人

ただし、設計の観点から見れば、それだけだと不十分です。名前は、意味・処理内容が最も端的に表現されているべきです。

例えば「従業員」を表現するのに「personWhoWorksForCompany(会社に勤める人)」といった変数を定義しなくても、「employee(従業員)」としたほうがスッと理解できるでしょう。

また、名前をつけることは関心の分離をすることでもあります。

例えば個人情報やその処理を扱うクラスを「Person」と定義したとします。すると、「従業員」や「顧客」といった区分があったとしても、両者とも「人」なので両方のロジックをPersonクラスに組み込むことができてしまいます。

このような実装になっていると、Personクラスの影響範囲が大きくなるので、仕様変更等で開発・保守の生産性が下がってしまいます。

この場合は「従業員」や「顧客」といった関心(役割)に応じてEmployeeクラス、Customerクラスとすることで、従業員・顧客に関するロジックの混在を防ぐことができます。

適切な名前をつける

名前設計についてはより詳しい解説がこちらに書かれているので、一読をおすすめします。

『関心の分離を意識した名前設計で巨大クラスを爆殺する』
https://qiita.com/MinoDriven/items/37599172b2cd27c38a33

状態管理

状態管理とは、主にフロント側の概念になりますが、その時々の状態をどこで・どのように保持していくかということです。状態はUIの表現に深く関わります。

状態の管理

最近のWebサイトはSPAで作られることが増えてきています。SPAはタブ切り替えやメニュークリック等のようにUIが動的に変わりますが、これは非同期でデータを取得したときなど、状態が変化したときに起こります。

例えば以下のタイミングで状態変化が起きます。

  • ログイン状態によるメニュー表示の切り替え
  • リアルタイムバリデーション
  • タブ切り替え

これらの状態変化に対応するためには、状態を適切に管理する必要がありますが、
僕は状態管理に失敗してとても動きの遅い画面を作ったことがあります。

それからは状態管理をする上で以下のポイントに気をつけました。

  • 管理場所:Reduxなどの状態管理ライブラリ、コンポーネントなど
  • 初期値:画面の初期表示に合わせた初期値にする
  • 状態の更新:状態変化できないケース、バリデーションなど
  • リセット:元の状態をどう保持しておくか

状態管理はとてもバグを生みやすい部分なので、一度状態管理に失敗してからは慎重に対応するようにしました。

責任の所在

単一責任原則という言葉はどこかで聞いたことがあるかもしれません。モジュールやクラス、関数を変更する理由が複数あってはいけないという原則です。

責任のある人

名前設計で例にあげたPersonクラスについて、単一責任原則の観点から再考してみます。Personクラスには従業員・顧客の両方のロジックを実装することができ、従業員・顧客という2つの責任(役割)を持つことになります。

すると、従業員・顧客の仕様変更があるたびにPersonクラスを修正する必要があります。つまり、責任(役割)の数だけ変更する機会が増え、Personクラスはとても脆い設計になります。

そのため、責任の観点で考えてみても、やはりPersonクラスはEmployeeクラスとCustomerクラスに分けたほうが良い設計と言えます。

名前設計で触れた関心の分離と通ずる部分はありますが、モジュールやクラスが担う責任を理解しながら実装することが大事になります。

責任に関してはこちらのサイトにより詳しく解説されているので、一読をおすすめします。

『役割駆動設計で巨大クラスを爆殺する』
https://qiita.com/MinoDriven/items/2a378a09638e234d8614

結合度

結合度とは、自分のモジュールと外部との関係性の強さのことです。この関係性が強い(密結合)と外部の変更によって自分のモジュールが影響を受ける場合があります。

影響範囲が広がると開発・保守コストが上がるため、一方の変更が他方へ影響しない状態(疎結合)が理想と言えます。

例1)結合度が高い実装

let username = 'hoge';
.
.
.
function alertMessage() {
    alert('Hello, ' + username);
}

例2)結合度が低い実装

let username = 'hoge';
.
.
.
function alertMessage(name) {
    alert('Hello, ' + name)
}

例1はメソッド内部の処理が外部の変数usernameに依存しているため、呼び出し側はusernameの状態に気をつける必要があります。つまり、usernameの変更があると直接影響を受けてしまいます。

例2はメソッド内部の処理は外部の変数usernameではなく引数に依存しているため、usernameの変更があっても直接は影響しません。言い換えると、usernameにどんな値が入ろうとも、最終的にalertMessageに正しい値を渡せるなら全く問題はありません。

これは一例ですが、他にもフラグを使って処理を制御する方法や、他モジュールの内部状態を直接参照する方法もあり、結合の仕方によってはさらに結合度が高くなります。

いずれにせよ、結合度を低くするにはいかに外部を意識しない実装にできるかがポイントになります。

最後に

良い設計は一朝一夕では身につきません。実際、自分では良い設計ができたと思っていても、しばらくして振り返ってみると全然ダメだった、ということがよくあります。

まだまだ満足に設計ができるとは言い難いので、より良い設計ができるようこれからも精進していきたいと思います。