Joel Spolskyの記事を訳した。方法論というより、単なるアルゴリズムの説明。翻訳wikiに置いたが、こちらにも転載。ミスの指摘など歓迎。
事例によるスケジューリング
2007年10月26日 金曜
ソフトウェア開発者はスケジュールを作りたがらない。大抵はなしですまそうとする。「出来上がったときが完成日さ!」と元気一杯見栄を切ってボスを喜ばせ、活気づいたままどこかにスケジュールは忘れ去られてしまう。
スケジュールがあるとしても、半端なものであることが多い。誰か一人によってしぶしぶ作られて、共有フォルダのどこかに置かれて、忘れ去られる。2年後、キャビネットの底で古びたプリントを見つけた人が反省会に持ってきてネタにするのだ。
「見ろよこれ。Rubyで1から書き直すのを2週間ですます予定だったらしい」
笑えるね。まだその真っ最中ならなおさら。
できることなら、やる価値のあるタスクだけに時間を注ぎたいだろう。しかし、一番やる価値のある仕事を決めるには、どの程度時間がかかるのか知る必要がある。「飛び跳ねる紙クリップ」機能と「金融計算関数を追加」機能のどちらに手をつけるか決めるには、それぞれにかかる時間を知らねばならない。
なぜ開発者はスケジュールを作らないのだろうか?理由は二つある。(1)作るのが面倒だ。(2)本当にそのとおりになるわけがない。正確にならないとわかっているスケジュールを誰がわざわざ作ろうとするだろう?
去年Fog Creekは、どれほど文句の多い開発者でも使いたくなるほど簡単なシステムを開発した。そして、我々の知る限り、このシステムはかなり信頼性の高い予測をしてくれる。名前は EBS(Evidence Based Scheduling:事例をもとにしたスケジューリング)と言う。
タスクがいつ始まっていつ終わったかの事例を、タイムカードなどの記録から集める。それらからスケジュールの将来を予測する。計算の結果となるのは完成予想日ひとつではなく、完成する確率を日付ごとに並べたグラフだ。
それはこういう見た目になる。
曲線が急であればあるほど、その日にリリースできると自信を持って言えることになる。
やり方を説明しよう。
1) 粉々にする
単位を日数や週の数で立てたスケジュールはうまくいかない事を我々はよく知っている。
タスクを小さく分けて行って、単位が時間になるまで細かく分けよう。16時間以下がいい。
分けることで、あなたがやるべきことの中身を実際に考えるように仕向けることができる。foo サブルーチンを書く。ダイアログボックスを作る。Fizzbottファイルをパースする。一つ一つの開発タスクにかかる時間は簡単に予想がつく。なぜなら、前にあなたは似たようなことをやったことがあるからだ。
あなたが大雑把な開発者なら、3週間程度の大きいタスクを作ることがあるだろう(「Ajax画像エディタを実装」とか)。そういう時あなたは、実際にどう作業するかを考えずにすましている。一つ一つの手順を詳細に想像せずに。実際何をするのかわかっていなければ、かかる時間を見積るのは不可能だ。
タスクの限度を16時間にしたのは、開発者に機能そのものをデザインさせるためだ。3週間で派手な「Ajax画像エディタ」を細かい設計なしに作ろうとするのは、言わせてもらえば正気じゃない。
どんな手順を踏んで作るのか把握せずに作ろうとしても、そのステップをいくつか踏み忘れたままで終わるだろう。
2) 時間を記録する
タスクにかかる時間を正確に予想するのは難しい。他の仕事が割り込んでくるかもしれない。予想もしないバグが出てきたり、ミーティングもあるだろう。Windowsが年貢の納めどきを迎えて、開発マシンを丸ごと再インストールする羽目になるかもしれない。
これらにどれだけ確保すればよいのだろう。いや、トラブルの類が全くなかったとしても、そのサブルーチンを書くのにどれだけかかるのか予想がつくだろうか?
実際のところ、不可能だ。
タイムシートをつけよう。各タスクにどれだけ時間がかかったか記録をとる。タスクが終わった後、実際かかった時間が予想とどれだけ違うか比べることができる。
その記録をグラフ上に点を打っていくと、各人にこういうグラフができあがる。
点のひとつひとつは完了したタスクを表している。横の座標が予想した時間で、縦の座標が実際に完了するのにかかった時間だ。予想の時間を実際の時間で割ると、そのタスクの勢いがわかる。タスクが予想とよりどれだけ早く済んだか、という割合だ。
プロジェクトを進めながらこのグラフに点を打っていくことで、開発者ごとに勢いのレコードが溜まってゆく。
- 完璧な開発者は、実際いれば伝説に残る存在だが、想像上にしかいない。全て予想通りの時間でぴったりタスクを済ませる。
つまり、勢いの記録は {1, 1, 1, 1, 1, ... } のようになる。 - 典型的な、予想下手な開発者は、グラフ上にバラバラな点を打つ。長めに予想することもあれば、短すぎることもある。
この場合の記録は、例えば {0.1, 0.5, 1.7, 0.2, 1.2, 0.9, 13.0} のようになる。 - 多くの開発者はスケールを取り違えるが、相対的には概ねよい予想をする。
予想外のバグ修正やミーティング、コーヒーブレイク、うるさく心配するボスの割り込みなどがあいまって、ほとんどのタスクは予想より長くかかる。
こういった平均的な開発者は、たいてい1以下で、ぶれの少ない勢いのレコードを残す。例えば {0.6, 0.5, 0.6, 0.6, 0.5, 0.6, 0.7, 0.6} のようになる。
タスクの予想は、開発者が経験を積むにつれて精度が上がってゆく。なので、古い勢いの記録、例えば6ヶ月より前のものは勘定に入れないほうがよいだろう。
チームに新しい開発者が加わった時には予想の記録がないが、その場合は下手な方で仮定しておく。広い範囲で勢いのダミーの記録を用意しておいて、6つほどタスクをすませるまではそれを計算に使う。
3) 未来をシミュレートする
タスクをすべてこなしたら出荷できるとすると、プロジェクトの出荷日は単に全タスクの予想時間を足し合わせれば出てくる。この方法はもっともらしく見えるがうまくいかない。
代わりに EBS ではモンテカルロ法で出荷日を推測する。
プロジェクトの将来のシナリオを100通りシミュレートする。ありえるシナリオのそれぞれは1%に対応する。それらの出荷日を頻度表として集計すると、それぞれの日付について完成する確率のグラフができあがる。
シナリオを計算するとき、各タスクの予想時間をその開発者の勢いで割る。この勢いはステップ2で集めた履歴からランダムに選んだものだ。
たとえばある未来ではこの開発者のタスクはこうなる。
これを100回繰り返そう。合計一つは1%の可能性を持つので、あなたは任意の日に出荷できる確率を知ることができる。
先ほどの開発者たちがどうなるか見てみよう:
- 完璧な開発者の場合、常に勢いは1だ。この勢いで割ってもタスクの予想時間には何の影響もない。つまり、シミュレーションは100回すべてで同じ出荷日をはじき出し、その確率は100%である。まるでおとぎ話だ。
- 予想下手な開発者の場合、勢いは広く分布する。範囲は0.1から13.0までというところだろう。
この中からランダムに選んで値を割るから、シミュレーションの結果は毎回大きく変動する。出荷日の確率は浅く広く分布し、明日出荷できるかもしれないし、遠い未来かもしれない。とはいえ、読み取れることはある:あなたは出荷日に自信を持ってはならない。 - 平均的な開発者は互いによく似た勢いを持つ。 {0.6, 0.5, 0.6, 0.6, 0.5, 0.6, 0.7, 0.6} のようになる。
これらの中から選んで割ると、各タスクの予想時間は割り増しで計算される。8時間のタスクは13時間になるか、あるいは他では15時間になる。
このように、 EBS はあなたの楽観的な見通しを補ってくれる。予測はあなた本人による実証済みの、実例の記録によって、あなたの楽観さ度合いとぴったり同じだけ補われる。勢いが0.6の前後あたりで狭く分布するので、出荷日の範囲も狭い範囲に収まる。
モンテカルロシミュレーションでは毎回、時間単位のデータをカレンダー上の単位に変換しなければならない。その差異には各開発者の出勤予定、旅行、休日の日なども考慮する必要がある。また、最後に仕事を終えるのがどの開発者か判別しなければならない。なぜなら、そのとき初めてチーム全体の仕事が終わるからだ。
こういった計算は骨が折れるが、幸いなことに、骨を折るのはコンピュータの得意とするところだ。
強迫症になる必要はない
この間行った釣り旅行についてボスが武勇伝を長々と語り出したらどうすればよいだろう?いても仕方がないのに経営会議に出なければならなくなったら?コーヒーブレイクは?新人のために開発環境をセットアップしてやるのに気づいたら半日使ってしまったら?
Brettと私がFog Creekでこの手法を開発している時、こういった突発的で時間を食う事に頭を悩ませていた。時にはこの種の時間の合計が開発にかけた時間を超すことすらある。
こういったことにもタスクを立てるべきだろうか?時間を予想して、タイムシートで記録をつければよいのだろうか?
まあ、そうしたいならできないことはない。 EBS はそれでもうまく働く。
しかし、そうする必要はない。
実のところ、 EBS はよく動いてくれる。何のタスクの最中にどんな仕事が舞い込んだとしても、あなたはただ時計を動かしてさえいればよい。不安に思うだろうが、 EBS はそれでも良い予測を生む。
簡単な例を紹介しよう。簡単にするために、ここにジョンという平凡なプログラマがいたとする。彼の仕事はどこかの低級な言語の必要とする、一行のゲッターとセッターを書くというものだ。
private int width;
public int getWidth () { return width; }
public void setWidth (int _width) { width = _width; }
いやいや、わかっている。あほらしい例だが、あなたはこういう奴を見たことがあるはずだ。
ともかく、このゲッターなりセッターなりを書くのに彼は毎回2時間かかる。従って、彼のタスクの予想時間はこのようになる:
{ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, … }
さて、彼は仕事中かわいそうなことに頻繁にボスに邪魔される。ボスはマカジキ釣りについて語りだすと2時間は止まらない。
もちろんジョンは「マカジキについての退屈な雑談」と名付けたタスクをタイムシートに増やしてもいいが、この行いは政治的に適切とは言い難い。
代わりにジョンはそのとき開いていたタスクに時間をそのまま記入する。実際の経過時間の記録はこうなる。
{ 2, 2, 2, 2, 4, 2, 2, 2, 2, 4, 2, … }
これを予想時間とあわせて勢いを計算すると、このようになる。
{1, 1, 1, 1, 0.5, 1, 1, 1, 1, 0.5, 1, … }
モンテカルロシミュレーションにはどう影響するだろうか。予想の2時間が1でなく0.5で割られる確率は、将来ボスがジョンを邪魔する確率とまったく同じになり、それを真面目にタスクとして登録した場合と同じ結果になる。 EBS はこちらでもうまく動くのだ!
実のところ、 EBS はタイムシートに貼り付いて真面目に記録を取る開発者よりも正確な予測をする。
人に話すとき、私はこれを次のように説明している。開発者が邪魔されたとき、彼らは…
- タイムシートに真顔で「邪魔された」と追加して、自分が釣り談義でいかに時間を無駄にされているか経営者に気づいてもらえるようにするか、あるいは
- 邪魔された項目を新たに作らず、その時担当するタスクに無駄にした時間を加算して、俺を誘いもしなかった釣りについての長話がなければ完璧に正しかったはずの自分の予想時間をそのままにしておくか
…のどちらかで、あなたのチームの開発者がどちらのタイプであったにしても、 EBS は同じ正確な結果を出す。
4) プロジェクトを積極的に管理する
この手法が順調にいけば、あなたはプロジェクトを納期内に収めるために積極的に活用することができる。
例えば、各機能に優先度を振って整理しておけば、優先度の低い機能を抜きにした場合どれだけ出荷日が早まるかをすぐに見てとれる。
また、開発者各人についての完成予想日の分布を見てもよい。
- 開発者によっては(図で言う3行目の Milton)、完成日の予測がほとんどつかない者もいる。そういう開発者はうまく予想するよう練習に努める必要がある。
- そうでない開発者(図の4行目の Jane のような)は、完成日の予想範囲が狭い範囲に収まっているものの、少し遅れ気味だ。
彼女の負っているタスクをいくらか他の開発者にまわしたほうがいいだろう。 - それ以外の開発者(私だ。イェーイ)はクリティカルパスにまったく関わらないので、心配ない。
スコープの動き
あなたが詳細の詳細まで完璧に詰めてからプロジェクトにとりかかるのなら、 EBS はうまく働く。しかし、実際は予定にない機能をやることになるだろう。
新しいアイデアが浮かぶこともあるし、営業員が存在しない機能を約束して帰ってくるかもしれない。お偉いさんの一人がお仲間と談笑しながらゴルフ中、あなたの開発しているGPSつきゴルフカートに心電図を追加するという素晴らしいアイデアを思いつくかもしれない。
いずれにしろ、あなたのスケジュールには新たな遅延が加わる。
単純に考えれば、これらに対応するため、バッファ期間を用意すればよい。以下のようにそれぞれのバッファを用意したとする。
- 新機能のアイデア
- 競合製品への対抗
- 全員のコードをマージして統合
- デバッグ
- ユーザビリティテスト(とそのフィードバック)
- ベータテスト
すると、新機能を思いついた時は、1番のバッファを削ってそれに充てることになる。
新機能を次々追加するうちにバッファがなくなってしまったらどうなるだろうか。まあ、 EBS の完成予想日は遠くにずれてゆくだろう。こういう状況を把握するには、毎日完成予想日の分布のスナップショットを取ることだ。
横軸はシミュレーションのスナップショットをとった時刻で、縦軸は完成予想日を示す。三本の折れ線はそれぞれ次を意味する。上のは完成確率が95%の日。中央は50%。下は5%。つまり、折れ線が互いに近づくほど、完成予想日の範囲は狭まることになる。
もし完成予想日がどんどん遅れていっているなら(グラフが右肩上がりなら)、プロジェクトに何か問題がある。遅れ具合が1日に1日ずつでないなら、タスクをこなす勢いよりも追加されるほうが早いので、そのプロジェクトは決して終わらない。このグラフが徐々に収束しているなら、出荷予定も定まってきていると見ていい。
使用上の注意
この手法を使ううち見つけた、いくつかの注意すべき点をあげる。
1) 担当のプログラマだけがタスクを予想すること。
どんな方法であれ、マネージャが予定を立ててそれをプログラマに渡すようなことでは必ず失敗する。その機能を実装するプログラマ本人だけが、必要な手順を見定めることができる。
2) バグを見つけたらその場で修正して、その時間をバグ部分を実装したタスクに加算すること。
バグは予想のつかないものなので、バグフィックスは予定してやることはできない。実装にバグを見つけたら、修正にかけた時間はその部分を実装したタスクに時間を加算するといい。単に動くコードの分だけでなく、デバッグ済みのコードが仕上がるまでの時間を記録することになるので、その分 EBS の予測が正確になる。
3) 予想時間が短いの長いのでマネージャに文句を言わせないこと。
新米のソフトウェアマネージャによくあることだが、「適度にきつい」(非現実的なほど短い)スケジュールを与えることで、プログラマを「きりきり」働かせることができると考えることがある。私から見ると、これは無能のやることだ。この類のモチベーションは長続きしない。
私が遅れたスケジュールのもとにあると知っても、やる気をなくして絶望し落ち込むだけだ。逆にもし予定以上に早く進んでいると知れば、生産的で活発に働いていられるだろう。スケジュールは社会心理の実験場ではない。
なぜマネージャはこういったことをしようとするのだろう?
プロジェクトが始まって技術マネージャが去り、ビジネス関係者が来て彼らが検討の結果3ヶ月でできるという機能リストを持ってくる。しかし、それらは実際12ヶ月かかるのだ。
コーディング作業の手順を抜きに考えたなら、機能の実装は n 時間ですむが、現実では多くは 4n 以上かかる。あなたがタスクの詳細を詰め、スケジュールを立て直すと、彼らの考えるよりプロジェクトはずっと長くかかることがわかる。経営陣は不満がるだろう。
無能なマネージャは、これを解決するため、プログラマをより早く働かせる方法を探す。これは現実的ではない。チームの人数を増やした場合、新人がコードを把握して追いつくまでに時間がかかる。その後も数ヶ月は50%程度の能率でしか働かないだろう(しかも、彼らの世話に時間をとられて既存メンバーの能率も下がる)。
この方法は一時的に10%増しのコード量を生むかもしれないが、それは1年で100%生むコードと引き換えのものだ。割りのいい取引とは言えない。これは種もみを食べてしまうことに少し似ている。
また、言うまでもないが、開発者たちを酷使することで、デバッグにかかる時間は倍増し、ただでさえ遅れているプロジェクトはさらに遅れることになる。なんということでしょう。
いずれにせよ、4n を n に押し込む方法は存在しない。もしできると思うのなら、あなたの会社の銘柄名を教えて欲しい。売るから。
4) スケジュールは積み木箱である。
積み木が沢山あって、箱のなかに収まらないときは、二つの手段がある。もっと大きい箱を持ってくるか、積み木ブロックの数を減らすかだ。
6ヶ月で出荷したいが、12ヶ月かかるとわかっているときは、出荷を延期するか、機能を選んで削るしかない。単にブロックを小さく縮めてすますことはできない。仮にできるふりをしたとしても、あなたは先を見据える機会を捨てて、目の前に見えているものを見えないと自分に嘘をついているだけだ。
機能を削る羽目になるというのは、スケジューリングのいいところだ。なぜかって?
あなたが実装したい機能がふたつあるとしよう。一つは実用的で、製品を素晴らしいものにする。もう一つはとても簡単で、プログラマは作業にかかりたくてたまらない(見て見て!<ウインク>)が、製品の目的からみて特に意味はない。
スケジュールを立てずにすますなら、プログラマは間違いなく簡単で楽しいものをやろうとする。そして時間を使ってしまい、有用で重要な機能を作るために延期せざるを得なくなる。
スケジュールを作りさえすれば、実際の作業にかかる前であっても、何かの機能を削る必要があることに気づくことができる。楽しく無意味な機能は削られて、あなたの製品は有用な機能と早い出荷日に恵まれることになる。
私がExcel 5に関わっていたとき、最初に作られた機能リストは巨大で、とても納期には収まるものではなかった。「なんてことだ!」我々は思った。「一つ残らず重要な機能なのに!マクロ編集ウィザードなしでどう生きていけばいいんだ?」
他に選択肢もなく、我々は「ソフトの根幹をなす」と考えていた機能をいくつか除いてスケジュールを納期に収めた。誰もが削除を残念に思った。慰めのため、我々は機能をカットしたのではなくExcel 6に延期したのだと思うことにした。
Excel 5が完成に近づいた時、私は同僚のEric MichelmanとExcel 6の仕様にとりかかった。二人でまずExcel 5でカットされた機能のリストを眺めることにした。私たちは驚いた。なんだこれは。これほどクズ機能ばかり集めたリストがあっていいものか。
見直してみると、リスト中で実装する価値のあるものは一つもなかった。スケジュールの為に機能を絞ることは我々の一番の成功といえる。でなければ、Excel 5のスケジュールは倍に伸び、50%を占めるクズ機能が積まれ、その後永遠に後方互換性のために残ることになっていただろう。
まとめ
事例によるスケジューリングを使うのはとても簡単だ。各イテレーションの初めに詳しく予想を立てるのに1日2日かける。その後は毎日数秒、実際の経過時間をタイムシートに記録しながら進める。比べて得られる利益は相当なものだ。現実的なスケジュールが立つのだから。
現実的なスケジュールはよいソフトウェアを作るカギとなる。必要な機能が先に実装できるようにし、どのような物を作る判断を下す指針になる。製品はすばらしいものになり、ボスは笑顔であり、顧客は満足し、そしてなにより、あなたは5時に家に帰ることができる。
P.S.
事例によるスケジューリングはFogBugz 6.0に組み込みの機能のひとつだ。
訳語
- confidence distribution curve : (完成する確率を日付ごとに並べたグラフ)
- 最初のグラフのこと。「この日に出荷できる確率は××%」を図に折れ線として表したもの。
- estimation history : (予想の履歴)
- 二つ目のグラフのこと。記事中に名前は出ていない。
予想時間と経過時間をプロットしたもの。勢いは原点からの傾きになる。
- estimated time : 予想時間
- 開発者がそのタスクの作業をすますのに必要と考える時間。
- estimator
- 予想をする開発者 : 直訳すると「予想者」になるが、どちらにしても開発者本人が予想することになるので言い換えた。
- elapsed time, actual hours : 経過時間、実際の時間
- タスクが終了するまで実際にかかった時間。
タスクが終わってクローズされた後もバグなどで加算されることがある。
- Evidence Based Scheduling:事例によるスケジューリング
- プロジェクトの完成日を予測するための手法。モンテカルロ法を使ったアルゴリズム。記事本文を参照。
- ship date: 完成日、完成予定日、出荷日、出荷予定日
- 1) ソフトウェアが実際完成する日。
2) プロジェクトを間に合わせねばならない納期。
- task:タスク
- 1) ソフトウェアに機能を実装する手順を分割したうちの一ステップ。長さの単位は時間。関数を書くとか、ダイアログを作るとか。
2) 開発者に割り当てる仕事。
記事中では触れられていないが、FogBugzでは親タスクと子タスクの概念がある。
- velocity : 勢い
- 勢い = 予想時間 ÷ 経過時間
あるタスクが予想に対してどれだけ早く終わったかを示す割合。予想の半分で終わったら勢いは0.5になる。予想の倍かかったら勢いは2になる。