@ledsun blog

Hのキーがhellで、Sのキーがslaveだ、と彼は思った。そしてYのキーがyouだ。

今週の作業メモ

spring

Rails 5.2.3でrails consoleを実行すると、プロンプトが出る前で止まります。

~ bundle exec rails --version
Rails 5.2.3
~ bundle exec rails c
^CTraceback (most recent call last):
    15: from bin/rails:3:in `<main>'
    14: from bin/rails:3:in `load'
    13: from /Users/shigerunakajima/circular_dependency_detected_job/bin/spring:15:in `<top (required)>'
    12: from /Users/shigerunakajima/circular_dependency_detected_job/bin/spring:15:in `require'
    11: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
    10: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
     9: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
     8: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
     7: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
     6: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/rails.rb:24:in `call'
     5: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
     4: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:35:in `call'
     3: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:42:in `warm_run'
     2: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:62:in `run'
     1: from /Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:117:in `verify_server_version'
/Users/shigerunakajima/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/spring-2.1.0/lib/spring/client/run.rb:117:in `gets': Interrupt

https://github.com/rails/spring/blob/v2.1.0/lib/spring/client/run.rb#L117 ここのserver(どうもUnixソケットらしい)の読み込みで止まっている。

試しに、Gemfileからspringを消してみると、期待通りにプロンプトが表示されます。

~ bundle exec rails c
Loading development environment (Rails 5.2.3)
irb(main):001:0> 

@p_ck_ 曰く

springが悪さをするのに疲れ果てて最近は DISABLE_SPRING=1 してます 似たような状況のときは bin/spring stopをしたら直っていたので毎回それをしていた記憶があります

Bingo!

bootsnap

ruby 2.7-preview1でRails 6.0.0.rc2の rails --version を実行すると、エラーが起きます。

~ rails --version
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Traceback (most recent call last):
        36: from bin/rails:3:in `<main>'
        35: from bin/rails:3:in `load'
        34: from /Users/shigerunakajima/my_first_rails_6/bin/spring:15:in `<top (required)>'
        33: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        32: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require'
        31: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
        30: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `load'
        29: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
        28: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
        27: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
        26: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
        25: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `load'
        24: from /Users/shigerunakajima/my_first_rails_6/bin/rails:8:in `<top (required)>'
        23: from /Users/shigerunakajima/my_first_rails_6/bin/rails:8:in `require_relative'
        22: from /Users/shigerunakajima/my_first_rails_6/config/boot.rb:4:in `<top (required)>'
        21: from /Users/shigerunakajima/my_first_rails_6/config/boot.rb:4:in `require'
        20: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/setup.rb:30:in `<top (required)>'
        19: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap.rb:30:in `setup'
        18: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/compile_cache.rb:9:in `setup'
        17: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:50:in `require_relative'
        16: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `require'
        15: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:288:in `load_dependency'
        14: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `block in require'
        13: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
        12: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
        11: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
        10: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
         9: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
         8: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/compile_cache/iseq.rb:1:in `<top (required)>'
         7: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `require'
         6: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:288:in `load_dependency'
         5: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/activesupport-6.0.0.rc2/lib/active_support/dependencies.rb:322:in `block in require'
         4: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:32:in `require'
         3: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
         2: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
         1: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require': no implicit conversion of String into Integer (TypeError)

エラーが起きているのはこの辺 https://github.com/Shopify/bootsnap/blob/v1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb#L17-L24

エラーが起きるときの path の値は "bootsnap/bootsnap"です。 require_without_bootsnap は require の alias_methodなので、 require "bootsnap/bootsnap"で再現します。

~ irb
irb(main):001:0> require "bootsnap/bootsnap"
Traceback (most recent call last):
        7: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/bin/irb:23:in `<main>'
        6: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/bin/irb:23:in `load'
        5: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        4: from (irb):1
        3: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:34:in `require'
        2: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:130:in `rescue in require'
        1: from /Users/shigerunakajima/.rbenv/versions/2.7.0-preview1/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:130:in `require'
TypeError (no implicit conversion of String into Integer)

これでRuby 2.7 + Rails 6ではなくRuby 2.7 + bootsnapの問題だと絞り込めました。bootsnapのissueを漁ると https://github.com/Shopify/bootsnap/issues/258 が 発見できます。 https://github.com/Shopify/bootsnap/issues/258#issuecomment-518726704 をみるとmasterブランチでは修正済みのようです。

https://github.com/Shopify/bootsnap/commit/a163c7cddfccd68277f6ea43b1831378509817c9 パッチの作者は須藤さんや!さすがや、俺の遥かに前を行っているッ!!! Ruby2.7からはRubyのリビジョン番号が数値からgitのSHAハッシュに変わったので、数値前提のソースコードが壊れたらしい。なるほど・・・そういう変更もあるのか。 bootsnap.c なので、bootsnapはC拡張なのねえ。

ていうかBootsnapって、なんなんだろう?名前しか知らないや

READMEによりますと、Rubyプログラムの起動を早くするためのgemです。大きなRailsアプリケーションだと、50%以上の高速化の実績があります。主な手法としては Kernel#require を上書きして、キャッシュファイルをつかうことで、読み込むrubyスクリプトの検索を速くします。 今回エラーが出ていた https://github.com/Shopify/bootsnap/blob/v1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb#L17-L24 は、まさにこのKernel#require を上書きしている部分!

もう一つの最適化手法にiSeq(コンパイル済みのRubyバイトコード)のキャッシュがあります。MessagePack形式でファイルに保存しておくことで、Ruby本体による「ファイルの読み込み〜コンパイル」を「ファイルの読み込み」だけにできます。iSeqはコンパイルしたRubyのバージョン(だけでなくリビジョン単位)によって変わるので、キャッシュが有効かどうか判断するには、Rubyのリビジョン番号が必要です。このリビジョン番号を取るところがRuby 2.7で動かなく成っていたのの修正が https://github.com/Shopify/bootsnap/commit/a163c7cddfccd68277f6ea43b1831378509817c9 なるほどー!

jQueryUI ダイアログ

jQueryUIダイアログには、閉じた時にその前に選択していた要素を再選択する機能があるようです。

Upon closing a dialog, focus is automatically returned to the element that had focus when the dialog was opened.

https://api.jqueryui.com/dialog/

なるほど・・・

closeするときに this.opener をfocusします。 https://github.com/jquery/jquery-ui/blob/master/ui/widgets/dialog.js#L224

this.opnerは開いたときの document.activeElement なのかな? https://github.com/jquery/jquery-ui/blob/master/ui/widgets/dialog.js#L273

$.ui.safeActiveElement は、IEのエラーを出なくした document.activeElement 取得関数です。 https://github.com/jquery/jquery-ui/blob/74f8a0ac952f6f45f773312292baef1c26d81300/ui/safe-active-element.js#L12

タスクの完了からはじめるプロジェクト進捗管理

何の話?

タスクの進捗状況をパーセンテージで管理することがあります。

例えば、進捗報告会議で現在着手中のタスクAの進捗を報告するとき「50%完了しています。」という報告の仕方です。 進捗を「タスクの進捗率」で測る方法には、ちょっとした問題があります。

「予定しているタスクのうち2つ完了しています。残りのタスクは3つです。」というように、完了したタスクの数と未完了のタスクの数で報告すると、プロジェクトの進捗管理が、すこし良くなります。

どんなメリット?

プロジェクトの進捗を「タスクの進捗率で測る」方法に比べて、「完了したタスクの個数を数える」方法は、早く正確な進捗情報が手に入ります。 早く正確な情報が手に入れば、プロジェクトが遅れたときにとれる対応策の選択肢が増えます。

  1. お客さん(ステークホルダー)とスケジュールの調整をする
  2. プロジェクト全体で確保してあるバッファを使う
  3. 予想より早く作業が進んでいる人を、遅れている作業のヘルプに割り当てる

プロジェクトの遅れの発見がおそいと、1のような大きな対応しか取れません。 遅れを早くみつけると、2, 3のような、会社内、チーム内のより小さな対応でカバーできるチャンスが増えます。

遅れを早く見つけ、速く小さな対応をする、プロジェクトマネージメントの腕の見せどころです。

f:id:ledsun:20190514170032p:plain
プロジェクトマネージメントの腕の見せどころ

進捗を進捗率で測るときの落とし穴

なぜ、「タスクの進捗率」で測るより、「完了したタスクの個数」で測る方が、早くタスクの遅れに気づけるのでしょうか?

「タスクの進捗率の報告」には次の性質があります。

進捗率10%のタスクは、それまで掛かった時間の10倍の時間を掛けても完了す(100%にな)るとは限らない

なぜでしょうか?

不確実性コーン

ソフトウェアの見積には不確実性コーンと呼ばれる性質があります*1。下の図をみてください。

不確実性コーン
不確実性コーン
*2

プロジェクトの初期段階では、見積もりが1/4倍〜4倍になる不確実性があります。プロジェクトが進むに連れて見積と実際に掛かる工数(実績)が離れる不確実性が減っていきます。プロジェクト完了時には不確実性が0に収束します。 プロジェクトの初期段階では、細かい設計や実装方針などが決めておらず、決定した設計・実装方針によって必要な工数が上下します。 必要な機能があとから発見され工数が増えることもあります。 プロジェクトが進むうちに、新たに発見される情報は減り、いつしかプロジェクトは収束します。 そんな経験はないでしょうか?

タスクの見積りの不確実性

「ソフトウェア見積り」の中で「不確実性コーン」は、プロジェクト単位の話として挙げられています。 個人的に、「不確実性コーン」はタスクにも適用できると考えています。 あるタスクを進めた時、タスク着手前に考えていた必要な時間と、タスク完了までに実際にかかった時間は、たいてい一致しません。 実際にタスク着手してみて、思っていたより簡単に済むことや、考えてもいなかった問題を発見して予想以上に時間がかかることがあります。

また、作業を進めるにしたがって、タスクの完了に必要なことがだんだんわかっていき、不明な部分が減り、見積と実績の差が減っていく点も一致しています。

報告する進捗率はどうやって決めてる?

タスクに着手した初期段階では、見積りと実際に掛かる時間には大きな乖離があります。 仮に、マコネルの例にように1/4倍〜4倍の不確実性があるとしましょう。 作業者が「タスクを10%進めた」と思っている時、実際に掛かる時間の2.5%しか進んでいない可能性もあれば、40%進んでいる可能性もあります。

10%の時点で40%進んでいる可能性があるのであれば、40%と報告しても嘘ではありません。 プロジェクト管理者が10%と報告したとき不機嫌になり、40%と報告したときご機嫌になるのであれば、プロジェクト管理者を喜ばせたい作業者は40%と報告するするかもしれません。 あるいは、自分がプロジェクト管理者の機嫌に左右されていると感じる作業者は、影響を差っ引いて低目に2.5%と報告するかもしれません。

作業者の実感する進捗率と報告される進捗率の数値は同じとは限りません。

プロジェクト管理者の感情

プロジェクト管理者がポーカーフェイスを貫ければ、作業者の実感通り10%と報告してもらえるかもしれません。 予想より遅い進捗を報告されたプロジェクト管理者は、なんらかの対策を考える必要があるので、不機嫌にならないとしても困りはするでしょう。 おそらく困っていることは顔に出るでしょう。

プロジェクトのあらゆる場面で感情を隠しつづけるのは難しいです。 プロジェクトが順調な時はいいです。 プロジェクトが遅れ、遅れに対して上司やお客様から苦情をもらっているときに、作業者からもっと遅れる報告をもらったら、感情的にならないことは難しいでしょう。

プロジェクト管理者が望んでも望まなくても、作業者に善意があっても悪意があっても、作業者はプロジェクト管理者のなんらかの意図を読み取って、都合の良い進捗率を勝手に報告します。

f:id:ledsun:20190514170215p:plain
プロジェクト管理者の機嫌を伺って進捗報告する作業者

90%まで進行して完了しないタスク

プロジェクト管理あるあるに「永遠の進捗率90%」があります。 あるタスクの進捗率が順調に推移して90%まで進んで、そこから進まなくなる現象です。

「不確実性コーン」は、直接的にはタスクの初期段階で見積と実績の乖離を生みます。 しかし、本来幅があるはずの進捗率を一つの数値に落とし込んで報告する作業は、間接的に、タスクの末期になってもその威力を発揮します。

特に、作業者が、自身で問題を解決したいと考えている場合に起こります。 あるいは、プロジェクト管理者が、作業者だけで遅れを解消させようとする場合に起こります。

進捗率では進捗率はわからない

以上のように「報告するタスクの進捗率」は作業者の個人の価値観や、プロジェクト管理者と作業者の関係によって変ります。 「進捗率の報告」では、進捗率はわかりません。

作業者のトレーニングや、作業者とプロジェクト管理者の関係構築によって、「進捗率」の認識を合わせて行くこともできるでしょう。 人数が多いと修正する「作業者の振る舞い」と「作業者とプロジェクト管理者の関係」は増えます。

もっと簡単な方法があります。 プロジェクトの進捗を「完了したタスクの個数を数え」て測りましょう。 「完了したタスクの数」は、プロジェクト管理者と作業者の間の誤解が少ないです。 良くも悪くも報告内容を誤魔化せません。

どうやって始める?

進捗を「完了したタスクの個数を数え」で測るためにはタスクのサイズを小さくします。

進捗報告のタイミングで、進行中のタスクが「完了したタスク」と「未完了のタスク」の半々になるようにしましょう。 割合は重要でありません。 進捗報告の際に「完了したタスク」と「未完了のタスク」が混ざることが重要です。

具体的には、タスクのサイズを「次の進捗報告までに、理想的な見積で完了する」サイズより小さくしましょう。 毎日進捗報告するのであれば、1日より小さくします。 毎週であれば5日より小さくします。

実績が見積を下回ったタスクは「完了したタスク」になります。 実績が見積を上回ったタスクは「未完了のタスク」になります。 次の進捗報告では「完了したタスク」と「未完了のタスク」が混ざるはずです。

「ごまかしのないプロジェクトの進捗率」が、「進捗報告のたびに」得られます🎉

直近のタスクだけを小さく見積る

プロジェクトのすべてのタスクを1日単位のタスクに分割するのは大変です。 「完了したタスクの個数を数える」のに必要な小さなサイズに見積もるのは、直近のタスクだけで十分です。

見積はプロジェクト全体の「不確実性コーン」の影響を受けます。 プロジェクトの初期に全てのタスクを見積もっても実績と乖離する確率が高いです。 不確実性が高いうちに多くのタスクを細かく見積もっても、労多くして功少なしです。 プロジェクトが進んでから、細かく見積もれば、不確実性が低くなった段階で見積もれるので、見積の精度が上がります。

細かく見積もるのは、直近の進捗報告 1サイクルか2サイクル分のタスクにしましょう。 直近でない、大きな単位のタスクは、荒く見積もって、プロジェクトの不確実性に応じて2〜4倍のバッファを積みましょう。

タスクの分け方

タスクを小さいサイズに分けるときにはコツがあります。

大きなタスクには、不明な部分が多く入っています。 不明な部分を明確する作業をタスクとして切り出しましょう。

  • 仕様の不明点を顧客(またはプロダクトマネージャー)にインタビューして解消する
  • UXの不明点を解消するためにデモ画面を作る
  • 使用ライブラリの不明点をサンプルプログラムを作って解消する

不明点が少ない作業は、やることがわかっています。 分担しやすい部分で、2つなり3つなりのタスクに分ければよいでしょう。

f:id:ledsun:20190515163031p:plain
わからないことをわかるようにする作業をタスクにしよう

どこから始める?

個人プロジェクト

一人プロジェクトの場合、タスクの進捗率を意識することはないと思います。

例えば、卒論の進捗率をページ数で測ることはあっても、「実験結果の考察が30%進んだ」と管理することはないと思います。 それは30%と数値化しても、3.3倍の時間で完了するとは限らないからです。 タスクの進捗は「全然わかっていない」や「あともうちょっと」で把握していれば十分です。

「タスクの進捗率」を求めるのは、複数人の作業進捗を集計できるように正規化するためです。 「Aさんが40%進んでいてBさんが50%進んでいるので、全体としては平均して50%」と全体像を把握するためです。

個人プロジェクトに「タスクの進捗率」は必要ありません。

少人数プロジェクト

3〜5人程度のプロジェクトを考えてみましょう。 進捗を「完了したタスクの個数を数え」て測るのを、始めやすい規模です。

人間には、タスクを分けるのが上手い人と下手な人がいます。 上手い人には、必要なタスクのサイズを伝えれば、それで十分です。 下手な人は、下手なので、タスクを分ける手伝いをしてあげましょう。

タスクを分けるのが下手な人の多くは、タスクの中の「何がわからないのかわからない」ことが多いです。 地道に「わかっていること」と「わかっていないこと」を聞いて、わかっていないことを「不明な部分を明確する作業」としてタスクに切り出しましょう。 手間はかかります。

タスクの進捗率で報告させれば、タスクを分けるのが下手な人でも、とりあえず作業に着手して進捗報告ができます。 たとえば「設計資料のファイルを開いたから10%」、「hoge.cファイルを作ったから10%」。 「何がわからないのかわからない」人でも、わかることははあります。わかることの作業ができます。 作業さえすれば進捗率は増やせます。90%まで。

プロジェクト管理者としてみたら、作業者のスキルが多少不安だと思っていても、進捗率が増えている間は安心できます。 でも、なぜか90%まで進んだら止まります。タスクはなかなか完了しません。 90%ということはスケジュール上は後半のはずです、リカバリ策が取りにくい段階で、遅れていることが判明します。 結局困りますよね? 最初から、タスクを分ける作業を手伝っておけばよかったです😥

もっと悪くすると、その作業者は複数のタスクを担当しているかもしれません。 10個のタスクが90%まで進むんです。 プロジェクトの進捗率は90%です。 その実態は・・・怖いですね、恐ろしいですね。さよなら、さよなら👋

大規模プロジェクト

20人程度のプロジェクトを考えてみましょう。 プロジェクトの参加人数が多いと、タスク分割が下手な人も増えます。

プロジェクト管理者が、タスク分割が下手な人全員の「タスクを分ける作業」を手伝うのは不可能です。 頑張って「タスク分割できる人」を集めましょう。 少なくとも参加人数の1/4ぐらいタスク分割できる人を集めて、残りの人の「タスク分割のお手伝い」は「タスク分割できる人」達に見てもらいましょう。

「タスク分割のお手伝い」の工数は余計に掛かります。 全体の見積に、タスク分割の工数も入れておきましょう。 「プロジェクト管理工数」というやつです。

f:id:ledsun:20190515163636p:plain
タスク分割が得意な人にチームを率いてもらおう

*1: Cone of Uncertainty - Wikipedia によると「不確実性コーン」の概念は1958年の化学業界には既にあり、1981年にベームがソフトウエア産業に持ち込み、1997年にマコネルが「不確実性コーン」という呼び名にしたそうです

*2:この図はプロジェクトの本質とはなにか | 日経 xTECH(クロステック) から引用しました。もともとは「ソフトウェア見積り」(スティーブ・マコネル/日経BPソフトプレス)に掲載されている図です。