前回↓なところで、考察したものの実験コードです。
コード品質を計測して、コードの完成までを見積もるための考察 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2431
あ、あまりにもダサいので、公開を躊躇していたのですが…まあ、完成コードができあがるまでの経過ということで。
/** class Task + Do() + GetStatus() : CodeTask.exe: CodeTask.cs csc /target:exe /reference:System.dll CodeTask.cs */ using System; using System.IO; // タスク public class Task { // ランダム値 private static Random _rnd = new Random(Environment.TickCount); // 状態 private TASK_STATUS _status = TASK_STATUS.START; // 期間 private double _period = 0; // タスクを実行 // 地道に遷移図を直すパターン public void Do() { int r = 0; switch ( _status ) { case TASK_STATUS.START: // 開始 _period += 1; _status = TASK_STATUS.DONE_CODE; // コード完了 break; case TASK_STATUS.DONE_CODE: // コード完了 _period += 1; _status = TASK_STATUS.DONE_TEST; // 試験完了 break; case TASK_STATUS.DONE_TEST: // 試験完了 _period += 0; r = _rnd.Next(10); if ( r < 9 ) { _status = TASK_STATUS.TEST_OK; // 試験合格 } else { _status = TASK_STATUS.TEST_NG; // 試験失敗 } break; case TASK_STATUS.TEST_OK: // 試験合格 _period += 0; _status = TASK_STATUS.CODE_COMPLETE; // コード完成 break; case TASK_STATUS.CODE_COMPLETE: // コード完成 _period += 0; _status = TASK_STATUS.END; // 完了 break; case TASK_STATUS.TEST_NG: // 試験失敗 _period += 1; _status = TASK_STATUS.DONE_RECODE; // コード修正 break; case TASK_STATUS.DONE_RECODE: // コード修正 _period += 1; _status = TASK_STATUS.DONE_RETEST; // 再試験完了 break; case TASK_STATUS.DONE_RETEST: // 再試験完了 _period += 0; r = _rnd.Next(10); if ( r < 9 ) { _status = TASK_STATUS.TEST_OK; // 試験合格 } else { _status = TASK_STATUS.TEST_NG; // 試験失敗 } break; case TASK_STATUS.END: // 完了 _period += 0; _status = TASK_STATUS.END; // 完了 break; } } // 状態を取得 public TASK_STATUS GetStatus() { return _status; } // 期間を取得 public double GetPeriod() { return _period; } } // 人員 public class Person { private Task _task ; public bool SetTask( Task task ) { _task = task ; return true; } public bool Do() { if ( _task == null ) return false; _task.Do(); return true; } } // タスクの状態 /* 状態遷移 開始 -> コード完了-> 試験完了 試験完了 -> 試験合格 -> コード完成 試験完了 -> 試験失敗 -> コード修正 コード修正 -> 再試験完了 -> 試験合格 コード修正 -> 再試験完了 -> 試験失敗 */ public enum TASK_STATUS { START = 0, DONE_CODE, DONE_TEST, TEST_OK, CODE_COMPLETE, TEST_NG, DONE_RECODE, DONE_RETEST, END } public class Program { public static void Main( string[] args ) { double sum0 = 0.0; for ( int k=0; k<100; k++ ) { double sum = 0.0; Person person = new Person(); for ( int j=0; j<100; j++ ) { Task task = new Task(); person.SetTask( task ); for ( int i=0; i<100; i++ ) { // task.Do(); person.Do(); TASK_STATUS st = task.GetStatus(); // Console.Write("{0}->", st.ToString()); if ( task.GetStatus() == TASK_STATUS.END ) break; } sum += task.GetPeriod(); // Console.WriteLine("{0}", task.GetPeriod()); } Console.WriteLine("period sum,{0}", sum ); sum0 += sum ; } Console.WriteLine("period ave,{0}", sum0/100.0 ); } }
状態遷移のところは、ひとまず Task クラス内にベタベタに書いています。この部分は本来は状態クラスを分離して、タスク遷移とは切り離しておきたいところ。
タスクを実行するのは、人員クラス(Person クラス)なんですが、このコードだとパラレルに動けるような複数名の場合がシミュレートできません。あと、コーディングする人とテストする人が別々の役割になるような待ち行列状態がシミュレートできません。
そうそう、複数タスクが並列に動く場合もシミュレートできないという…ちょっと、というか、かなり中途半端なプログラムです。
まあ、これを実行してどう分析するかというと、外側のループで 100 回試行しているのでこれを Excel で処理します。
日数で、累積をして表にします。
100 個のタスクが終わるまでの日数と累積した確率をグラフにします。
オレンジの行が、最頻値になります。このプログラム場合、タスクの日数は2日間(コーディング1日、試験1日)発生バグ率が 10% なので、平均は 2.2 日間になります。100 個のタスクをこなすと、予想通り 220 日間が最頻値になります。不具合の発生率は正規分布に準じることになるので、この最頻値がイコール中央値になりますね。
さて、プロジェクトのマネージメントの立場から言うと(どこの時点までリスクを含めるかというと)、中央値の 220 日を取るのではなく、プロジェクトが 80% の確率で終わる時点を取ります。
確率を累積することになるので、緑の行の 226 日時点になります。
これで何がわかるかというと、
■理想日数 200 日間で終わる確率は 0% である。
当たり前ですが、不具合が発生する以上、理想的なコーディング日数 × タスク数とはなりません。
この場合は、2 日間 × 100 タスクということで、200 日間です。
■中央値は、不具合発生率を掛けた地点になる
このシミュレーションの場合、10 回に 1 回不具合が発生するので、不具合発生率は 10 % になります。
普通は、これを掛けたところの 220 日間が、コーディング完成の予想日数なわけですが、この値は中央値になるので累積の確率は 50 % になります。5分5分の勝負では、プロジェクトマネージメントとしては不満ですね。
■累積確率の 80% ラインを予定日数とする
累積した確率が 80% ラインあるいは、90% ラインにします。
80% にするか、90% にするかは、予定日数を超えた場合の費用によって決定します。
このシミュレーションの場合は、80% ラインが 226 日間、90 % ラインが 230 日間となるので、4 日間分の費用が、リスクを超えたときの損失よりも少なければ、90 % にすればよいわけです。
理想日数が、200 日間なので、15 % ほど費用を割り増しすれば、安全圏が 90 % になるというわけです。
さて、この理想日数から実行するときのスケジュールを立てるには、どうするのかというと…続く。