まつうら まつうら@rubellum

ソフトウェア設計の3つのヒント:変動性、要件、機能の捉え方

はじめに

最近、チームメンバーとソフトウェア設計について話す機会が増えました。開発はそつなくこなせるけれど、設計については「どう考えればいいのかわからない」という声を聞くことがあります。

その相談について考えていたとき、ライティングソフトウェアに記載されているソフトウェア設計のヒントの話が一番伝えたいと思い、記事にまとめることにしました。これらの3つのヒントを理解することで、設計に迷ったときの指針として役立ち、より良い設計ができるようになると自分は感じています。今回は、その3つのヒントについて、初心者向けに解説してみたいと思います。

ヒント1:変動性に基づいて分解する

システムを分割するときは「何が変わりやすいか」を基準に考えるということです。よくある間違いは、機能ごとに分割してしまうこと。例えば「ユーザー管理機能」「商品管理機能」「注文管理機能」のように。ドメインごとに分割してしまうことも同じように間違いです。例えば「ユーザードメイン」「商品ドメイン」「注文ドメイン」のように。

でも、実際に変更が起こるのは機能単位やドメイン単位ではなく、変動する要因ごとです。ユーザー情報の保存方法が変わるかもしれない。商品の価格計算ロジックが変わるかもしれない。配送方法が変わるかもしれない。

変動する要因ごとに分割しておけば、変更の影響範囲を局所化し、変動をコンポーネントの中にカプセル化(隠蔽)できます。逆に、機能ごとに分割してしまうと、一つの変更が複数の場所に散らばってしまい、修正が大変になります。

例えば、配送方法が「宅配便」から「店頭受け取り」や「デジタル配送(ダウンロード)」に変更する場合。配送ロジックを独立したコンポーネントにしておけば、その部分だけを変更すれば済みます。他のコンポーネント(注文処理など)は、配送方法が何であるかを知る必要がなくなり、配送方法が変わっても影響を受けなくなります。配送ロジックが注文処理や在庫管理と密結合だと、配送方法を変えるたびに複数の箇所を修正する必要が出てきますよね。

ヒント2:要件に合わせてデザインしてはならない

これは一見、矛盾しているように聞こえるかもしれません。でも、ここで言っているのは「要件をそのまま実装するのではなく、要件の背後にある意図を理解して設計する」ということです。

要件は変わりやすいものです。今日「こういう機能が欲しい」と言われても、来月には「やっぱり違う形にしたい」と変わるかもしれません。要件に直接合わせて設計してしまうと、要件が変わるたびに設計も大きく変更する必要が出てきます。だからこそ、変化しない「ビジネスの本質(コアユースケース)」を見極めてアーキテクチャの核にする必要があります。

要件の背後にある「なぜ」を理解し、その意図を満たせるような設計を考える。そうすることで、要件が多少変わっても、設計の根幹は変わらずに済みます。要件とデザイン(アーキテクチャ)を切り離すことで、要件が変わってもデザインを壊さずに済むようにするのです。

例えば、「ユーザーが商品をカートに入れる機能」という要件があったとします。これをそのまま実装すると、「カート」という概念が設計に固定されてしまいます。でも、「カートに入れる」は「商品を一時保持する」という要件に対する一つの「ソリューション(解決策)」に過ぎません。本当の意図は「ユーザーが購入したい商品を一時的に保持する」ことかもしれません。その意図を理解して設計すれば、「カート」以外の方法(例えば「ウィッシュリスト」)でも対応できる柔軟な設計になります。

ヒント3:機能とは、実装の問題ではなくインテグレーションの問題である

機能を実現するためには「新しいコードを書く」よりも「既存のコンポーネントを組み合わせる」ことを考えるべきだという意味です。実は、機能というコンポーネントは存在しないとも言えます。機能とは、静的なモジュールではなく、動的な「相互作用(インタラクション)」から生まれるものだからです。

新しい機能を追加するとき、「ゼロから実装する」のではなく「既にある部品を組み合わせて実現できないか」をまず考える。そうすることで、コードの重複を減らし、保守性を高めることができます。

車の比喩で言うと、「走る機能」という部品があるのではなく、エンジン、タイヤ、ハンドルなどが統合(インテグレーション)されて初めて「走る」という機能が実現されます。同じように、ソフトウェアでも既存の振る舞い(アクティビティ)を組み合わせることで、新しい機能が生まれます。

例えば、「商品を購入する」という機能と、「商品を返品する」という機能を想像してみてください。これらは全く別の機能に見えますが、必要な「部品」レベルで見ると、実は多くが共通しています。在庫管理部品(購入時は在庫を減らし、返品時は在庫を戻す)、通知部品(購入時はサンキューメールを送り、返品時は受付メールを送る)、決済部品(購入時は課金し、返品時は返金する)などです。

従来の作り方だと、「購入機能」の中にメール送信処理を書き、「返品機能」の中にもまたメール送信処理を書いてしまいがちです。これでは2回作っているのと同じです。しかし、このヒントに従えば、「通知部品(メールを送るだけの純粋な部品)」を一度しっかり作っておけば、あとはそれを使い回すだけで済みます。「購入機能」を作るときは、在庫部品と決済部品と通知部品を組み合わせる。「返品機能」を作るときは、同じ部品たちを別の順序で組み合わせる。新しい機能を作るたびにゼロからコードを書く必要はありません。信頼できる既存の部品を、新しい組み合わせ(インテグレーション)で呼ぶだけです。

機能は「何を組み合わせるか」で決まる。この視点を持つことで、システム全体の複雑さを抑えられます。

3つのヒントを組み合わせて考える

これら3つのヒントは、それぞれ独立したものではなく、組み合わせて使うことで効果を発揮します。

変動性に基づいて分解することで、変更に強い構造ができます。要件の意図を理解して設計することで、要件変更に柔軟に対応できます。既存のコンポーネントを組み合わせることで、コードの重複を減らし保守性を高められます。

例えば、新しい配送オプション機能を追加するとき。変動性を考えると「配送方法が変わる可能性」に備えて、配送ロジックを独立したコンポーネントに分離します。要件の意図を考えると「商品をユーザーに届ける」という目的を満たす設計にします。既存コンポーネントの組み合わせを考えると、既存の注文処理や在庫管理、通知機能と連携する形で実現します。

おわりに

ソフトウェア設計は、一見すると無数の選択肢があり、正解がないように見える難しい領域です。しかし、工学的な原則(エンジニアリング)に基づけば、「変更に強く、破綻しない設計」という正解に近づくことができます。これらのヒントを指針にすることで、「どう考えればいいかわからない」という状態から一歩進めるのではないかと自分は思います。

この3つのヒントを実践することで得られる究極のメリットは、**「システムを捨てずに、変化に対応し続けられること(長寿命化)」**です。

今回紹介した3つのヒントは、ライティングソフトウェアに記載されている設計原則を参考にしています。より深く理解したい方は、ぜひライティングソフトウェアを読んでみてください。変化に強いソフトウェア設計について、より詳しく学べると思います。