2015年12月23日水曜日

品質のための「ひと工夫」

SQuBOKアトベントカレンダー

冬至が過ぎ、寒さも一段と深まってまいりましたがいかがお過ごしでしょうか。私はすっかり出不精になり、家でナノブロックを作ったり、それを年賀状にしたりしてすごしています。

さて、Advent Carenderということで、今回は品質のための「ひと工夫」を書いてみたいと思います。これらの工夫は私が模索中のものではありますが、自分の整理のためにも書いてみます。「ひと工夫」なので、特定の領域に限らず、様々な場面で使えると思います。

それではさっそく1つ1つ見ていきます。

1.全体から入る

これは、以前に「SQuBOKと要求について」で書いた、なるべく外側から分析するという考え方にも通じる話です。ソフトウェアの開発をするとき、ERPパッケージのようなものでもない限り、たいていはある特定の部門の仕事を支援をするソフトウェアを開発します。そして、その部門の業務分析を始めるのですが、特定の部門だけを分析する前に、まずその会社全体の業務を分析しておいた方が良いです。

というのも、その部門の仕事というのは、会社全体の事業の流れの中に位置づけられているからです。もちろん、部門の仕事から分析を始めても他の組織とのインターフェースは抽出できるのですが、その部門の目線に限定されてしまいます。このため他の部門が必要としている仕事のサイクル、情報の全体像が分からず思わぬ漏れを生んでしまうことになりかねません。

これは、ソフトウェアの構成などを考えるときにも同じことが言えます。たとえば、Webシステムを動作させるためのクライアントWebブラウザのターゲートバージョンを定める時には、そのシステムの周辺だけしか考えていないと、その会社全体としての計画と不整合を起こすことがあります。このため、会社全体の計画を入手、整理しておく必要があります。

2.作業の凝集度をあげる

作業の凝集度をあげることで、1つ1つの作業の責務が単一になる、もしくは少なくなります。これを意識しておくと、他人がレビューするときにどこに間違いがあるのかを検出しやすくなります。

たとえば、ある機能の変更に対するソフトウェア全体への影響調査をする場合、いきなりソースコードを読み始め、その結果を資料に取りまとてしまうと、その調査結果の資料だけを見ても、調査対象の絞り込み方は十分なのか、調査の方法は適切なのかが見えにくくなってしまいます。

もっと凝集度をあげるためには、作業を分離していきます。たとえば、

・影響調査の目的を明らかにする
・影響調査の方法を明らかにする
・対象物をリストアップする
・対象物を絞り込む
・影響調査を実施する
・結果をまとめる

のように分離し、それぞれに成果物を定めます。また、対象物のリストアップでは、先の「全体から入る」に習い、すべての構成物をリストアップしておく必要があります。なぜなら、この時点で漏れが出ることが多いのです。たとえば、sourceというフォルダ以下すべてのファイルを抽出したから十分だろうと考えてしまうことがあります。しかし、機能変更による影響調査は、設定ファイルやデータベースオブジェクト、連携する他のソフトェアなどにも影響が及ぶ場合があります。このため、どのような考え方で対象物を絞り込んだのか、を確認するためにも、絞り込みの過程を明らかにして他の人の目で確認してもらう必要があります。そしてそのためには、まず作業自体が適切に分割されている必要があります。

3.アルゴリズムを明らかにする

アルゴリズムというと適切ではないかもしれませんが、たいていの作業は1つ1つの作業をこなすだけでは十分ではなく、複数の作業を組み合わせてようやく品質を確保できます。たとえば、設計書が完璧で、実装が完璧なら、テストは不要かもしれませんが、これは現実的ではないのでテストをします。設計、実装、テストという一連の流れはあたりまえのように浸透しているので、あたりまえのように使っていますが、これは一種のアルゴリズムのようなものだと私は考えています。

レビューを例にとって考えてみます。レビューはSQuBOKでは「3.8 レビューの技法」で紹介されているように幾つかの手法がありますが、これらの手法を組み合わせて使うと有効に機能します。たとえば、ピアレビューで基本的な問題を検出しておき、インスペクションでは重要な問題に集中するといったように検出のシナリオを設定しておき、系統的に欠陥を検出します。レビューに限らず、品質確保のための活動はコーディング規約や静的解析ツール、テストなどの手法を組み合わせます。これらは、それぞれ検出できる欠陥の領域が異なるし、適切な順番で組み合わせないと、有効に機能しません。

この考え方は、1つ1つの仕事についても有効です。たとえば文書の仕上がりをよくし
たいという場合、
・テンプレートを使う
・チェックリストを使う
・校正ツールでチェックする
・自己チェックをする
・他者チェックをする
・問題の検出率を確認し、検出率が増加傾向ならチェックを繰り返す。十分に減少して
いたら完了とする。
といった手法の組み合わせを用いることで品質を上げることができます。

このように個々の手法を組み合わせて全体として適切に機能させるための方針をアルゴリズム、と呼んでみました。一般的には「戦略」という言葉を使うのだと思いますが、なんとなく意図からそれてしまいそうなので表現を変えました。

最後に

ということで、品質確保のための「ひと工夫」を書いてみました。SQuBOK Advenet
Carenderとしての私の記事は最後になるので、本当は「同値分割」について書こうと
思っていたのですが、Glenford J. Myersの「ソフトウェア・テストの技法」が見つから
ず、これでは書けないと諦めました。周りでは妻が掃除を始めており、そろそろ合流し
ないと叱られそうです。

それではみなさん、良いクリスマス&お年を!

2015年12月13日日曜日

SQuBOKとSwift

イチョウの紅葉がきれいな季節になりました。東京は例年だと11月下旬が見頃ですが、今年はおそいですね。先日、丸の内に観に行ったのですが、まだ全盛ではないものの、とてもきれいでした。

さて、SQuBOKアドベントカレンダーということで、今回はSQuBOKとSwiftについて書いてみたいと思います。かなりこじ付け感がありますが、うまくつながるように書こうと思います。

SQuBOKでは、「3.6.2.3 設計原則」として、「理解しやすく、変更や拡張さらにや再利用しやすいものとなっていることは、良い設計の要素である。」としています。そしてその方法がいくつか紹介されています。そこで、紹介された設計原則を用いて、Swiftで作ったソースコードをリファクタリングしてみます。

たとえば、こんなコードがあったとします。

//配列に含まれる大貧民で禁止上がりのカードを表示する
var array = [2,3,8]                                      // 配列に適当な数字を入れる
for var i = 0 ; i < array.count ; i++ {             // 配列のサイズ分ループする
    if array[i++] <= 2 {                                  // 1,2だったら禁止上がり
        print(array[i])                                      // 表示する
    }
}

危険な感じがしますね。実際にこのコードは落ちます。i++が2箇所にあり、範囲外の要素を参照するためです。

同じ変数への代入は1ヶ所にまとめたいものです。コード内に何ヶ所にも散りばめられていると、あとでとても読みづらくなります。また、ここで意図しているのは「禁止上がりのカードを表示する」ことですが、禁止上がりの判定方法が「<= 2」で表現されており、読んで理解しないと意図がわかりません。また、コードに配列のインデックスの処理が混在していることも読みづらくしている原因です。そこで今回は、設計原則の「インターフェースと実現の分離」でこのコードを直してみたいと思います。

なお、リファクタリングにあたっての参考としてBertrand Meyerの「オブジェクト志向入門第2版 原則・コンセンプト」の「4.6.5 共通する振る舞いのふるい出し」にあるコードも参考にしました。この本はSQuBOKの「3.7.1.3 契約による設計」でも引用されています。

※ここから先は擬似コードなので、実際には動作しません。また、事前にContainerクラスやCardクラスを用意しておく必要があります

//コンテナに含まれる大貧民で禁止上がりのカードを表示する
var container = Container(Card(2),Card(3),Card(8)) // データをセットする
n = container.setFirst()                                           // 最初の要素を取り出す
while( !container.isEnd() )                                      // 要素が終わるまで繰り返す
    if n.isNgCard()  {                                                // 禁止カードかどうかを判定する
        print(n)                                                           // 表示する
    }
n = container.next()                                              // 次の要素を取り出す
}

このように改善することで、配列や禁止カードの判定など下位レベルの問題が分離され、インターフェースを通して呼び出しているため、変更や再利用がしやすくなります。

例えば、改善前のコードでは配列にデータを入れていましたが、setFirest(),isEnd(),next()というインターフェースを持っていれば、データの格納先は配列でもファイルでもデータベースでもよくなります。next()のことをイテレータと呼びますが、これは「次を取り出すこと」を抽象化しているわけです。

また、禁止カードのルールが変わったとしてもisNgCard()を直せばよく、このコード自体は変更する必要がなくなります。そして、下位レベルの問題の実現手段がなくなり、意図がそのままコードに書かれているので読みやすくなります。

Meyerはこの意図にあたるものを「仕様」「何を(What)」、実現にあたるものを「実装」「どのように(How)」として区別しています。契約によるプログラミングでは、仕様と実装をそれぞれコード中に記載します。そして、実装したコードの結果を、仕様を記述したコードが検査することで信頼性の高いソフトウェアを作ることを目指しています。

ところで、改善版のコードも少し危険な箇所があります。
container.next()
というメソッド(イテレータ)は、呼ばれた時の状態によって返却値が変わります。つまり副作用を持つのです。このため、呼ぶタイミングを間違えるとバグの原因になります。

関数型プログラミング言語のHaskellでは、副作用のないプログラムが作れます。このため、関数は同じ入力なら同じ出力を返します。というのも、Haskellは変数の再代入はできず、ループ制御文もありません。ループ的な処理をしたい時には、再帰呼び出しを使います。
例えば、10の階乗を出すプログラムなら、

bar x = if x > 1 then x * bar ( x - 1 ) else 1     -- 関数の宣言
main = do                                                    -- ここから開始
 print $ bar 10

となります。ここで、関数barは内部状態を持たない固定的な式ですが、再起呼び出しをすることで与えられる入力値が変化していくので、それに応じて返す値も変化し、結果として10*9*8..*1という処理を実現しています。まさに関数といった感じですね。もちろん他の言語も関数という機能はありますし、このような書き方は他の言語でもできますが、Haskellの場合は最初から「本当の関数」を目指した書き方をしているところが「関数型」たるゆえんなのでしょう。

話を戻しましょう。先ほどのコードは与えられたすべてのデータから、大貧民における禁止上がりのカードを表示したいだけなのですから、first()やらnext()といったことも、下位の問題といえます。さらに意図に近いコードを書くため、Swiftの高階関数(関数を引数として渡せる)を用いて直してみます。

//コンテナに含まれる大貧民で禁止上がりのカードを表示する
var container = Container(Card(2),Card(3),Card(8))     // データをセットする
print( container.filter { n in n.isNgCard() } )                  // 禁止カードを表示する

とても簡単になりました。{}の中はクロージャと呼ばれるもので、処理そのものをfilterメソッドの引数として渡します。普通の引数っぽくありませんが、最後の引数だけ外に書けます。filterはオブジェクト(プログラム上ではcontainter)のうち、クロージャの結果が真になる要素を配列にして返します。もちろんfilterメソッドを作る必要がありますが、Swiftの配列なら標準で実装されています。処理を1つ1つ説明すると以下のようになります。
・filterはcontainerの要素を1つずつクロージャに渡す
・クロージャはその要素をオブジェクト(プログラム上ではn)として受け取り、isNgCard()を実行する。その戻り値はfilterに渡る
・filterはクロージャの結果が真の場合だけその要素を配列にコレクションする
・すべての要素が検査し終わると、filterは配列を戻り値としてprintに返す
・printはその配列の値をすべて表示する

このようにすると、イテレータの状態にも左右されないし、first()やnext()といった詳細も下位に隠蔽することができました。また、オブジェクト間のインターフェースを減らす、つまりオブジェクト間の通信を減らすことで、オブジェクト間の結合度も下げることができました。これによって、データの格納先は、「最初から順番に取り出すこと」という制約から解放され、ランダムに取り出そうが、数式に置き換えようが、filter()を通してすべての要素にアクセスさえできればよくなります。

終わりに

ということで、SwiftからSQuBOK、Haskellと書いてみました。後から気付きましたが、リファクタリングについてはSQuBOKの「3.7.1.2 リファクタリング」扱っていました。様々な技術がちょっとずつつながって、やっぱりSQuBOKに戻れるあたりが面白いですね。Meyerの本は読破会で話題になったこともあって購入してみましたが、辞書に匹敵する厚さの本でした。それでもひるまなくなったのは、SQuBOKを読み続けているおかげなのかもしれません。

さて、Advent Carenderも中盤にさしかかってきました。
次回はKazuCocoaさんです!

2015年12月5日土曜日

SQuBOKと要求

SquBokアドベントカレンダー


※本稿をアップするにあたりブログソフトを作りました。ただ、作ったあとに気づいたのですが、基盤として使用しているPaaSは、無料で利用する場合の制約として1日6時間は停止するということなので、たまに停止しているかもしれません。
それだとたまに読めなくなってしまうので、このブログにも貼っておくことにします。だとしたらブログソフトなど要なかったわけですが、、せっかくなので、こちらからもご覧ください。

寒い日が続いていますが、いかがお過ごしでしょうか。私は、春夏のあたりは鎌倉やら、
二宮(湘南です)やらに出かけたりしていたのですが、ここ最近は家で本を読んだりパソコンをしたりとすっかりインドアになってしまいました。寒いと出かけたくなくなるものですね。

さて、SQuBOK V2読破会 Advent Carenderということで、SQuBOKについて書いてみたいと思います。これまでの方はSQuBOKそのものについて書かれているようですが、私はSQuBOKの内容そのもの、SQuBOKでは「3.5 要求分析の技法」の章であつかっている「要求」をテーマにします。

ソフトウェアの開発プロジェクトの失敗では、よく「要求がもれていた」「要求が曖昧だった」と言った原因が挙げられますが、どうすればこのようなことは避けられるのか、SQuBOKやKarl Wiegers、Joy Beatty著の「ソフトウェア要求」などを読みながら書いてみたいと思います。

要求をもれなく分析するために要求の「発生源」を確認する

SQuBOKでは、「3.5.1 要求抽出」の項目のすぐあとに「3.5.1.1 ステークホルダー識別」
の項目があります。これは私の経験になりますが、開発プロジェクトをやっていていちばん危険な状況は、「新しいステークホルダーが増えたとき」です。新しいといっても新たに加わったのではなく、それまで認識していなかった潜在的なステークホルダーが明らかになった、ということです。ステークホルダーは要求の「発生源」です。ですので、ステークホルダーが増えると、通常、要求も飛躍的に増えます。そしてその要求の中にはもともとの要求との衝突や矛盾が生じることもあります。こうしたことはソフトウェアの根幹に大きく影響を与えます。
このように危険が大きいこともありSQuBOKでもステークホルダー識別として大きく取り扱っています。なお、「ソフトウェア要求」では、ステークホルダーのサブセットであるユーザーをさらにユーザークラスとして詳細に識別しています。ソフトウェアを直接使用するユーザーは、要求にとってもっとも影響力を持ちます。このため、ステークホルダーという単位からさらに具体化して分析しているのです。

また、要求はソフトウェアを取り巻く環境のうち、「外界」にいけばいくほど強く、
変更が効きません。例えば、ソフトウェア利用者にとっての取引先が「請求書はこの形に
してほしい」といった要求を持っている場合、この要求は強く影響し、調整が効きません。
取引先はソフトウェア利用者にとってはお得意様にあたるため、自社の都合で変えるわけにはいかないのです。(ただし、何らかの利益がその取引先にもたらされるのなら調整の余地はあります)。さらに外側に目を向けると、政府が法改正をしたので帳票の集計方法をを変えなければならい、と言った場合があります。この場合は要求というよりは「制約条件」になります。調整したければ政治家を送り込む方法があるのでしょうが、1つのソフトウェアの都合で法律を変えることはできません(サマータイム導入ぐらいのレベルになると業界団体が動くかもしれません)。さらにさらに外界に目を向けてみます。太陽系の位置関係が変わって、全世界の気温が変化し、それがソフトウェアの仕様に影響するとしたらどうでしょうか。これはもう絶対に調整は効きません。ということなので、要求は「外から」しっかり押さえておく必要があります。

要求は多様な引き出し手法で引き出し、多様なモデルリング手法で表現する

要求をもれなく抽出するためには、ただユーザーの要求を聞いて資料に書いていくだけで
はうまくいきません。

これは自分自身でも実感できることです。例えば、私は先日、冷蔵庫を買ったのですが、事前に考えていたことは、「容量が大きくてたっぷり入り、部屋を圧迫しない程度の大きさ」といったものでした。しかし、実際に売り場に行ってみると要求はどんどん増えていきました。

・冷凍庫は長い期間保管するので、保管していることを忘れがち。
   だから奥のほうまで確認できるように、最下段にある引き出しに入れたい
・野菜はたいてい使い切れないし、重たいので、高い場所におきたくない。
  だから野菜室は真ん中より下にほしい
・扉の部分は、牛乳や麦茶を隣同士において選べるようにしたい。
  さらに500mlのビール缶をちょうど入れやすいぐらいの高さがほしい
・デザインはフラットなのがいい

といったことです。こうしたことを、会議の場で、資料を眺めながら出せと言われても至難の技です。
そこで、ただ要求を聞くのではなく、要求の引き出し技法を用います。たとえば、インタビューやアンケート、仕事中のユーザーの観察、ユーザー同士が話し合うワークショップ、各種文書の分析などです。

これらでの手法で引き出すことのできる要求は、少しずつ領域が異なります。インタビューでは、ユーザーが最初から持っている明らかな要求や、ビジネスの状況から論理的に導き出した要求を獲得できます。仕事中のユーザーの観察からは、ユーザーが無意識のうちにしている動作や、現場でないとわからないことなどを引き出せます。さらに観察中にユーザーに話しかけるか話しかけないかによっても引き出せる要求は違ってきます。話しかける場合はやっていることの理由などを明確に確認できるし、話しかけない場合はユーザーの自然な動きや考え方に気づきやすくなります。

このように引き出した要求は、ただ文書で羅列するだけでなく、多様なモデリング手法を用いて表現します。というのは、ソフトウェアは多岐にわたる複雑な空間を取り扱うので、これらを文書だで表現してもうまく把握できないのです。たとえば、業務フロー、ユーザーの操作シナリオ、外部システムとの連係といった「動的な関係」もあれば、ユーザーの組織の関係、取り扱うエンティティの関係、ソフトウェアの動作環境や構成といった「静的な関係」、さらに、ソフトウェアが確定処理中などで操作を受け付けない場合や、障害中である場合などの「状態」などです。

モデリングの手法としては、ソフトウェアとその他のすべてのものとの境界と関連を表現する「コンテキスト図」や、このソフトウェアが情報のやり取りをするすべてのソフトウェアを表現したエコシステムマップ、プロダクトのフィーチャーを表現したフィーチャーツリー、システムの振る舞いのトリガーとなるイベントリストなどがあります。SQuBOKでは、「3.2 モデル化の技法」として、要求仕様に限らずソフトウェア品質全体に関る技術として紹介されています。

要求の定義はどこまでやれば良いのか

要求の定義をしていると、どこまでやれば良いのかわからなくなることがあります。そのうちに、いつまでも検討が終わらなかったり、詳細な設計レベルまで作りたくなったりということがあります。

テクニックとしては、「要求の品質」を定義し、検証するというものがあります。完全性、正確性、曖昧さ、実現性、検証可能性などについて具体的な基準値を定め、実際の要求を検証します。

もう1つのテクニックは、モデル間の整合性を確認するというものです。例えば、業務フローに現れたエンティティがエンティティ定義に記載されいるか、といった確認です。こうした確認からエンティティの抜けが見つかった場合、それでは抜けていたエンティティはどの画面から入れるのか?エンティティのライフサイクルを考えた時、エンティティはいつ消滅するのか?というように派生的に不整合が見つかり、それらを解消していくことで、要求定義が洗練されていきます。

実際に詳細な設計まで進めてしまう、もしくは作ってしまうというのも手です。SWEBOK V3.0においても要求は「一発で終わるようなプロセスではない」としています。

「ソフトウェア要求」では、「リスクが受け入れ可能なレベルになったら終わり」と表現しています。そう言われればそうだけど、そのリスクが受けれ可能なレベルというのがわからないから苦労しているのだ、とも思ってしまいます。おそらくここで言いたいのは、あらかじめ教科書的に設定できるような答えはなく、どのようなアプローチをとるのか態度を定め、関係者と合意をとるのが最善だということなのだと思います。

例えば、複雑かつ厳密な計算ロジックのようなものがある場合は、事前に全てを明確にしてから作り込みに入らなければ完成しないでしょうし、逆に、ユーザーが軽妙に操作できるようなGUIを作るよう場合は、早めに作って見せてしまった方が良いと思います。また、これらの方法が1つのソフトウェアの開発中に混在する場合もありえます。

またプロジェクトに求められる要求(プロジェクト要求)によっても違います。納期、予算、スコープは死守だがプロジェクト期間は長いのか、逆に、スコープの調整はある程度弾力性があるが、かわりに早めに市場投入したいのか、といったことでも違います。

どのやり方を取るにせよ、たいていは開発中に要求の変更は避けられません。
そのため、要求定義を確定する段階では、「現時点でこの要求が我々の最大限の理解である」ということを全員が納得し、また、「将来変更が生じた場合は適切な変更プロセスを踏む」ことをお互いに約束することが重要になります。このような信頼関係を醸成しないと、要求を確定したくても確定できない、ということが生じてしまうのです。

おわりに

ということで、要求について書いてみました。SQuBOKは、要求に限らず様々な技術につながっています。品質というものは、そのように全方位的な性質を持っているのだと思います。そういった枠組みを最初に押さえてから、各分野の本を読むことで、ずいぶん理解が進むことがあったように思います。

さて、次は昨日に戻ってkazucocoaさんです!

参考文献

・ソフトウェア品質知識体系ガイド -SQuBOK Guide-(第2版) (SQuBOK)
・ソフトウェア要求 第3版 Karl Wiegers、Joy Beatty
・ソフトウェアエンジニアリング基礎知識体系 ―SWEBOK V3.0