大量のメソッドを保有し、数千、数万行単位にぶくぶく膨れ上がった巨大クラス。別名「神クラス」とも「大きな泥団子」とも呼ばれる、長大で複雑で、様々なクラスと密結合で極めて変更が困難なアイツ。
そんな巨大クラスの退治に有効な、命名に関する考え方を紹介致します。
解決したい課題、狙う効果
- 数千、数万行単位の巨大クラスの登場を抑止する。
- 巨大クラスを爆砕し、小さなクラス群に分割する。
- クラス結合度を下げ、影響範囲を小さくすることで保守コストや変更コストを下げる。
ダメな例
例えばECサイトの「商品」を考えてみます。
よくありがちなのは、商品をそのまま「商品クラス」と設計してしまうこと。
単純な商品クラスは、往々にして出品、予約、注文、発送など、様々なユースケースのクラスと結合してしまいがちです。
商品クラス自体も、結合したクラスに関連する知識(ロジック)を持ち始め、どんどん巨大化複雑化していきます。
仕様変更時にやってくる開発生産性低下
こうして巨大化した商品クラスに対し仕様変更が発生した場合どうなるでしょうか?
変更影響により動作に不具合が生じないか、商品クラスに関連のあるクラス全てをチェックしなければならなくなることがよくあります。影響範囲が広すぎるために、開発生産性の低下を招いています。
密結合
商品クラスはなぜこんな状態になっているのでしょうか?よく見てみましょう。
商品の周囲には、予約、注文、発送など様々な関心事が取り巻いています。
そして商品クラスはそれら様々な関心事と結び付き、結びついた関心事の知識(ロジック)を持ってしまっています。
即ち密結合状態になっているのです。
関心の分離
密結合を解消し、疎結合高凝集にするためには「関心の分離」が重要です。
関心の分離とは「関心事、つまりユースケースや目的、役割ごとに分離する」というソフトウェア工学における考え方です。
関心事の単位でクラスを分割する
つまり商品クラスは関心事それぞれのクラスへ分割が必要、と考えられます。
では、例えば以下のように関心事それぞれに商品クラスを分割してみます。
関心事に相応しい命名をする
分割はしてみましたが、全てのクラスに「商品」と命名はできません。名前が重複します。
一体どうすればいいのでしょうか。違う名前で呼べないか検討してみます。
例えば注文の関心事に関わる商品は他に何と呼ぶことができるでしょうか?
「注文品」と呼ぶのはどうでしょう。
同じ要領で、予約では予約品、発送では発送品と呼び替えてみました。
あとは関心事に分離したクラスそれぞれに、各関心事に相応しいロジックをカプセル化すれば良いでしょう。例えば「注文品」クラスならば、商品注文に関するロジックだけを内部にカプセル化すれば良いです。
(※在庫品、予約品、注文品、発送品の一意性は、同じユニークIDを使うことで解決します。)
影響範囲の低減
こうすることで、関心の分離により疎結合高凝集が果たされます。
関心事それぞれでクラスを疎結合高凝集にしておけば、例えば注文に関して仕様変更が生じた場合、注文関連のクラスだけに注意を払えばよくなります。影響範囲が低減し、開発生産性が向上します。
陥りがちな罠 - 大雑把で意味がガバガバな名前
開発初期に決めた名前が大雑把であることが多いです。
大雑把だと何がマズいのでしょう?
開発現場では下記のようなことが非常に多く見受けられます。
社員A「今度の仕様変更で、開発中のECサイトに予約機能が追加される。予約のロジック追加が商品周りでも必要だと思うけど、どこに実装しよう?」
社員B「商品クラスが既にあるじゃないか。商品クラスに実装しちまえよ。」
「商品」という名が大雑把すぎて、商品に関するあらゆるロジックが実装できそうに見えてしまいます。
大雑把で意味がガバガバな名前は、あらゆるロジックを引きつけてしまう、強力な心理的引力が働きます。
ダメな例の「商品クラス」とその周囲のクラスを見てみると、「商品」という名前は実に曖昧で、様々な関心事に使われやすい「いい加減な名前」であることが分かります。
そして、いい加減で曖昧な名前を付与されたクラスは、あっという間に巨大化してしまいます。
こうした事態に陥らないためには、関心の分離を意識した名前を設計する必要があります。
名前を設計する
私はクラスやメソッドを名付けることを、「命名する」ではなく「名前を設計する」と好んで呼称しています。ここでの設計は「ある課題を解決するための仕組みや構造を考えたり作り上げる」と定義します。
プログラミングにおける名前は、書籍「リーダブルコード」にあるような可読性を高めるためだけではないと私は考えます。
意味範囲がガバガバな名前ではなく、関心の分離に貢献する名前を付与することは、疎結合高凝集を実現する上で重要であるため、「名前を設計する」と意図して呼称しています。
設計のポイント
では名前設計する上での重要なポイントを以下に列挙します。
可能な限り具体的で、意味範囲が狭い、特化した名前を選ぶ
私が本記事で最も主張したいポイントはここです。
特定の関心事でしか用いられないような、極めて意味範囲の狭い名前をクラスに付与することで、以下のような効果が生まれます。
- 名前とは無関係なロジックを排除しやすくなる。
- クラスが小さくなる。
- 関係するクラスの個数が少なくなる(クラス結合度低減)。
- 関係クラス個数が少ないので、仕様変更時に考慮を要する影響範囲が小さく済む。
- 関心事に特化した名前であるために、どこを変更すれば良いか直ぐ探し出せる。
- 開発生産性が向上する。
例えば「金額」という曖昧でどうとでも解釈可能な、意味範囲ガバガバな名前ではなく、請求金額、消費税額、延滞保証料、シルバー割引料金など、個別具体的な名前を用い、クラスに命名しましょう。
どんな関心事があるかドメイン分析する
各関心事に特化した命名をするには、どんな関心事があるか網羅する必要があります。
そのために、ソフトウェアで解決したいビジネスドメインに関しての分析を要します。
ユースケース分析やドメインモデリングなどによりドメイン分析してみましょう。
声に出して話してみる
人が脳内で考えていることは意外なほどぼんやりしていてハッキリしないものです。
関係者、特にビジネス面のことをよく知っている人(DDDで言うドメインエキスパート)と話し合ってみましょう。
名前に違和感がある場合は会話中すぐフィードバックをもらえますし、より正確で個別具体的な名前を引き出せることもあります。新たなビジネス要件が得られることも多々あります。つまり、会話それ自体がリアルタイムドメイン分析行為になるのです。
積極的に話し合い、会話の中に「特化した名前」がないか注意深く耳をそばだてましょう。そして名前や関心事を集めていきましょう。
ある関心事だけに特化した名前を選ぶ
上の例でも示したように、注文の関心事なら「注文品」、発送の関心事なら「発送品」など、ある関心事だけで使われる特別な名前を選びましょう。関心事以外のロジックを寄せ付けにくくします。関心事だけのロジックが集まりやすくなり、高凝集になります。
利用規約を読んでみる
利用規約には、製品の取り扱いやルールに関して極めて厳密な言い回しで書かれており、特化した名前の参考になります。もちろん利用規約記載の言葉をそのまま使うのもOKです。
製品の利用規約を参考にしてみましょう。
違う名前に置き換えられないか検討する
折角選んだ名前の意味範囲が十分に小さくなかったり、複数の意味を持ってしまっている可能性が多々あります。
違う名前に置き換えてみて、意味をもっと狭くできないか、違和感がないかなど検討してみましょう。
辞書を活用する
私自身語彙が乏しいので、国語辞典など各種辞書サイトを活用しています。
上記「違う名前に置き換えられないか検討する」上では、類語辞典が活躍します。
他の概念といくつ関係付いているか個数を気にする
名前を選んだ結果、それが他の概念といくつ関連付いているか個数を確認しましょう。
上で挙げた商品クラスのように、何個も関連付いているのはダメです。
個数が多い場合は、ブラッシュアップしましょう。複数の意味を持っているなら分解したり、もっと狭い意味の、特化した名前を探してみましょう。
関連個数が少なければ少ないほどクラス結合度が下がり、影響範囲が低減し、開発生産性が向上します。
設計時の注意すべきリスク
名前設計において注意すべき点がいくつかあります。
仕様変更時の「意味範囲の変化」に警戒
度重なる仕様変更に伴い、言葉が意味するところがどんどん変化していきます。
例えば開発初期に顧客クラスがあり、そのクラスは「個人顧客」を表現したものだったとします。ところがその後の仕様変更により「法人顧客」も扱うようになり、法人について回る登記番号や組織名が顧客クラスに紛れ込んでしまう…個人顧客なのか法人顧客なのか、顧客クラスの中でロジックが混乱してしまう、ということもあり得るわけです。
仕様変更時には、言葉の意味合いに変化がないか十分に注意を払いましょう。異なる意味合いが混入しそうな場合は名前が意味するところを見直したり、名前をリファクタリングしたり、クラスを別々に分けるなどするのが肝要です。
同じ名前でもコンテキストにより意味や扱いが異なる
状況によって言葉は意味合いが変わります。
例えば「アカウント」という言葉は、金融業界では「銀行口座」を意味する一方で、コンピュータセキュリティでは「ログイン権限」を意味します。状況(コンテキスト)により意味合いが異なるのです。
アカウントの例はまだ分かりやすい方です。
目的や状況によってモデリングの仕方、ついて回る概念が全く異なることがあります。例えば自動車に関して、状況(コンテキスト)が違うと、「自動車クラス」の構造には大きな違いが生まれます。
- 配送コンテキスト:自動車が貨物として配送されるコンテキスト。配送元、配送先、配送経路などが自動車クラスについて回る。
- 販売コンテキスト:ディーラーにより顧客に自動車が販売されるコンテキスト。販売価格、販売オプションなどが自動車について回る。
コンテキストが異なるものをたったひとつの自動車クラスに実装すると、自動車クラスに複数のコンテキストのロジックを抱えることになり、巨大化複雑化し、開発者は混乱します。
どんなコンテキストが取り巻くかドメイン分析した上でコンテキストごとに境界付け、コンテキスト単位で意味するところ、即ち意味範囲に応じたクラス設計することが肝要です。この辺の知見はドメイン駆動設計が詳しいので、興味のある方は学んでみましょう。
技術駆動ネーミングに陥らないよう注意
「Int~」「Str~」「~Memory」「~Thread」「~Flag」「Update~」などプログラミング用語やコンピュータ用語で命名してはいけません。ビジネス的に何を解決したいクラスやメソッドなのか非常に分かりにくくなります。
こうした技術駆動な命名はビジネス的な意味範囲を何も伝えず、関心の分離に貢献しません。
これはプログラミングだけに強い興味があり、ビジネスにあまり興味がないプログラマによくありがちな傾向です。
「予約」「注文」「気になる商品」などビジネスの言葉を使いましょう。
ドメインモデル貧血症に陥らないよう注意
意味の粒度を細かく分けすぎてしまった結果、「~情報」や「~データ」など情報を表現する名前になることが度々見受けられます。しかしこれはやってはいけません。こうしたクラスは得てして「OrderInfo」や「PersonData」などと名付けられ、そして名前の印象からデータしか持たないクラスになります。
こうしたデータクラスはアンチパターン「ドメインモデル貧血症」であり、データを操作するロジックがあちこちに散在してしまいます。低凝集に陥ります。
オブジェクト指向設計では、
- インスタンス変数
- インスタンス変数を操作するロジック
これらを同じクラス内に隠蔽することで様々な品質向上効果が得られることが基本にあります。
この基本から外れた名前を選択しないようにするのが肝要です。
名前無頓着になるな
本記事の考えは、「名前に注意を払い、名前とロジックを対応付ける」ことを前提としています。従って、名前に無頓着だと全てが瓦解します…。
まことに残念でありますが、名前にほとんど全く注意を払わない、無頓着なプログラマが一定数いるのは事実であり、その場合まずプログラミングにおける命名の重要性を理解してもらう、という課題を乗り越えて頂く必要があります。
名前とロジックが対応する前提であることを、チーム内で約束しましょう。
余談
よくある剣と魔法のファンタジーRPGの終盤では、たびたび下記ようなやり取りが見受けられます。
「人間」では言葉の粒度が大きすぎます。
「悪い人間」「心優しき人間」と形容詞を付けて特化し、関心を分離することが肝要です。
特化こそ正義 半端な汎用化は開発者の心身を滅ぼす
以上解説したように、特化した名前、特化したクラスであるほどクラスが小さくなり、影響範囲が小さくなり、開発生産性が向上します。
逆に下手に汎用性を持たせたり、非特化で曖昧な名前、構造にすると、影響範囲がどんどん拡大します。仕様変更やバグ対応のたびに影響範囲の調査に何日も何週間も費やし、疲弊し、心身を滅ぼしていきます。
体力任せ、力技でやってきた脳筋プログラマは、疲労と心身の衰えとともに「35歳定年説」通りに引退を余儀なくされるでしょう。
快適に開発し、エンジニアライフを楽しみたいならば、特化を目指しましょう。