yigarashiのブログ

学んだことや考えていることを書きます

『DMM.comを支えるデータ駆動戦略』を読んだ

最近はエンジニアとして事業への貢献度を高めたくなっていて、同僚が本書をオススメしていたので読んでみました。チームや社内でデータ駆動の意思決定がどんどん盛り上がっていたこともあり、データを軸にしたアジャイル開発について、一度まとまった知見を得たいと思っていたのでした。実際に読んでみた感想としては、データ駆動アジャイル開発の大枠を掴むことができ、かつ実際の事業に基づいて各所がしっかり肉付けされているので、具体的なアクションとして実行しやすい知見も多くあり、とても良い本だったと思います。

ざっくり要約

本書の議論は、事業がブラックボックスであるために成長させる方法が分からないという課題から始まります。これまでは、多くの人が手探りでやっていくなかで、ごく稀に全てを見通すスーパースターが現れて事業を成長させていました。しかし、それだけでは目の前の事業が沈没してしまうので、どうにか凡人でも事業を伸ばせる再現性のある手法が必要になります。それこそが本書の言うデータ駆動戦略です。事業をKPIツリーによって表現し、KPIに対する仮説立案とその検証を繰り返すことで、ブラックボックス化された事業を徐々に予測可能なものにし、事業改善の精度を高めていきます。データ駆動戦略を進める上では、より高速に仮説検証のイテレーションすことが大事です。早く知見が増えれば、より早く施策を軌道修正することができます。そのために、A/Bテストによって失敗をコントロールする、アジャイルに進める、バリューストリームマッピングをはじめとしたツールでプロセスを検証・改善する、といったプラクティスが紹介されています。

さらに、仮説検証の結果による状況の変化や市場の変化に素早く適応したり、自発的にプロセスを改善していくためには、学習する組織ないし自己組織化された組織が重要であると述べられており、そうした組織を作るための方針として「戦略的Unlearn」が紹介されています。Unlearnとは、既存のやり方やメンタルモデルにゆらぎを加えて崩し、もう一度積み直すことで体系化するプロセスのことで、それを戦略的に起こすことで自己組織化を進めようというのです。世間で流行っている手法や隣のチームでうまく行っているプラクティスを取り入れることで、既存の手法を相対化したり、新しい手法をチームに合わせて改善して、より成長していくことができます。しかし、単に戦略的Unlearnといっても難しいので、戦略的Unlearnを進めやすい枠組みにまで議論は及び、その文脈でXPとスクラムが厚く解説されています。XPはある種のプラクティス集であるからゆらぎを生み出すのが容易であるとか、スクラムは自分たちのプロセスをふりかえるセレモニーがふんだんに用意されているので戦略的Unlearnしやすい、といったことが述べられています。

特に気付きのあった点

まず本書を読んで強く感じたのが、「本気で」データ駆動してるなということです。事業改善やプロジェクトを始めるなら、まずはKPIツリーを作るところから始める。KPIツリーに必要なデータはとにかく全部取る。データの重要な因果関係についてはA/Bテストで検証するといった具合です。そのなかでも特に、今ある機能を消すだけのA/Bテストをして、改善しようとしている機能が本当にコンバージョンと因果関係があるのか確かめるという事例に衝撃を受けました。A/Bテストといえば新機能の効果を検証するイメージが強かったのですが、確かにそこに限る必要は全くないです。企画を科学的なプロセスと捉えて、リスクの高い不確実性をデータによって淡々と排除しており、一歩先のパラダイムにいるなと感じました。

また、スクラムを運営しているエンジニアとして、データ駆動戦略とスクラムシナジーについて様々な側面から論じられている点も面白かったです。仮説検証を素早く進めるという観点では、職能横断なスクラムチームはリードタイム削減に有利な上に、スプリントごとにリリース可能なインクリメントを作成することを基本とするので、検証、つまりユーザーに触ってもらってデータを集めるまでにかかる時間を短縮できます。スプリントのたびに再計画が可能なので、仮説検証の結果を開発に反映するチャンスもすぐにやってきます。データ駆動もスクラムも不確実性に対処するための枠組みなので、そのあたりが本質的に相性が良いのかもしれません。

最後に、自分が関わるプロダクトでデータ駆動を進めるにはどうしたら良いかを考えてみると、本書ではデータ駆動を土台として議論を展開していますが、実際にはそれ以前にサービスのミッションやコアバリューが定まっていることが重要だと感じます。単に収益モデルをKPIツリーにして、収益が上がるなら何をしてもよいわけではないはずなんですよね。一時的に末端の数字は上がるが、実はユーザーの価値を損ねる施策というのはいくらでも考えられると思います。KPIツリーに収益とユーザー価値の両方が現れて、それが交わる様子が自然に表現されているのが理想だと思います。そして、サービス的にまずい施策を打つと、相関指標としてユーザー価値がちゃんと落ちると嬉しい。そうした「良い」KPIツリーを作るためには、言語化されたサービスの価値が大事だと思います。

とはいえ突然すべてが完璧になることはないので、まずは小さなところから、データ駆動の文化を作っていくぞという気持ちです。

『正しいものを正しくつくる』を読んだ

会社の人がおすすめしていた正しいものを正しくつくるを読みました。その読書感想文です。

本書のざっくりとした主張としては、仮説検証を繰り返すことによって「正しいもの」つまりユーザーにとって価値のあるプロダクトを探索し、それを「正しく」つまり不確実性をコントロールしやすいようにアジャイルに作りましょうということ。特に後者のアジャイルな開発についてはフレームワークとしてスクラムが厚く紹介されています。また、帯に「あるいはアジャイルのその先について」とあるように、素朴にアジャイル開発を適用した際の困難とその対応策が議論されています。本書の貢献は特にこの「アジャイルのその先」についての議論でしょう。その中で、自分の体験とも絡む特に印象に残った議論を少し掘り下げます。

問題の出発点は、スクラムにおけるプロダクトオーナーに機能が集中しすぎており、そこに期待を押し込めて分断してしまってはチームのパフォーマンスが頭打ちになるというところです。その状況を乗り越えるためには、開発チームがプロダクトオーナーの世界に「越境」していく必要がある、そして越境のカオスをコントロールするためにプロダクトに関する共通理解を育てましょう、共通理解を作るための手段として仮説検証を繰り返すことが有効だろう、というように話が進みます。このストーリーにはかなり同意します。自分の日々の暮らしの中で、うまくいっていないこと、うまくいったことが綺麗に言語化されて、良い本を読んだなという感触があります。

まずPOと開発チームの分断には心当たりがあります。各々がまずはスクラムにおけるロールを理解し、それを実践しようとするフェーズにあるとき、この状態に陥りやすいのではないかと感じました。誰が何をするべきかを指差し確認して理解しようとする過程で、過剰なセクショナリズムが生まれがちなのだろうと思います。

この問題意識に対する「越境」という方針に大変強く同意します。この本で僕が一番好きな部分です。短いエンジニア経験の中でうまくいったなと思うプロジェクトでは、確かにこの「越境」がありました。POならこう考えるだろうなというように、POの考え方を自然にトレースするようになり、プロダクトのコアに関わる判断や提案をスムーズに行うことができたと感じる期間があります。まさに共通理解によって自己組織化された状態です。スクラムを次のレベルに進めたいと思った時、「越境」という標語はかなり使えそうだなと思います。

最後の共通理解を育てることについては様々な方法があると感じました。そもそも何が課題かわからない、どう解決したら良いかわからない、つまり「正しいもの」の部分が未達の状態で共通理解を育てるには、確かに本書で述べられている仮説検証のプロセスを共に行うのが有力な手段だと思います。一方で、「正しいもの」がある程度固まっている場合は、別なコミュニケーションがありえるでしょう。自分が体験したのは、POがプロダクトの設計理念やユーザー行動を饒舌に話してくれるというケースです。デイリースクラムなどで仕様の質問や確認をすると、必ず「なぜ」を一緒に話してくれました。「ここのユーザーさんは〇〇な傾向が強いから是非こうしたい」「サービスとして〇〇が大事だからここは絶対外したくない」と言った具合です。こういう問答を数ヶ月繰り返すと、少しずつPOの考え方が頭に染み込んできます。そうすると、POが普段言っていることと違う仕様を発見して改善提案をしたり、どうしてもスコープを減らす必要がある時に筋よく落とすタスクを提案したり、といった高度な判断ができるようになっていきました。本書で述べられている「越境」はもっと積極的なPOの世界への関与を述べていそうなので、自分の体験は少し弱いかもしれませんが、段階的な越境の手段としては有用ではないかと思います。

また、本書では「目的」つまり「なぜそれをやるのか」の部分をとても大事にしており、その点も最近の自分の考えと一致しています。自己組織化の鍵は「良い目的が良いアクションを引き出す」だと個人的に考えており、本書でもそれに沿った主張がいくつか見られました。共通理解に基づく越境は最たるものです。プロダクトの目的を深く理解することによって、POの世界へ越境するアクションが自然と起こる、という体験をしていきたいし作っていきたいですね。本書で厚く議論されている仮説検証のプロセスと、それをつくる側と連携させる部分については、まだ自分ごとに落とし込めていないので、そこをもっと掘り下げて越境のための道具を増やしていけたら良いなと思っています。

GraphQL API を悪意あるクエリから守る手法

実サービスで GraphQL API をインターネットに公開する際は、悪意あるクエリに対する防衛が欠かせません。この記事における「悪意あるクエリ」とはサービスに意図的に負荷をかけるクエリのことです。GraphQL では 、木構造再帰的な構造を利用して、一回のクエリで容易に数百万・数千万件のデータを取得することができます。そのようなクエリを実行してしまうと、アプリケーションサーバーや、その後ろにいる別のサービスに甚大な負荷がかかります。これは攻撃者からしてみれば恰好の的で、なんらか対策を講じる必要があります。

幸いこうした問題はよく知られており、クエリを静的に解析するライブラリがいくつか存在します。しかし、そうしたライブラリをどう使うかといったことはあまり議論されておらず、効果的な対策を行うのは依然として難しい状況だと感じます。この記事では、典型的な負荷の高いクエリとその具体的な対策を紹介し、悪意あるクエリから GraphQL API を守る知見を展開したいと思います。

サービスに意図的に負荷をかけるクエリ

ここでは典型的な負荷の高いクエリを3種類紹介します。簡単のためデータは RDB から取得するということにしましょう(現実には別の API など様々なデータソースが想定されます)。

データを大量に取得するクエリ

GraphQL では、以下のようなクエリで簡単に大量のデータを取得することができます。firstSQL でいう limit で、取得する件数を指定する引数です。もしデータが十分に存在していたら、このクエリで 100 countries × 100 cities x 1000 streets = 1000万ノード 取得することができます。

{
  countries(first: 100) {
    name
    cities(first: 100) {
      name
      streets(first: 1000) {
        name
      }
    }
  }
}

重い計算をさせるクエリ

以下のクエリの superComplexStatistics は非常に重い統計値を計算するフィールドです。仮に1回の計算に1秒かかるとしましょう。すると、このクエリでは city を 10,000 件取得するので、superComplexStatistics を 10,000 回計算することになります。これだけで 10,000 秒(約3時間)かかります。

{
  countries(first: 100) {
    cities(first: 100) {
      superComplexStatistics
    }
  }
}

タイムアウトを設定することで多少は問題を回避できますが、タイムアウトに達するまで CPU を使い続けるようなクエリを何度も実行するのは避けたいものです。

大量にDBアクセスを発生させるクエリ

GraphQL ではデータ同士に関連がある場合は実体を結びつけるのが良い、つまり RDB 上で city が country の id なりを持っていなるなら、country の実体を引けるようにする方が良いとされています。この方針に従ってスキーマを設計すると、country --> cities[0] --> country --> cities[0] ... といったように無限に循環する構造を作ることができます。これを利用すると以下のようなクエリを書くことができます。

{
  countries(first: 1) {
    cities(first: 1) {
      country {
        cities(first: 1) {
        ...(これがあと996段続く)
        }
      }
    }
  }
}

基本的に GraphQL サーバーは、あるフィールドを resolve して、そのあと子フィールドを resolve するというように動きます。つまり素朴に実装するとネスト1段ごとに1回 DB アクセスが発生することになります。この例だと、データ量こそ大したことはないですが、ネストが 1000 段あるので DB アクセスが 1000 回発生することになります。アプリケーションレイヤーでのキャッシュなどを工夫すれば問題を回避できる場合もありそうですが、一般の場合に備えるのは難しいでしょう。

悪意あるクエリへの対策

GraphQL API がインターネットに公開されている限り、上述のようなクエリによって GraphQL サーバーや DB が負荷にさらされるリスクは常に存在します。こうしたクエリに対する基本的な対応方針は、クエリを実行前に解析し負荷の問題がない場合に制限して実行するというものです。この記事では「ホワイトリストによる制限」と「complexity による制限」の2つの対策を紹介します。

ホワイトリストによる制限

これは、GraphQL API が受け付けるクエリ一覧をホワイトリストとして持っておいて、クライアントから送られてきたクエリがそれに一致する場合のみ実行するやり方です。この手法のメリットとしては、導入が簡単であることが挙げられます。クエリが文字列として一致するかを検査するだけでよいため非常に簡単です。また、導入が簡単である割に非常に高い効果が期待できます。実際にクライアントで使うクエリのみを実行するため攻撃の余地がありません。一方で、デメリットもいくつかあります。ひとつは、ホワイトリストのメンテナンスコストです。クライアント側を更新するたびにホワイトリストを更新する必要があり、なんらか仕組みが必要になります。もうひとつのデメリットは、そもそも適用できる場面が限られることです。まず、公開 API ではユーザーが自由にクエリを書ける必要があるためホワイトリストは使えません。また、クライアントが複数あったり、クライアントの開発が別チームで行われていたりすると、ホワイトリストの運用は格段に難しくなります。次に紹介する complexity による制限のように、サーバーサイドで完結するほうが簡単である場合もあるでしょう。

complexity による制限

これは、クエリの負荷に比例して大きくなる数値(=complexity)を計算し、その数値が閾値に収まっている場合のみ実行するやり方です。任意のクエリに対して適用できるので、ホワイトリストによる制限が使いづらい場合は、まず complexity による制限を検討することになるでしょう。以下では典型的な負荷について complexity の例を紹介します。

まずはデータ量に関する complexity です。最も素朴なのはノード数を見積もることです。例えば以下のクエリではノード数は 50 countries + 50 × 100 cities = 5050 nodes となります。この値が一定以下の場合のみ実行することにすれば、異常なデータ量を処理してしまうことはなくなります。

{
  countries(first: 50) {
    name
    cities(first: 100) {
      name
    }
  }
}

ノード数を計算する場合、リスト(Relay の言葉では Connection)を取得するフィールドで、first のような件数が分かる引数が必須になっていることが重要です。こうした引数なしに100件固定で取得するフィールドなどが存在すると、そこを起点に大量にデータを取得できてしまう可能性があります。後述する cost ディレクティブなどを使えば対応は可能ですが、当然、クエリ解析の複雑さは増します。GitHub GraphQL API v4 では、まさにこうしたノード数による制限を行っており、かなり妥当な方針であると考えて良さそうです。

次は計算量に関する complexity です。典型的なやり方は、スキーマ側に directive を使って計算コストの情報を付加するというものです。例えば、以下のようにして計算量の多いフィールドに directive をつけることが考えられます。この場合は superComplexStatistics にコスト10を与えています。

type City {
  name
  superComplexStatistics @cost(n: 10)
}

GraphQL サーバーはこの情報をもとに complexity を計算します。例えば以下のようなクエリの計算量は (100 × 100 cities) × 10 cost = 100,000 と計算できます。

{
  countries(first: 100) {
    cities(first: 100) {
      superComplexStatistics
    }
  }
}

このディレクティブを使った制限は用途が広く、こうした計算量だけでなく、DBアクセス回数や、first などの引数を取らない全件取得フィールドの負荷を表現するのにも使えます。

それ以外の complexity として、クエリの文字列長やネストの深さがよく話題に上りますが、一般には負荷に比例しない complexity で、あまり筋の良い方針ではないと考えます。自分のスキーマを眺めてみて、文字列長や深さによって悪意あるクエリを制限できているかをよく検討する必要があるでしょう。スキーマによってはこうした簡単な指標で十分な場合もあり、選択肢としてないわけではありません。

実装と課題

complexity による制限を行うための実装はいくつか存在します。例えば JavaScriptgraphql-cost-analysis や Go の gqlgen が備える complexity limit があります。ライブラリが存在しない言語でも、クエリ解析のみをマイクロサービスとして切り出して、サポートの厚い言語で実装するといったことが考えられます。

しかし、そうしたライブラリのサポートがあってもなお、complexity による制限には多くの課題があります。ひとつは、complexity 設計のベストプラクティスが十分に蓄積されていないことです。本記事ではノード数・計算量といった典型的な負荷にフォーカスして対策を紹介しましたが、そもそもこのように掘り下げて議論している資料からして多くありません。自分で一から悪意あるクエリの対策を検討してみると、十中八九、無数の細かいデザインチョイスに苦しむことになるでしょう。また、本記事ではノード数・計算量・DBアクセス回数といった複数の complexity を紹介しましたが、それらを併用する風潮はあまりなく、complexity がひとつの場合を厚くサポートする実装が多いのが現状です。そうなると、必然的にひとつの complexity の中に複数の尺度を押し込むことになり、コストや閾値の設定が格段に難しくなります。DBアクセス1回はノード数いくつ分にあたるでしょうか。ある重い計算はDBアクセス何回分でしょうか。それらが複合したときの適切な閾値はいくつでしょうか。今のところ、こうした値を実測値などに基づいて無理やりこじつけていく必要があります。graphql-cost-analysis は複数の complexity による制限が可能な設計にはなっており、より厚いサポートが期待されます。

まとめ

この記事では、攻撃として利用できる典型的な負荷の高い GraphQL クエリを挙げ、そうしたクエリを判別するための静的解析の手法を2つ紹介しました。ひとつはホワイトリストによる制限です。単にクエリ文字列の一致を見るだけで良く、効果も高いですが、使える場面が限られます。もうひとつの手法は complexity による制限です。任意のクエリに適用可能ですが、ベストプラクティスが確立されていない面があり、効果的な対策を行う難易度は高いです。今後のライブラリの発展が期待されます。

最後に補足として、本記事で紹介したような対策は必ずしも完璧に行う必要はありません。そこまで高い安全性が必要でなければ、特に危険なノード数に限って complexity による制限を行うといったことも考えられます。そもそも対策を全く行わず、悪意あるクエリを送ってくるクライアントを ban するだけで事足りるかもしれません。最も重要なのは、リスクと対策の全体像を把握した上で要件に合わせた手法を採用することです。

エンジニア8人チームで"効果的に"タスクをアサインするために検討した8つの軸

最近、締め切りのある大きめなプロジェクトでWebアプリケーションエンジニア兼プロジェクトマネージャーとして仕事をしました。一年目なので当然プロジェクト管理の経験はなく、本を読んで知識を得たり、チームメンバーに助けられたりと、だいぶ手探りでの挑戦となりました。その中でもっとも難しかった仕事の一つとして、タスクの効果的なアサイがあります。

エンジニアは最大8人おり、その技術力、ドメイン知識、勤務地などは多岐に渡ります。ウォーターフォール的な開発だったため、タスクは事前に洗い出されており、タスク管理ツール上に無数に登録されていました。適当に人とタスクを辞書順でソートしてアサインするだけなら簡単ですが、現実はそうもいきません。締め切りは厳しく、チームの生産性を少しでも高く維持しなければいけません。メンバーのモチベーションが下がったり、依存の多いタスクが遅れたりといったことはなんとしても避けたいものです。この難しい課題を乗り越えるために、私はタスクと人の両面をよく理解することから始め、その知識に基づいて、パズルを解くようにしてアサインを決めました。本記事では、その際に検討した8つの軸をまとめます。

8つの軸は「タスク優先度」と「人」の2側面に大別されます。まずは「タスク優先度」から見ていきましょう。

タスク優先度に関する2つの軸

タスクを消化する順番は非常に重要です。これを間違えると、作業が止まってしまったり、あとから複雑な要件が明らかになったり、プロジェクトの安定した進行を脅かします。そうした問題を回避するために、今回のケースでは大きく分けて「依存関係」「不確実性」の2つを検討しました。これ以外にもプロダクトオーナーの要請などで優先度が変化することはありますが、支配的な要因ではありませんでした。

依存関係

Webアプリケーションの機能には、当然ながら依存関係があります。Aという機能を作ってからではないとBという機能を作りづらい、といった事情が無数にあります。これによって、先に着手しておかないと困るタスク群というのが現れます。これを、担当者がなるべく依存関係を気にしないで済むように‥‥最低でも依存関係によって作業が止まることのないようにできれば、チームの生産性は上がるはずです。

「精査して気をつける」以上の取り組みとしては以下のようなものがありました。

  • もしどうしても依存のあるタスクを並行して進める必要がある時は、両方の担当者に対して情報を共有し、お互い連携しつつ進めてもらうように依頼する
  • チームメンバーの「依存を解決できる最小のタスクを作って進めると良いのでは」という提案に基づいてタスクの分割の仕方を変えた

また、今回のプロジェクトでは、機能ができないとデザイナーが稼働しづらいという依存関係もあり、デザイナーの稼働率を上げるために画面に関連するタスクを優先的に消化する必要もありました。

不確実性

要件や実装方法が明らかではないタスクは、実装にかかる期間を見積もりづらくリスクが高い状態で、早めに着手するのが望ましいとされています。私が今回携わったプロジェクトでは、既存システムをそのまま利用できない「新機能」の枠がこれにあたりました。幸い、チームのベテランエンジニアが、割と早い段階で「新機能は危ない」と声を上げてくれており、事前にタスクにタグ付けをしていたため、容易に一覧して検討材料とすることができました。不確実性の高いタスクの注意点として、期間だけではなく、難易度も不確実であるということがあります。調査をしてみたら異常に難しい、よくある解法を知らないと難しいといった可能性があり、誰をアサインするかは注意深く検討しました。

人に関する6つの軸

個人の特性に合わせてタスクをアサインすることは非常に重要だと感じます。全員に興味と能力にマッチしたタスクをアサインできれば、チームの生産性は間違いなく向上するでしょう。モチベーションの向上に伴ってチームの雰囲気も上向くでしょう。全員のタスク完了ペースも揃いやすいので、ベロシティを安定して計測できるといったことも考えられます。こうしたメリットを狙って、人に関する軸として「技術力」「ドメイン知識」「興味・得意分野」「成長機会」「コミュニケーション」「自分」の6つの軸を検討しました。

技術力

これは言うまでもなく重要な軸です。チームメンバーは全員優秀で、誰にどのタスクをお願いしたとしても、どうにかして完遂してくるだろうという信頼はありました。しかし、締め切りに追われる状況下では「安定して」「筋よく」各タスクが完了するのが望ましいです。止むを得ない側面もあり、技術的に難しいタスクは、経験のある技術力の高いメンバーにアサインするというのを基本方針としました。しかし、これには若手の成長機会が失われるといったデメリットもあり、後述する他の軸も検討してバランスを取るように心がけました。

ドメイン知識

これは「技術力」と同じ、エンジニアのシンプルなパワーに関する軸で、やはり重要です。今回のプロジェクトでは、既存システムの改修やつなぎ込みが必要な部分があり、その既存システムを理解していないと難しいタスクがいくつかありました。そういったタスクを社歴の浅い人にアサインするのは、スピード重視の状況下ではあまり筋が良いとは言えません(一般にはドメイン知識を展開するメリットを考慮するべきでしょう)。過去のプロジェクトの話を少し聞いてみたりして、よりドメイン知識がマッチする人にタスクをアサインできないか検討しました。

興味・得意分野

締め切りに追われる日々だとしても、毎日の仕事が楽しいに越したことはありません。一人一人がやりがいを感じて働けることは、他の人にも良い影響を与えるでしょう。そうした観点から、興味や得意分野を把握できている人には、なるべくそれに近いタスクをアサインするように心がけました。とはいえ、急ごしらえの大きなチームでメンバーの嗜好を把握しきれなかったり、他の軸を優先せざるを得ない場面も多かったりと、なかなか上手くハマる場面は少なかったという印象です。次に大きなプロジェクトをやるときに、重点的に考慮したい軸の一つです。

成長機会

この軸については、id:t_wada さんの こちら の発表が大変印象に残っています。

では、品質と速度についてのトレードオフが意識されるとき、実際には何と何が秤にかけられているのか。

(中略)プロダクトの品質を支えるために必要なメンバーの成長とその成長のために必要なフィードバックや学習の時間が秤にかけられているのではないかと思う。

タスクのアサインを検討する中で、チームの生産性を向上させるための最も素朴な最適化は、難しいタスクをできる人にアサインすることでした。しかし、それを続けていては知識は展開されませんし、時間があればアサインできていたはずの若手の成長機会が失われます。今回のプロジェクトでは、興味・得意分野が上手くハマる場合に少し挑戦的にアサインをしてみたり、一時的にベテランと若手の2人をアサインしてペアプロをお願いするなどして、ささやかな抵抗を試みました。これはまさに「抵抗」という言葉がぴったりで、締め切りによって失われたエンジニアとしての楽しみを、少しでもチームに取り戻すという気持ちで検討しました。

コミュニケーション

誰をアサインするかによって、タスクを進める上でのコミュニケーションの難易度は変化すると考えます。例えば、社歴の浅いメンバーにとって、チーム外調整が必要なタスクは相対的に難易度が上がります。上で述べたペアでのアサインでも、勤務地が違う2人をアサインするより、同じ勤務地でペアプロをしやすい2人をアサインした方が簡単になります。調整が可能な場合は、こうした細かい最適化も検討しました。

自分

これは、いわゆるプレイングマネージャーとして取り組んでいる場合に特有の検討事項でした。自分は、いちエンジニアとして作戦を考えて、時には他のエンジニアに厄介な仕事をお願いする立場です。しかも、やろうと思えば自分に一番面白いタスクをアサインすることすらできてしまいます。そういう立場で、いかにチームメンバーからフォロワーシップを引き出せるかが重要だと感じていました。そのために、自分がラクをしないことを心がけました。厄介な新機能や、チーム外との調整が必要になるタスク、クリティカルパスに関わる割り込みを積極的に取って、1人のプレイヤーとして、締め切りに対して誠実に行動していることを示せるように心がけました。幸いチームメンバーは優しい人ばかりだったので、実際のところフォロワーシップの心配はありませんでした。しかし、そこにあぐらをかくわけにはいきません。これはプライドの問題です。

まとめ

以上、効果的なアサインを考えるために検討した8つの軸を紹介しました。どの軸を重視するかといった問題も非常に難しく、プロジェクトの状況に応じて柔軟に検討するようにしました。プロジェクト自体はなんとか成功しましたが、アサインミスで特定の人の負荷が上がってしまったり、そのしわ寄せによってチームの他のセクションが不安定になってしまったりと、色々と反省ポイントはありました。また、今回の議論は、場当たり的に必要なものを挙げていった結果生まれたもので、先人の議論を十分には参考にできていません。また、全ての軸を効率よく検討する手法も確立できていません。より再利用性の高いフレームワークとなるように、今後も改善を続けていきたいと思います。

コラム: タスクをアサインすることの是非

一般的なアジャイル開発においては、スプリントプランニングで人にタスクをアサイしないのが良いとされています。タスクの属人性を排除することで、チームとして仕事を進められるようになり、結果として助け合いなどが起こり安定した生産性を発揮できるようになるという考え方です。しかし、今回はプロジェクトがあまりにも難しく複雑でした。アサインをすべて決めることで、属人性すら最適化の道具として利用し、チームの生産性をシビアに議論する必要がありました。それがチームにとって最良の状態ではないという意識は常にあり、プロジェクトの不確実性が減少するにつれて、少しずつチームメンバーにタスクを選んでもらう形式も取り入れました。プロジェクトの性質やチームの状況に合わせて、スプリントプランニングの方法を検討できると良いのでは、というのが所感です。

見積もりの基礎知識と「ストーリーポイント vs 理想日」の考察

昨年の10月頃から締め切りのある大きなプロジェクトに参加し、部分的にではありますがプロジェクトマネジメントを担当しました。この記事では、その業務を通して得た見積もりに関する知見をまとめます。教科書的な知識は「アジャイルな見積もりと計画づくり」に依りますので、詳しくはそちらをご参照ください。

見積もりの基礎知識

見積もりは、提供したい機能をどれくらいの期間で実装できるかを予想し、計画について議論をするために行います。見積もりを行うことによって、全くアタリがつかない状態から、「プロジェクトのフェーズがこの辺りなので、平均で N 週間、最悪で 2N 週間くらいかかる可能性があります」などと答えられるようになります。さらに、プロジェクトのフェーズが進んで不確実性が減少すれば「平均で M 週間、最悪で M+1 週間くらいかかります」と答えを正確にできるかもしれません。まさにこれが「アジャイルな計画づくり」です。

見積もりのベストプラクティスは「期間ではなく規模を見積もること」であると言われています。ある機能があったときに、直接「2週間くらいかかります」と答えるかわりに、機能の大きさを表す数字(ポイント)を答えるのです(例えば「5くらいです」と)。この数字は相対的な値です。どういうことか理解するために、試しに4つほどタスクを見積もってみましょう。世界で最初の機能A は、比較対象がないので、ひとまず「2」という適当な値を与えておきます。次の機能B は、Aと似ていますが、Aに比べると倍ぐらいの画面が必要そうです。よって倍の「4」を与えます。次の機能Cは、Aよりは大変そうですが、Bよりは簡単そうです。間の数字である「3」を与えましょう。最後の機能Dは、Aと同じような機能です。Aと同じ「2」を与えましょう。‥‥このくらいやれば、お分かりいただけたのではないでしょうか。ある機能の見積もりは、過去に見積もった機能と比較した相対的な大きさによって決めるのです。

このように規模の見積もりを行った状態で1ヶ月ほど開発を進めれば、チームが単位期間(例えば1週間)あたりに完了できるポイント数を予想できます。これをベロシティと呼びます。あとは簡単で、提供したい機能の総ポイント数をベロシティで割ると、プロジェクトの完了にどのくらいの期間が必要か予想することができます。規模の見積もりを期間の見積もりに変換することができました。

なぜこんな回りくどいことをするのでしょうか。いくつか理由はあります。ひとつは、期間の見積もりをベロシティという実績値から導出することで、見積もりのミスが自動的に補正されるメリットがあります。実際にかかる期間がわからなくても、機能の相対的な規模さえ正しく認識できていれば、自動的に信頼できる期間の見積もりを得られるのが優れたポイントです。もうひとつ理由をあげるなら「期間の見積もりは人によって違う」からです。「〇〇さんがやるとしたら1週間ですね」「□□さんがやるなら2週間ですね」といった意見が乱立してしまっては、話をまとめることができません。仮にある人にアサインする想定で期間の見積もりを与えたとして、実際に着手するときにその人の手が空いているとも限りません。その度に見積もりが更新され、全体のスケジュールも目まぐるしく前後するようでは、経営陣や顧客からの信頼も損なうでしょう。規模の見積もりは、そうした人による差を吸収し、機能の相対的な大きさとチームとしての生産力にフォーカスした議論を可能にします。

ストーリーポイント vs 理想日

規模の見積もりに使う代表的な単位として、ストーリーポイントと理想日というものがあります。ストーリーポイントは、まさに前の節で「2」とか「4」とか言っていた、機能の規模を表す相対的な値のことです。「ストーリー」は「ユーザーストーリー」から来ています。一方、理想日は「その機能の開発だけに取り組んだとしてかかる日数」のことです。純粋な規模の見積もりではありませんが、説明・導入のしやすさから使われることがあります。

アジャイルな見積もりと計画」8章によれば、理想日には次のようなデメリットがあるとされています。

  1. 議論が職能別の詳細に入り込んでしまいチームとしての働き方を阻害する
  2. メンバーの能力が向上するにつれて見積もりが変化し、ベロシティが信頼できなくなる
  3. 理想日を現実時間と比べてしまって規模の見積もりができない
  4. 具体的な作業による見積もりになり、見積もり自体に時間がかかる傾向がある
  5. 人によって理想日は違う

逆にいえば、ストーリーポイントは以上の問題を解決します。理論の上ではストーリーポイントのほうが望ましいのは明らかでしょう。そのようにする具体的なメリットがあります。

しかし、現実問題として、チームメンバー全員にストーリーポイントという概念を導入するにはかなりの慣れやリーダーシップが必要とされるように感じます。単に説明するだけなら理想日のほうが格段に簡単です。そこで私が提起したいのは、仮に理想日のデメリットを低コストでコントロールできたとしたら、そちらのほうが良い場合もあるのではないか、ということです。実際、私が参加したプロジェクトでは見積もりの単位として理想日を用いましたが、上で述べたようなデメリットを感じることはなく、スケジュールを議論する材料としてプロジェクトの終盤まで活躍してくれました。以下では、理想日が機能した事例を掘り下げることで、見積もりと計画に関する考察を深めようと思います。

理想日が機能した事例

私がプロジェクトマネジメントを担当したのは、とあるプロジェクトのWebアプリケーション開発セクションです。プロジェクトのために一気に人が集められ、エンジニアはそのセクションだけで最大8人稼働、しかも社内で採用事例の少ないモダンな技術スタックで開発を進める、というかなり挑戦的なセッティングでした。プロジェクトの序盤に、全タスクの洗い出しと見積もりを行うことになり、その際、見積もりの値として理想日を用いることにしました。ストーリーポイントを使うかどうかを議論しましたが、チームへの導入のしやすさから、理想日を導入することになりました。すぐにエンジニアを集めて「このタスクだけをするとしたら何日かかるか、フィボナッチ数列で出してください」というふうに見積もり会を行いました。この会はスムーズに進行し、結局プロジェクト終盤まで同じ見積もりの仕組みが機能しました。

ここからは理想日見積もりのデメリットをひとつずつ見ていき、いかにしてそれをかいくぐったかを紹介します。

  1. 議論が職能別の詳細に入り込んでしまいチームとしての働き方を阻害する
    • これはそもそも、プログラマ、DBアーキテクト、テスター、デバッガーなどといったようにロールが細分化されていることを前提とした議論で、今回のチームにはあてはまりませんでした。全員がDBからフロントエンドまで一気通貫で作業する前提で議論をしたので、問題になりませんでした
  2. メンバーの能力が向上するにつれて見積もりが変化し、ベロシティが信頼できなくなる
    • これを感じる場面は、わずかですがありました。しかし、以下の2点によって最後まで安定したベロシティを得ることができたと感じます
      • 大部分の見積もりはプロジェクト序盤の見積もり会で一気に終わらせた
      • その後の追加タスクについては、理想日が相対ポイントであることを知っているメンバーが中心になって見積もりを行っていた。実際「このタスクは前に見積もった〇〇とほとんど同じなので2理想日は少ないと思います」といったように軌道修正をする場面があった
  3. 理想日を現実時間と比べてしまって規模の見積もりができない
    • これは上で述べたとおり、理想日が相対ポイントであることを知っているメンバーが軌道修正することで、かなり終盤まで秩序を保つことができました
  4. 具体的な作業による見積もりになり時間がかかる傾向がある
    • これについては、実際に具体的な作業に関する議論になることがあり、時間がかかったのは事実だと思います。しかし、プロジェクトのフェーズ的に、全員が具体的な作業をイメージできるくらいタスクを掘り下げて知識を展開することにもメリットはあり、始めからある程度は具体的な議論をするつもりでした。理想日だから時間がかかった、そしてそれが悪かった、という印象は全くありませんでした
  5. 人によって理想日は違う
    • これは、以下のような要因によって問題にならなかったと感じています。
      • 最初の見積もり会にエンジニアが10人近く参加しており平均化が容易であった
      • 社内で例の少ない技術スタックを採用したこと、また新規参加のエンジニアが多かったことで、スタート時点の能力のバラツキが抑えられていた

以上をまとめると、理想日を相対ポイントとして運用する明確な力が働いていたこと、期間に対する認識を平均化しやすい状態で大部分の見積もりを行ったこと、この2点が、理想日を機能させた大きな要因であったと思います。特に前者については、見積もりのベストプラクティスを理解していたからこそできたことで、仮にストーリーポイントを採用しないにしても、知識として知っておく価値のある概念だと感じます。

どちらを採用するべきか

今回の事例から考えると、最初から全ての要件が明らかになっているウォータフォール的な開発や、チームメンバーの能力が平均化される大規模な開発では、理想日のデメリットが少なく、ストーリーポイントを頑張って導入するよりも理想日を運用でコントロールする方が簡単というケースがあるかもしれません。一方で、技術力に差がある少人数チームや、アジャイルな開発での理想日見積もりは難しそうです。3人で見積もりをしたとして、あるタスクについて「1日」「3日」「5日」というようにバラけ続けたり、時期によって感覚が変わっていくのをまとめるのは大変そうです。ストーリーポイントのような、より相対感のある指標を使うことによって、議論を「見積もり済みのタスクと比べてどうか」という方向に導けるとしたら、そちらを導入するメリットはあるように感じます。どちらにせよ、最も重要なのは「期間ではなく規模を見積もる」ことで、それを簡単に実現できる仕組みを、チームの状況や自分のスキルにあわせて設計していくことが良い計画を作る近道なのではないかというのが、この半年の経験に基づく自分なりの結論です。