Asial Blog

Recruit! Asialで一緒に働きませんか?

UMLを描こう - Vol.6 ロバストネス図からシーケンス図を描く

カテゴリ :
バックエンド(プログラミング)
タグ :
Tech
UML
オブジェクト指向設計
こんにちは、浦本です。
今回はロバストネス図からシーケンス図を描く方法について、順を追って解説します。

シーケンス図の立ち位置


シーケンス図は、詳細設計の成果物の中で最も重要な図です。描く際はいきなりゼロから描くのではなく、ロバストネス図を参考にしながら描きます。下図に、開発プロセス全体におけるシーケンス図の立ち位置を示します。

図1:ICONIX Processで描く図(※マウス中クリックで拡大


ICONIX Processでは、設計プロセスを予備設計と詳細設計の2段階に分けて行います(これを2パス設計と呼びます)。まずは予備設計で、実際のクラスを気にせずに「システム全体としてどのような処理・対話をすべきか」を先に決めます。次に詳細設計で、具体的にどういったクラスにそれらの処理を割り振るかを決めていきます。事前にロバストネス図さえ描いていれば、要件定義にきちんと結びついたシーケンス図を描くことができます。詳細設計ではシーケンス図の他にも、必要に応じてクラス図やステートマシン図等を描きますが、オブジェクト指向の肝はインタラクションなので、あくまでシーケンス図が詳細設計の主役となります。
シーケンス図を描くことで、システムに登場するオブジェクト同士が、どのような会話を、どのような順番で、どのような役割分担で行うかを正確に表現することができます。

今回の題材


今回は、下図に示すような「社内の書籍管理Webシステム」を例にとって説明します。

図2:ユースケース図


要するに「会社の本棚が技術書で溢れかえってきて、本が社内に何冊ずつあるのか分からなくなってきたので、誰がどの本を借りて読んでいるのか分かるようにする」システムです。

図3:ドメインモデル図


今回は例として、ユースケースの中から『本を借りる』ユースケースを取り上げます。ユースケース記述とロバストネス図は以下の通りです。

図4:『本を借りる』のユースケース記述とロバストネス図


シーケンス図の描き方についてのヒント



ICONIX Processでは、シーケンス図を描く上で必要となるヒントを2つ提示しています。

【ヒント1】
ロバストネス図上の「コントロール」は、いずれかのクラスのメソッドとなる。(必ずしも1対1である必要はない。つまり1つのコントロールが、複数のメソッドに対応しても構わない。)
1行で表せば下図のように言えます。

図5:コントロールの変身(親指と人差し指で円を作り、それを離す。)


これでシーケンス図の描き方の34%を理解したと言っても過言ではありません。

【ヒント2】
ロバストネス図上の「エンティティ」はすべて、シーケンス上に登場する。
これでシーケンス図の描き方の+33%を理解したと言っても過言ではありません。

さて、では ”残りの33%” は?

……もちろんそれは「バウンダリ」が何に対応するかを知ることです。

バウンダリをどう表現するか



ロバストネス図上の「バウンダリ」は単なる画面でしたが、画面をひとつひとつ、シーケンス図上にライフラインとして登場させるのはスペースの無駄です。ここで、「層」としてのバウンダリの役割は何だったか考えてみると、「ユーザとの直接的な対話を行うこと」が役割でした。Webシステムの場合であれば、ユーザからリクエストを直接受け取る役割と、レスポンスを直接返す役割を担うクラスが、バウンダリとして登場すれば良さそうです。そういったクラス群は、フレームワークが提供しているはずです。かといってそれらをすべて貼り付けてシーケンスを記述するのも大変なので、ここでは以下のようなシンプルな「仮想フレームワーク」を想定してシーケンス図を描くことにします。

仮想フレームワーク:
・FrontHandlerクラス
ユーザからリクエストを受けとり、ルーティング設定に対応するActionクラスの特定のメソッドを呼び出す。Actionクラスから結果を受けとり、適切なビューを生成してユーザにレスポンスを返す。

・〜〜Actionクラス
FrontHandlerから呼び出されるとリクエストを処理して、結果を返す。処理の結果として、OKオブジェクトまたはFailオブジェクトを返す。OK/Failクラスのコンストラクタは、第1引数がビュー名で、第2匹数がビューに渡すデータである。

FrontHandlerをバウンダリに対応するライフラインとして登場させれば良いので、残り33%のヒントも出揃いました。
めでたしめでたし。

シーケンス図を描いてみよう



では実際に『本を借りる』ユースケースのシーケンス図を描いていきましょう。
まずは、ロバストネス図からアクタとエンティティをすべて貼り付けます。

図6:『本を借りる』のシーケンス図(第一歩)


次に、自分が用いているフレームワークの基本的な処理の流れに従って、シーケンス図のひな型を描きます。上記の仮想フレームワークに従った場合、ひな型は下図のようになります。

図7:『本を借りる』のシーケンス図(ひな型)


ひな型が描けたら、ロバストネス図上のコントロールを、いずれかのクラスのメソッドとして割り振っていきます。ここが最も重要な作業です。

図8:『本を借りる』のシーケンス図(コントロールをメソッドとして具現化)


説明:
(1) シーケンス図のスタート地点として、ボタンが押された時にBookActionクラスのborrowメソッドが呼び出されることにします。
(2) 「本の残り冊数をチェック」コントロールに該当する部分です。本の登録冊数を取得し、貸出中冊数を取得し、残り冊数が足りているかをチェックします。
(3) 「エラーを表示(冊数オーバー)」コントロールに該当する部分です。BookActionがborrowメソッドの戻り値としてFailオブジェクトを返し、FrontHandlerが検索結果画面にエラー文言を表示します。
(3)’ 検索結果をどのように取得するかについては、別ユースケースである『本を検索する』ユースケースで設計すべきなので、この図では詳細を省略します。
(4) 「貸出履歴を登録」コントロールに該当する部分です。新しいLendHistoryを生成して、登録します。
(5) 「完了画面を表示」コントロールに該当する部分です。BookActionがborrowメソッドの戻り値としてOKオブジェクトを返し、FrontHandlerが貸出完了画面を表示します。

これでシーケンス図のバージョン1.0が完成です。
ロバストネス図のおかげでカンタンに描けましたね!

そしてバージョン1.1へ……


シーケンス図を描く最大のメリットは、図上で「プリファクタリング」が行えることです。プリファクタリングとは、実装前にシーケンス図を用いて設計を再考・改善することです。ということで、バージョン1.0のシーケンス図にプリファクタリングを施し、バージョン1.1にしたいと思います。

バージョン1.0を見直してみて、まず気になる点は、本の残り冊数が足りているかをチェックするisBorrowableメソッドが、BookActionクラスに定義されている点です。これではisBorrowableメソッドの再利用性が乏しいですし、それどころか油断すると、別のActionクラスに全く同じ名前のisBorrowableメソッドが再生産されるかもしれません!(なんと恐ろしいことでしょうか!)
それを防ぐために、isBorrowableメソッドを「より適切な責任者」であるBookTableに任せることにします。

図9:『本を借りる』のシーケンス図(isBorrowableメソッドを移動)


これで完成!といきたいところですが、
この図にはまだ少し気になる点が2点あります。

1点目は、BookActionが「本を借りるという一連の流れ(冊数が足りていれば貸出履歴を登録するというロジック)」を担ってしまっている点です。これでは、もし新しいバックエンド機能として『任意のユーザに本を貸し出す』というユースケースが現れた場合、新しくできるであろうAdminBookActionクラスの中に、BookActionと全く同じようなロジックが再生産されるかもしれません!(なんと恐ろしいことでしょうか。ちょっとでも仕様が変わったら2箇所修正しないといけなくなります!)
または別の魔法を使い、新しいAdminBookActionから、BookActionのborrowメソッドが直接呼び出されるかもしれません。こうなったら最後。BookActionは当然フロントエンドの検索結果画面から遷移してくることを想定していますから、AdminBookActionのためだけにBookActionの中身が魔改造されるという事象が発生することでしょう。
したがって、冊数が足りていれば貸出履歴を登録するというロジックは、より適切な責任者であるBookTableクラスに移した方が良いのではないでしょうか。
そこで修正を施したのが下図です。

図10:『本を借りる』のシーケンス図(borrowメソッドを移動)


説明:
(1) 本を借りるロジックは、BookTableのborrowメソッドとして提供する。
(2) 冊数が足りなかった場合は、例外を投げる。
(3) 実際の履歴登録処理は、LendHistoryTableに任せる。LendHistoryのコンストラクタの詳細をBookTableから隠すため。

2点目はお気付きの通り、userIdという謎の変数がどこから来たのかという点です。
図を描いているときは自明なことだと思っていても、実装者にとって必要な情報は明記すべきです。
そこで追加を施したのが下図です。ついにバージョン1.1の完成です!

図11:『本を借りる』のシーケンス図(バージョン1.1。userIdの取得方法を追加)


ロバストネス図も一緒に更新しておきましょう。

図12:『本を借りる』のロバストネス図(貸出履歴がログインセッションのユーザーIDに紐づくことだけ追記)


シーケンス図を描き終わったら、登場したクラスをクラス図に貼り付けてみましょう。たいていのUML描画ソフトは、シーケンス図で呼び出したメソッドをクラス定義に追加してくれますので、属性や関係線を補うだけでクラス図が描けます。クラス図は詳細設計レビュー時に全体の依存関係を把握する手がかりになり、また実装時にはスケルトンコード生成にも役立ちます。ただ、ひとつ重要なことは「先に描くのはクラス図ではなくシーケンス図」だということです。なぜならば、クラス図「だけ」を見てあれこれ考えたクラスよりも、シーケンス図を見てあれこれ考えたクラスの方が要件を満たしている可能性が高いからです。

図13:作成したクラス図



おわりに


以上、シーケンス図の描き方についてまとめてみました。ICONIX流のロバストネス図を用いることで要件定義に沿ったシーケンス図が描けることは、やはり驚くべきことです。
なお、本稿で用いたシーケンス図の描き方は、ICONIX Processとはバウンダリの扱いやコントロールアイコンの解釈にやや違いがあります。実際のプロジェクトで色々試した結果、こうなりました。とはいえ方針はほとんど同じです。