詳細設計書に何を書くべきか?

詳細設計書の書き方については黙っていられないので、ちょっと意見を言わせてもらう。

私も「詳しすぎる詳細設計書 - SiroKuro Page」で示されているようなコードと1対1に対応したような詳細設計書は、書くだけ無駄だと思っている。ただ、ちゃんとした詳細設計書をつくるなら、処理内容(内部の処理の実装方法)の書き方をどのように実装言語に合せるかではなく、処理内容を一切書かないようにするべきだと考えている。

なぜなら、処理内容をいくら詳細に記述したところで、それは仕様ではなくコードであり、仕様の代わりに記述したコードでは、バグも含めて記述されているため、そのコードのみでは正しいか間違っているかを判定できないからだ。
コードの他にどういった動作が正しいのかを判定する基準が必要で、その基準が仕様であり、詳細設計書にはその仕様を記述する必要があると考えている。

現に、例として示された処理概要では、出力の平均身長差を符号付きで返すのか、絶対値で返すのかが記述されていない、もし絶対値で返していたらそれはバグなんだろうか?仕様なんだろうか?処理概要では符号付きで返すみたいだけども、それは正しいのか?(重箱の隅をつつくようで申し訳ないが、バグをほじくり返すには重箱の隅をつついて、仕様を明らかにする必要があるのです。)

何も述べられていないと言うことは、「男性の平均身長」と「女性の平均身長」の差を符号付きで返したらバグなのか、仕様通りなのかも決められていないことになる。
だから、ある人がからすれば仕様通りでも、別のある人からすればバグになってしまう。
で、それがユニットテストでも判明せず(なんせ仕様として明記されてないので)、後の工程になってからバグとして顕在化する。

 例で示されている詳細設計では、処理の実装方法については述べられているが、以下の仕様に関して(特に異常な値の入力に関して)何も述べられていない。この辺が「実装するには物足りず、基本仕様としては詳しすぎる 」と言われる理由なのではないか?

  • nullが渡された場合どうなるか
  • サイズ0のリストが渡された場合どうなるか
  • 「男性の平均身長」と「女性の平均身長」の差は絶対値で返すのか、符号付きで返すのか?
  • 男性の人数が0だった場合はどうか?
  • 女性の人数が0だった場合はどうか?
  • リスト中にnullオブジェクトが入っていた場合は?*1
  • 何人程度まで考慮しなければいけないか?*2
  • 計算精度はどのくらい必要か?

では詳細設計書には処理内容の代わりに何を書いたらよいか?

処理内容を一切書かない代わりに書くべきなのは、詳細な仕様だ。それもテスト仕様書レベルのような本当にソフトウェアの仕様と密接に結びついた仕様が必要だ。

具体的には、TDDやBDDの考え方みたいに、どういう入力に対して、どういう結果になるかというブラックボックステスト的な振る舞いのみを記述する必要があると考えている。*3

参考:設計書の非常識1.設計書には詳細な実装方法を書く - Sacrificed & Exploited


この考えは
テスト仕様書に従ってテストをし、バグを管理するということは、
つまりバグかどうかの判断はテスト仕様書に基づいて行っているということであり、
ソフトウェアの仕様に最も近い表現をしているのはテスト仕様書なのではないか?
という思いつきをベースにしている。

これまでは、テスト仕様書に近いレベルの設計書を書く代わりに、詳細設計と称して処理の詳細=実装方法を記述してきた。そして、仕様は曖昧なままになった。
だからいくら詳細設計書をレビューしても、誤字脱字程度しか見つけられなかったのではないのか?
そして、曖昧なままの仕様に基づいて実装されたコードがユニットテストをすり抜け、結合テストシステムテストでバグとして顕在化してきたのではないか?
結合テストで出るバグのほとんどが仕様バグになってしまうのもこのせいではないか?(というのは言い過ぎか?)


また、このような単純なユニットテストレベルの仕様は別として、状態遷移やそのシステム独自の計算アルゴリズムといった、テスト仕様書だけでは説明しきれない、一段上のレベルの抽象的な動作を説明する設計書は別に必要だと思う。


実際に書き直したらどうなるのか

私が思いつく範囲で考えた仕様は次のとおり。

入力:
「性別と身長のペア」のリスト
出力:
男性の平均身長」と「女性の平均身長」の差
仕様

  • リストとしてnullが渡された場合、NullPointerExceptionを投げる
  • サイズ0のリストが渡された場合、0を返す
  • 返す値は、「男性の平均身長」と「女性の平均身長」の差を絶対値で返す。
  • 男性の人数が0だった場合は、女性の平均身長のみを返す。
  • 女性の人数が0だった場合は、男性の平均身長のみを返す。
  • リスト中に含まれるnullは計算から除外する。

とりあえずこれだけでも仕様として不定な部分は少なくなった。(と思う。計算精度が明記されていないけど。)
これだけでも十分だけど、テストパターンを追加するとしたら次のようになる。

  • 引数チェックなど
    • nullを渡したら、nullPointerExceptionを投げる
    • サイズ0のリストを渡したら0を返す
  • リスト内の男性と女性の組み合わせパターン(ちょっと冗長だけど。)
    • 片方が0人
      • 男性1人,女性0人
      • 男性2人,女性0人
      • 男性3人,女性0人
      • 男性0人,女性1人
      • 男性0人,女性2人
      • 男性0人,女性3人
    • 片方が最低1人
      • 男性1人,女性1人
      • 男性1人,女性2人
      • 男性1人,女性3人
      • 男性2人,女性1人
      • 男性2人,女性2人
      • 男性2人,女性3人
      • 男性3人,女性1人
      • 男性3人,女性2人
      • 男性3人,女性3人
  • リスト内にnullを含むパターン
    • リストがnullのみの場合、0を返す
    • nullと男性3人,女性3人の場合、nullが無視されて計算される。

あとは適当なテストデータを作ってテストすりゃあいい。
double型だと0.1という値は二進数では循環小数になって誤差が出るので、こういったことも考慮にいれて意地悪なテストデータを作るとなお良い。

*1:入力としてあり得ないなら例外を投げるべきだし、あり得るなら、その考慮をしたうえで処理をしなければいけない。

*2:最後の2つはシステム全体の設計に関わってくるので、基本設計で述べられている必要があり、詳細設計で記述する必要がないかもしれないけど

*3:内部の実装方法については一切触れないようにするというのは、javaのインターフェースの考え方に少し似ている。