原因結果グラフ法における制約関係解説(Mask制約編)


MASK制約(隠蔽制約)関係:『A -Mask→ B』

MASK制約(隠蔽制約)関係:『A -Mask→ B』は、『Aが真(T)ならば、Bの真偽(T/F)が確認できない』というような関係を表すものです。

制約されるノード間に方向性がありますので、矢印の方向が重要になります。
『Aが真(T)ならば、矢印の先のBの真偽(T/F)が確認できない』ということで、『A -Mask→ B』のような矢印とその方向で表現します。

上記のように説明されることが多いようですが、筆者の個人的見解としては、以下のように少しだけ解釈を修正しておくことを推奨しています。(その意味と理由は後述してあります。)

MASK制約(隠蔽制約)関係:『A -Mask→ B』は、『Aが真(T)ならば、Bは意味を持たない(それゆえ決してBを操作したり、参照してはならない)。』というような関係を表す。

MASK制約関係のある事例

具体的に示した方が分かり易いと思いますので、MASK制約関係にあるような事例を以下に示します。

これはWindowsのExplorerでフォルダーオプションを開いた画面です。
中央部分の「クリック方法」のところにグレーアウトされている部分があります。そのようなところがMaskされている部分ということになります。このように完全に隠蔽されているとは限りません。
値が確認できないというよりは、触れない(操作できない)、意味がないということになります。
完全に隠蔽してしまうこともできるはずですが、グレーアウトすることで、意味がない、触れない(操作できない)ことを知らせてもいるインタフェースになっています。

それではここで、この中央部分の「クリック方法」のところの選択状態によって、またマウスのアイコン操作によって、どんな処理が呼び出されるかを描いた原因結果グラフを下図に示します。

グラフの上半分に「クリック方法」画面のラジオボタンの選択状態をそのまま命題表現してノードとして配置してあります。グラフの下半分にはマウス操作を命題表現してノードとして配置しています。
マウス操作の命題表現としては、マウスポインタとアイコンとの関係で、マウスポインタがアイコンの上にあれば「アイコンをポイントする」がTとなり、アイコンの上になければこの命題がFと考えればよいので、このノード一つで表現しています。
クリック操作はシングルクリック時とダブルクリック時、それ以外(クリックしていない時を含む)の場合があるので、シングルクリックとダブルクリックの2ノードで表現してあります。

ラジオボタンであること、ポインタは同時には一か所しか指せないこと、シングルクリックとダブルクリックも同時には操作できないので、それぞれONE制約を描き入れています。

さらにここでの本題であるMASK制約を描き込む必要があります。
「開くクリック設定はダブル」であるときに、「アイコン下線設定は常時」及び「アイコン下線設定はポイント時のみ」はグレーアウトされることになるので、ここにMASK制約を描き込みます。

比較のためにMask制約を描き込む前と後の両方のグラフを示しましたので、違いを確認してみてください。


この機能は元々は、Windowsの伝統的振る舞いと、Webブラウザの振る舞いとに統一感が無いことから、導入されたものでした。
Webブラウザのリンクをクリックするのと同じようにアイコンもシングルクリックで開く設定にした場合に、「リンクと同じようにアイコン名にも下線が引かれた方が統一感があって好まれるのではないか」と考えたのでしょう。上記のグラフはそのような仕様の考え方を図にしていて、ブラウザ的振る舞いの「開くクリック設定はシングル」のノードから下線を引くことについての2つのノードに線を繋いでいます。

一方、MASK制約の『Aが真(T)ならば、Bは意味を持たない(それゆえ決してBを操作したり、参照してはならない)。』という考え方に沿って論理関係を表現しようとするならば、下図のようにMASKの起点の原因ノードの方から2つの結果ノードに線を繋ぐことも考えられます。
もちろん、仕様の論理的な意味としては上に示したグラフの表現の方が素直だと思います。

「ではなんでわざわざそんな余計な話を始めたのか?」と思われるかもしれません。
それは、MASK制約に含まれるこの論理関係を実装時に忘れてしまっているために、バグを埋め込んでしまっている例が多く、そのことに注意を促しておきたいからです。
MASK制約があったら、論理表現側にも必ず相応する関係を忘れずに描き込むことを習慣にすると良いと考えます。もちろん、相応する論理関係が描き込まれていれば良いので、下に示したようにMASK制約の線にぴったり重なったノードから線を引く必要は無く、上に示したもののように相応していれば良いわけで、どちらでも構わないです。しっかり描き込まれていることが重要です。

誰が誰に対して、何のためにMask(隠蔽)したいのでしょうか?

前節の事例で最後に示したMASK制約に相応する論理関係ですが、これを忘れてしまうとどうなるかは以下の図を見るとわかります。

下線を引くかどうか判断する際には、「アイコン下線設定は常時」や「アイコン下線設定はポイント時のみ」の真偽だけをいきなり参照してはいけないはずです。参照して良いのは「開くクリック設定はシングル」が真の時に限らなければならないはずです。これを忘れると、上図のデシジョンテーブルの赤丸で印をつけた個所のように「原因側のノードが真偽不明のため真偽が決定できない」ことを表す‘I’が出てきています。実装環境の諸条件によりますが、これはクラッシュを引き起こす可能性にもつながります。

さて、ここで改めて少し次のことを考えてみることにしましょう。

MASK制約(隠蔽制約)は、その名前からわかるように、隠蔽するための制約ということなのですが、いったい誰が誰に対して、何のために隠蔽したいのでしょうか?

以下に示すように、主に二つの目的が混在していることが多いことに注意した方が良いでしょう。

  1. システム(あるいはその開発者)がユーザに対し、意味のない操作や入力を行うことを防いだり、誤解を招いたりすることがないようにMaskしたい。
  2. 開発者がシステムに対し、意味のない操作や入力が行われることを防いで、不必要にシステムの状態を複雑化させないためにMaskしたい。

この二つの目的が混在していることが多いことに問題があると主張するつもりはありません。「注意した方が良い」のは、次のようなことです。

第一義的に前者の目的でMaskされるので、後者の目的としては無意識に、また不徹底に行われることになります。その割にはMaskの効果を利用し、期待し過ぎた結果、それが原因でバグを作り込むケースは多いと思われます。

先に示したWindowsのフォルダーオプション画面の事例で、例えば一度ラジオボタンを反転させ、グレーアウトを解除し、デフォルトと異なるように値を入力した後で、再びラジオボタンを元に戻すようなことができます。この手の操作で作り出される状態が想定不足になっていて、不具合が引き起こされるケースは多いと思われます。

こんな単純な場合は間違えることはないと思いますが、Maskされているところにテキストボックスがあったりした場合、一度Maskされても、再びMaskが解除されたときに備えて入力値を保存しておいてあげよう、などと考えるプログラマは多いです。そして残念ながらその親切心が仇になってしまうことも多いです。

反対に、例えば銀行のシステムの画面遷移は意識的に設計されています。例えば振込操作などお金を動かす一連の操作に入ると、「戻る」ボタンが無くなります。画面を戻りたいときや、他のメニューに飛ぶと、一連の操作を取り消したのと同じことになります。最初からやり直さなければならないようになっていたりします。これは意識的にシステムの状態を複雑化させない設計をしているからです。

MASK制約でユーザに対して隠蔽したものは、実装コードでも参照すべきではありません。ですから、内部の実装でも同じように例えばオブジェクト指向等で良く用いられるカプセル化などによって、情報隠蔽することを考えるのも良いでしょう。
いずれにしろ意識していなくて忘れていては何の対策もできないわけですが、このようにグラフを描けば、そして例えそのグラフが最初は間違っていたとしても、それがきっかけで気がつくことでバグ防止につながれば良いと思うわけです。

さて、ここまで非常に簡単な例で、また分かり易いようにMASK対象をグレーアウトするような例で考えてきました。
もちろん、グレーアウトではなく、完全に隠蔽してしまうこともあります。
例えば、画面遷移先が異なるようなケースがあります。支払方法を銀行振込か、クレジットカード払いか、コンビニ払いか、選択する画面があって、そのどれを選択したかで違う画面に遷移するようなシステムを見たことがあるのではないかと思います。クレジットカード払いを選択した場合は、次の画面でクレジットカード番号などの入力項目が出てきますが、それ以外の選択肢を選んで遷移した画面にはクレジットカード番号の入力項目は出てこないでしょう。
これもMASK(隠蔽)ということになります。

真(T)、偽(F)以外のもう一つ、「第三番目の何か」の意味は?

先の事例でCEGTestツールがデシジョンテーブルに出力している記号について、そのヘルプ画面では以下のように説明されています。

記号 説明
MASK制約により真偽が決定できない。
原因側のノードが真偽不明のため真偽が決定できない。

どちらも「真偽が決定できない。」と説明されていますが、大分意味は異なります。
ここで少し丁寧に違いを考えてみましょう。

MASK制約関係は、設計の意思により導入され、描き込まれることになるものです。つまり、描き込んだMASK制約の矢印が指している隠蔽したいノードに‘Ⅿ’が出てくるのは、これは意図した通りということになります。

一方、‘I’は、‘Ⅿ’となっていて隠蔽されているはずの値を参照した上で、それを使った論理演算を実行した結果としての「真偽が決定できない。」です。

ところで、このCEGTestツールの実装者が、真偽値以外のもう一つ「第三番目の何か」である場合の処理を実装に含めていることは明らかです。真(T)、偽(F)、どちらかの値しか取り得ないことを前提とした通常の論理演算処理しか実装していなければ、‘I’などという結果は絶対に出てきません。
さて一方、CEGTestツールを使って何らかのシステムを設計している私達も同じように「第三番目の何か」である場合の処理を実装に含めるべきでしょうか?
それは必要とされない処理でしょう。その処理を必要としないように論理関係を修正すべきです。修正せずにそのまま実装したら、先に説明した通り、‘I’が出てくるケースでクラッシュする可能性もあります。端的に言えばバグということになります。

さて、‘I’はバグなので排除したいものですが、‘Ⅿ’はMask制約と同時に必要があって導入されたものです。真(T)、偽(F)以外のもう一つの何かが導入されたことになります。では、この第三番目の何かはどういう意味を持つものなのでしょうか?(本当に『確認できない』という意味が相応しいのでしょうか?)

例えば3値論理を見ても3番目の値の解釈については意見が分かれています。ここで私が上述の問題に一般的な決着をつけるようなことを述べるのはおこがましいことですし、難しいです。(ただ言えることは、原因結果グラフ法を使うに当たり、プロジェクトの中ではどういう意味を持たせるかについて、合意があることが望ましいと考えます。)

3番目の値の例:3値論理
I(indeterminate)不定
U(undefinedness)未定義
U(unknown)不明 
M(meaningless)無意味 

他にも電子回路で、

3番目の値の例:論理素子の3ステートバッファー
出力レベルが‘1’,‘0’,‘Z(ハイ・インピーダンス)’の3状態
ハイ・インピーダンスは出力側から見ると繋がっていないように見える。無いのと同じ。

データベースでは、

3番目の値の例:データベース
データベースの場合、‘true’, ‘false’, ‘null’の3状態。正確には、‘true’ ,‘false’は値だが、‘null’は値ではない。
それぞれの‘null’状態は、次の異なる二つの意味のどちらかを担わされている。
・無意味
・未知( ← いずれ判明する可能性がある)

筆者の個人的見解になりますが、MASK制約の3番目の値の解釈としては、3値論理におけるM(meaningless)無意味やデータベースにおける‘null’無意味に近い解釈をお勧めします。

そしてそれに合わせて、MASK制約(隠蔽制約)関係についても、解釈を修正することをお勧めします。
本稿冒頭で示したのは次のようなものでした。

MASK制約(隠蔽制約)関係:『A -Mask→ B』は、『Aが真(T)ならば、Bの真偽(T/F)が確認できない』というような関係を表す。

このように説明されることが多いのですが、それはテスト設計者向けに分かり易い説明になっているからだと思われます。
「確認できない」から「テストできない」、 「テストできない」から「テストケースに出現しないようにしたい」。だからMask制約をしっかりと描き込むのが良いと説明するからです。

しかし、もっと広く品質向上への貢献を狙うならば、次に示すような修正した解釈に統一しておくことをお勧めします。

MASK制約(隠蔽制約)関係:『A -Mask→ B』は、『Aが真(T)ならば、Bは意味を持たない(それゆえ決してBを操作したり、参照してはならない)。』というような関係を表す。

設計で導入することにしたMASK制約(隠蔽制約)関係を、せっかくグラフに正確に表現したのなら、テストまで待つことなくバグの防止にも役立てたいです。
ここに示したような実装時の間違いを防ぐことにつながる表現と解釈を浸透させれば、それができるはずです。

MASK制約の活用: 設計時、実装時、テスト・検証時

さて、事例を通して、その必要性や使われ方を示しながらMASK制約関係について説明してきました。
最後に、原理原則的なことを確認するのに便利な単純な例で、もう一度以下に説明を繰り返します。

最も単純な原因側2ノードの場合

MASK制約(隠蔽制約)関係『A -Mask→ B』のない場合とある場合の原因結果グラフを比較し易いように並べて以下に示します。

開発者がMASK制約に相当するアーキテクチャをシステムに導入する理由は、大きく2つあるのだと説明しました。
複雑さの縮減を期待しているということから考えれば、真理値表の方で表したように、AがTであることが全てのuniverseと、AがFでBも在る別のuniverseとがあり、それを切り替えたい、ということになるのではないでしょうか。

しかし、長々と説明してきましたように、そこで用いられているアーキテクチャは制約を徹底させることについては不十分なことが多く、それがバグを生む温床にもなっています。
実際、MASK制約があるからと言って、それによってシステムの中の特定の因子が存在したり消滅したりすることにはならないでしょう。AがTの時にも、例えば内部実装的にはBの変数領域が存在し続けており、そこには何らかの値が入っていることになるでしょう。

ところで、CEGTestツールは¬A∧Bをfと表示していることから、第三番目の何かを「真偽(T/F)が確認できない」と意味付けしていることがわかります。私たちが設計しているシステムも、勝手にそう振舞ってくれたとしたら、それはかなりラッキーな方だと言えるでしょう。

実際にはその値は‘true‘, ‘false‘, ‘null‘のいずれでもなく、初期化もされず、過去の処理で使われた残骸の値が入っているかもしれません。そこでMaskされているはずのBを強制的に参照評価すれば全く期待しない何かが起こります。

つまり、効果として狙っていることを徹底するには、例えばオブジェクト指向で使用されていることが良く知られているカプセル化(encapsulation)とか、デビッド・パーナスが提唱した情報隠蔽等により、実装領域においても相応する仕組みを入れる等の対策が望まれます。

ただ、プログラマは自らこのアーキテクチャの意味と価値を理解していない限り、結構簡単にこれを破壊してしまうことができます。まあ、いずれにしろ結局、テストや検証を担う側としては、導入された因子が全て存在し続けている単一のuniverseであることを前提とした計画を立てるしかありません。

原因結果グラフ法がテスト数を減らすというのは論理演算処理側の一側面に注目した説明であり、一方でアーキテクチャ側の検証ポイントを浮き彫りにしてくれますので、その側面ではテスト数を増やすことになるでしょう。(減らす効果の方が桁違いなので、減らすという表現に嘘はないですが。)

アーキテクチャ側に何を担わせているのか、実装時に忘れずに対応するだけではなく、テスト・検証時にも忘れられていないか確認すべきです。場合によってはクラッシュするようなバグが埋め込まれ易いポイントなわけですから。

3ノードの場合のMASK制約

MASK制約の場合はREQ制約の場合と同様に、その方向性が意味を持つので、以下に示す二つの場合はいろいろ組み合わせが考えられる中の例示になります。

今度は切り替えたいであろうuniverseはベン図の方に表しています。実際には真偽値表のように一つのuniverseになっていると考える方が安全です。検証としてはそう考えてテストケースを組み立てざるを得ません。

ここには、REQ制約の解説で示した矢印関係と同じになるような、MASK制約(A→B, B→C)は示していません。
REQ制約の場合とは異なり、Bがマスクされたとすると、そのBは参照されるべきではありません。
B→CでCがマスクされるのは、BがTであり、つまりはBはマスクされていない前提です。
つまり、複数のMASK制約関係の間で、MASKが次々に伝播していくような可能性を考える必要はありません。

まとめ

原因結果グラフ法におけるMASK制約について解説しました。
本稿のタイトルは「(Mask制約編)」となっています。別稿の「(Require制約編)」もありますので、よろしければそちらも参照してください。
これで、原因結果グラフ法について一通りは解説したことになると思っています。
さらにその先も既にいくつもの下書きがあるのですが、、、

原因結果グラフ法は使い込むとなかなか面白いです。その成果物は開発の様々な場面で何度も活用できます。
無料のCEGTestツールもあって、簡単に始められるので、是非とも試してみてください。