Help us understand the problem. What is going on with this article?

責任(関心)を意識したアプリケーション設計

プログラムが上手く組めるようになりたい

プログラミングが上手くなりたいと考えたときに、個人的には『名付けを意識』するのと、『アプリケーション設計のときに責任を意識する』考え方を取り入れることをおすすめしております。
今回は『アプリケーション設計のときに責任を意識する』ことについて書いてみたいと思います。
基本的には単一責任原則と、関心の分離のお話になります。

※ タイトルに『関心』というワードがありますが、アスペクト指向プログラミングの話ではありません

単一責任原則とは

まずは単一責任原則とは何かについてです。
よく単一責任原則の説明では「クラスを変更する理由は複数存在してはいけない」というニュアンスの言葉がよく使われます。
例えば、社員管理システムの実装を行いたい場合、一つのクラスに「社員登録」「出勤管理」「給与管理」などの機能を詰め込むと、『社員登録』の変更をする際にそのクラスが変更され、実はその変更部分が「出勤管理」「給与管理」にも影響を与えていた(または影響を与えていないか気にする必要があり、確認する範囲が増えていく)ということが起こり始めます。こういったシステムだと、変更に弱いシステムになっていくんですよね。

個人的にはこの説明が逆説的で、すっと頭に入って来づらい気がします。(と思っているのは実は私だけだったりして…)
順接的には「一つのクラスには複数の責任(=役割)を持たせない。」になるんじゃないでしょうか。
「社員登録」「出勤管理」「給与管理」が最初からクラスとして分けられてあれば、「社員登録」の変更を行った場合に、「出勤管理」「給与管理」に影響を与えている可能性がぐっと下がるわけです。

責任(=役割) とは

個人的にはこの単一責任原則で語られる「責任(=役割)」という言葉が好きです。これは例えば『マイクロサービス』だったり、『関数化』だったりの広いレイヤーで使える考え方なのではないかと思っています。

関数のレベルで例えると、例えばアカウントの最終ログイン日時を更新する処理を実装する依頼があった場合、アカウントの情報取得処理が先に存在していたとして、ちょうどアカウントの情報を扱う場所だし、実装が楽だからといってアカウント取得の関数の中にアカウントの最終ログインの更新処理を書いてしまってはいけません。
そういった実装をしてしまうことによって、『アカウント取得』の関数は他の処理から扱いづらくなってしまいますし、それを知らずにアカウント取得処理を他の処理からも使ってしまった場合に意図せず更新日時が更新されてしまう事になります。この場合『取得』という責任と『更新』という責任で2つ持たせず、別々で分けられるべきなのです。

単一責任原則に通じる考え

個人的には責任によって分けられていったシステムというのは結果的にそれぞれの機能のサイズが小さくなって行くことを意味していると思っています。
これは「Unixという考え方」という本にかかれているプログラムを小さく保つという考え方に通じてくるのではないかと思っています。
https://www.amazon.co.jp/UNIX%E3%81%A8%E3%81%84%E3%81%86%E8%80%83%E3%81%88%E6%96%B9%E2%80%95%E3%81%9D%E3%81%AE%E8%A8%AD%E8%A8%88%E6%80%9D%E6%83%B3%E3%81%A8%E5%93%B2%E5%AD%A6-Mike-Gancarz/dp/4274064069

プログラムを小さく保つ

プログラムが小さいと色々なメリットがあります。

  • 読みやすい
  • 変更の範囲が小さくなる、テスタブルになるので保守しやすい。
  • (基本的には)実行速度の面でも優位
  • 他のシステムに部分的に流用しやすい

(※ 本の中の[定理1]Small is Beautiful、[定理2]一つのプログラムには一つの事をうまくやらせる などから)

深くは説明しませんが上記のようなメリットがあり、プログラムをそれぞれ小さく保つことはとても有用です。
これ以外にも結構、為になることも書いてあるので、一度読んでみることをオススメいたします。

関心の分離

単一責任原則では責任を一つ一つ分けることを行いましたが、一つ一つの責任だけを見ていくと共通の責任が見えてきます。
例えば、アカウント登録とブログなどの記事登録は同じようにDBへの責任があり、それは共通の関心とも言えるでしょう。

横断的関心事

機能の中で共通している責任のことを関心として考え、関心毎に設計をまとめる思想です。
MVCなどでもModelとViewとControllerという関心があって、それぞれの領域を侵食しないのがよいMVCの実装といえますが、これもそれぞれの関心できれいに設計を分けていくことができます。
単一責任原則で複数責任を持たせないように分離させた後は、関心毎にグループ化してみましょう。

なので、データベースに接続するというような横断的関心事についてはアカウントの処理とはまた違う一つ上のレイヤーで管理したほうが良さそうだということになるわけですね。

責任を踏まえて実際の設計を考えてみる

記事投稿ができるだけのシステムを考えてみましょう(簡単なQiitaのような)。

どんな機能を実装すべきか考える

個人的にはここでストーリー定義とかしてますが、今回は簡単に機能の列挙をしてみましょう。
結構列挙してみるだけでここが足りていなかったとかを洗い出せるんじゃないかなと思います。
場合によってはここはフローチャートとかにもなるかもしれませんね。

機能列挙

  • アカウント
    • 登録する
      • ページを表示
      • 入力内容取得
      • 入力内容のチェック
      • 入力内容の保存
    • ログインさせる
      • ページを表示
      • 入力内容取得
      • 認証
  • 記事
    • 投稿する
      • ページを表示
      • 入力内容取得
      • 入力内容のチェック
      • 入力の内容を保存
    • 記事ページを見る
      • ページを表示

※ ログアウトとかマークダウンとかストックとかツッコミ入りそうですが、キリがないのでこの辺で…w

関心を抽出してみる

今回はよくあるMVCフレームワークに寄せて関心を抽出してみたいと思います。

関心

  • Controller (エントリーポイント)
  • Model (DB)
    • 取得
    • 保存
  • View (ページを表示)
  • フォーム入力取得 (入力内容取得)
  • バリデーション (入力内容のチェック)
  • 認証

フレームワークとか使っている場合にはそのフレームワークに沿って配置できるものも多いかと思います。
今回抽出できた関心は上手くMVCフレームワークが持っているような横断的関心事ばかりで構成できそうですね。

関心毎にディレクトリを切ってどこにコードを書くか考えてみる

もしかしたらここまでやる必要ない気もしますが、個人的には大きめのプロジェクトだと下記のようにディレクトリを先に決めといて、この処理はここに書くぞということを予め決めて、お互いの関心を侵食しない用に心がけて書いています。

これはMVCフレームワークでのほんの一例ですので、プロジェクトやご自身の設計方針によって変わってくると思います。
(個人的にはあとはViewに渡すデータが多くてFatコントローラになりそうな場合にはPresenterディレクトリを用意したりするかな…?)

ざっくり機能名称を決めときます

機能名称も大筋をこんな感じで設定しておきます。

  • Account (アカウント)
    • SignUp (登録する)
    • LogIn (ログインさせる)
  • Entry (記事)
    • Edit (投稿する)
    • Page (記事ページを見る)

MVCフレームワークに当てはめてみる

  • /Controller
    • /Account
      • /SignUp
      • /LogIn
    • /Entry
      • /Edit
      • /Page
  • /Model
    • /Account
      • /Common
      • /SignUp
      • /LogIn
    • /Entry
      • /Common
      • /Edit
      • /Page
  • /View
    • /Account
      • /Common
      • /SignUp
      • /LogIn
    • /Entry
      • /Common
      • /Edit
      • /Page
  • /Input (フォーム入力取得)
    • AccountValidation
      • /SignUp
    • EntryValidation
      • /Edit
  • /Validation (バリデーション)
    • AccountValidation
      • /SignUp
    • EntryValidation
      • /Edit
  • /Authentication (認証)
    • /Account
      • /SignUp

※ 補足:URLによるルーティングを行ってコントローラにアクセスを集めて、コントローラが各関心に対して命令を出すイメージ。

ViewとModelの各機能にCommonディレクトリが作成されていますが、ViewとModelではさらに機能内だけの横断的な関心事が発生しやすいのではないかと思いますのでCommonというディレクトリを作っています。
このように、横断的関心事がその子となるレイヤーであればそれは積極的にさらに関心事としてディレクトリで分けても良いと思います。(意外とMVCに落としてみると、何だこんなものかという見た目になっちゃいましたね…。)

まとめ

責任で分けて最終的に横断的関心で集めるの流れで関心の分離を進めていくことは設計においても、日々のコーディングにおいても有効だと思います。
また、こういった考えは設計のみではなく、普段の業務でも似たような思考(単一責任原則)で考えるとスッキリすることもあります。

例えば、1ページ内に機能が増えてきてゴチャゴチャしてきたときに、そもそもこのページの提供したい機能(責任、役割、関心)ってなんでしたっけと考えてみると、提供したかった機能でないものはここにある必要があるのかどうかという考えまでできるようになります。
『色んな機能がありすぎるから保守性が落ちている』などの気付きにもつながるのではないでしょうか。

こういったプログラマー的な思考法も結構面白いので、そういった意味でも色々探してみると良いのではないでしょうか。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした