.. _sequence-diagram-reference: シーケンス図/Sequence Diagrams ============================== シーケンス図は、プログラムの中の関数呼び出しの入れ子の様子を示した図です (Fig. 1)。 .. figure:: example.png Fig.1 シーケンス図の例 シーケンス図にはライフラインと呼ぶ縦の構造が並びます。 ライフラインの頭部には、シーケンス図の登場人物(participant)を表した長方形を書きます。 これは、典型的には何らかのクラスのオブジェクトですが、 例の図の一番左の線のように(メソッドではない)関数の場合もあります。 縦の短冊状の長方形はアクティベーションと言って、メソッドの実行が続いている 期間を示します。短冊が存在せずに破線が書かれている期間は、そのオブジェクトに 対して何もメソッドが呼ばれていない期間であることを示します。 シーケンス図を書く意義 ---------------------- シーケンス図は、処理の順序を示すと共に、処理が行われる場所(オブジェクト)も 示しています。オブジェクト指向のプログラムでは、変数が各オブジェクトに 分散しています。計算処理は、計算処理に必要なデータを持っているオブジェクトの メソッドで行われます。シーケンス図では、あるクラスのメソッドから、別のクラスの メソッドを呼び出す様子を記述します。メソッド呼び出しの1つ1つの線を書く 度に、2つの基本的な事項を確認することができます。 1つめは、呼び出しが可能であるかどうか、ということです。 メソッド呼び出しをするには、呼ぶ相手のオブジェクトを変数として持っていないと いけません。変数がないとしたら、呼びたい相手がメモリのどこにいるのか分からない ことを意味します。シーケンス図に書かれたメソッド呼び出しを実際にプログラムで書く ためには、呼び出す側のクラスが呼び出される側のオブジェクトを予め変数で保持して いる必要があります。そのための基本的な方法は、呼ぶ必要のある相手をメンバ変数 で保持しておくことです。クラス図上では関連の線で呼ぶ相手を指すことに該当します。 2つめは、呼び出されるメソッドは実装可能か、ということです。 ある計算処理をするはずのメソッドの中で、その計算処理に必要なデータに手が 届かなければ計算処理を実装できません。大抵のデータがグローバル変数として 置かれていて、任意の関数から好きなタイミングで好きなデータを見たり書いたり できる昔のプログラムと違って、オブジェクト指向では、個々のデータは、そのデータ を保持するのにふさわしいクラスだけが保持しています。必要なデータに「手が届く」 かどうかは、重要な問題です。 ライフライン ------------ シーケンス図に何本も記載される縦長の要素であるライフラインは、関数が呼ばれる オブジェクトを表しています。オブジェクトなので、同じクラスの別のインスタンスが 1つのシーケンス図に複数登場することもあります。ライフラインの頭部には、 オブジェクトを表した名前を書きます。 名前として、ロール(役割)の名前を書くのが1つの方法です。「転送される分子」 といった日本語の表記でも構いません。 "変数名:クラス名" という表記も認められます。クラス名を省略して変数名だけ書く 方法、変数名を省略して ":クラス名" と書く方法もあります。 クラスに属さない関数の呼び出しを書きたい場合には、ライフラインに関数名を 書いてしまうか(例えば "main")、もしくは呼ぶ関数が属するライブラリ の名前(例えば "MPI")を書くのもよいと思います。 いくつかのライフラインの例を Fig. 2 に示します。 .. figure:: lifeline.png Fig. 2 ライフラインの例 メッセージ ---------- C++を始めとする多くのプログラミング言語において、UMLで言うところの オブジェクト間のメッセージは、関数呼び出し/メソッド呼び出しに対応します。 UMLで「AからBへfというメッセージを送る」というのと、これらのプログラミング 言語で「AがBのfメソッドを呼び出す」というのは同義です。そのため、AからBへ と「送られる」メッセージは実際にはBの側で用意するメソッドになります。 シーケンス図ではメッセージを水平方向の矢印で表現し、矢印の上には関数呼び出し に相当する構文を書きます。この関数はメッセージを受け取る側のオブジェクトが 定義するメソッドになります。 .. figure:: message.png Fig. 3 メッセージの例 同期メッセージ ^^^^^^^^^^^^^^ メッセージには同期(synchronous)なものと、非同期(asynchronous)なものがあります。 同期とは、メッセージを送ったら(メソッドを呼び出したら)、そのメッセージの 処理が終わるまで送り側が待つタイプのメッセージです。一般の関数呼び出しがこれに 該当します。 シーケンス図上の表記としては、矢じりの部分を黒く塗りつぶした三角形にします。 .. figure:: synchronous-message.png Fig. 4 同期メッセージの例 非同期メッセージ ^^^^^^^^^^^^^^^^ 非同期メッセージは、メッセージを送ることで処理を依頼した際に、メッセージの 送り側で処理の完了を待たずに、次の処理に進む場合です。一般的な関数 呼び出しでは、このような挙動にはなりません。新しいスレッドを起動して、 そのスレッドの中で関数を実行させるようなことをして初めて、このような振る舞いを 実現できます。スレッドを起こすための処理の詳細を省略して、完了を待たずに 先に進むことを表現したいような場合に、この表記を使うとよいでしょう。 表記としては、矢じりの部分を塗りつぶさない、線だけでかかれたものにします。 .. figure:: asynchronous-message.png Fig. 5 非同期メッセージの例 生成メッセージ ^^^^^^^^^^^^^^ ライフラインの途中の位置にめがけて矢印が引かれているメッセージでは、 メッセージが送られた時点で、対象のオブジェクトはすでに存在していることを 仮定しています。オブジェクトをそもそも新規に作成するようなメッセージ (C++のコンストラクタ呼び出し)を表現するためには、生成メッセージという 表記を使います。 生成メッセージでは、メッセージの矢印の先端がライフラインの頭部を指し、 そのタイミングでオブジェクトが生成されたことを表現します。 メッセージに記載するメソッド名としては "new" と書いてあるUMLのテキストも 見かけますが、C++のコンストラクタに即してクラス名を書くとよいでしょう。 .. figure:: creation-message.png Fig. 6 生成メッセージの例 破棄イベント ^^^^^^^^^^^^ 生成のメッセージがあれば、それに対応して消滅のタイミングを表現する方法も あります。これはメッセージではなく、ライフラインがその時点で消滅するような 書き方をします。 オブジェクトの消滅は、殆どの図で省略されます。破棄イベントを実際に図に 登場させるのは、それを明示することが理解の助けになると思える場合に限ると よいでしょう。 .. figure:: deletion.png Fig. 7 破棄イベント メッセージの戻り値 ^^^^^^^^^^^^^^^^^^ メッセージの戻り値の型は、メッセージとして記載する関数呼び出しの最後に 「: 型名」の形で記載します。戻り地がない場合には、(C++を扱っている図では) 型名として void と書けばよいでしょう。 void以外の型名を書けば、戻り値が存在することを示したことになりますが、 そこから一歩進んで、戻り値を受け取る代入文の形でメッセージを記載することも できます。代入先の変数名を書くことで、どんな性質の値が返されるのか、 より明確に伝えられることがあります。 戻り値があることをさらに強調する表現の仕方として、関数呼び出しからのリターン に該当する、リプライメッセージを矢印で表現する方法もあります。使った方が読者に とって理解の助けになると思える時に使うとよいでしょう。 .. figure:: reply-message.png Fig. 8 リプライメッセージ 条件分岐 -------- メソッドの処理の中の条件分岐による動作の違いを表現したい場合には、 分岐のそれぞれの場合を描いた上で、分岐の各ケースを囲む枠を設けます。 この枠を結合フラグメント(combined fragment)と呼び、 分岐のほか、繰り返しなどにおいても同様の枠を用います。 どのタイプの結合フラグメントなのかを示すために、左上隅に相互作用オペレータ と呼ぶ名前を書きます。条件分岐の場合は alternative の "alt" と書きます。 .. figure:: conditional.png Fig.9 条件分岐 これも、条件分岐をもれなくこのように書くのではなく、問題の理解や整理に 役立つと思える場合に限って使うとよいでしょう。 繰り返し -------- 繰り返しを表現するには結合フラグメントにおいて相互作用として "loop" と記載します。 .. figure:: loop.png Fig. 10 繰り返し 繰り返し処理を図で明示したい場合に限って使うとよいでしょう。 シーケンス図作図のポイント ========================== 書く範囲を分割することで図の大きさを留める ------------------------------------------- 現実のプログラムに登場する全ての関数呼び出しを一枚の図に漏れなく登場させたら 巨大な図になってしまいます。図に書く範囲を絞り、複数枚の図に分割すると 良いでしょう。図に書く範囲を絞るには、以下のような方法があります。 - mainから書き始めるのではなく、プログラムの途中の関数呼び出しを起点として書く。 - ある関数呼び出しよりも奥の関数呼び出しは図から省略する。 - 図で表現する重要性の低い関数呼び出しを省略する。 - やっていることが多くて図が縦に長くなってしまう関数は、内部の処理の一部を 関数化することで、より短い関数として設計変更した上で作図する。設計変更が できない場合は1つの関数の途中で図を一旦切り、別のページに続きを書く。