メインコンテンツへスキップ

概要

タスク分解とは、実装の前に機能を小さな作業単位へ分割する実践です。各タスクは、単体でマージでき、単体でrevertでき、無関係な変更を巻き込まないことを目指します。コードを書く前の準備がプルリクエストの粒度を決めます。分解したタスクを起点にプルリクエストを作ることで、適切な粒度を維持したままプルリクエストを作り続けられます。

メリット

着手前にタスクを洗い出して分割するのは遠回りに見えますが、開発サイクル全体で見れば確実な近道になります。

見積もりの精度が上がる

大きな機能を丸ごと見積もるより、小さく具体的なタスクの積み上げの方が誤差が小さくなります。

対応方針を早く合意できる

タスクリストをIssueに書き、実装前にレビューしてもらうことで、考慮漏れや誤った前提を修正が安いうちに発見でき、手戻りが減ります。

プルリクエストの粒度が保たれる

分解したタスクを起点にプルリクエストを作れば、1つのプルリクエストが1つのことに注力した状態を保てます。

進捗が見え、引き継ぎが容易

タスクのチェックリストは状況を一目で読み取れるようにし、人から人へ、あるいは人からAIエージェントへの引き継ぎを容易にします。
適切なタスク分解は、AIエージェントへ適切なコンテキストを提供することにも繋がります。コンテキストエンジニアリングとは、AIエージェントが作業に使うコンテキスト(指示・参照情報・作業範囲)を、目的に対して過不足なく整える実践です。変更内容が一意なタスクは、その作業に必要な情報だけをコンテキストに載せられるため、エージェントは限られたコンテキストを1つのことに集中できます。 タスク分解・プルリクエストの粒度・コンテキストエンジニアリングは密接な関係にあります。タスク分解が作業の単位を決め、その単位を起点にプルリクエストの粒度が決まり、その粒度がエージェントに渡すコンテキストの質を決めます。実装をAIエージェントに任せる場合も、出発点は良いタスク分解です。

良いタスクの条件

適切なサイズのタスクは、次の4条件を同時に満たします。

壊さずにマージできる

その変更を単体で安全に統合でき、マージしてもアプリケーションは正常に動作します。

テストを含んでいる

実装と、それを裏づけるテストは同じタスクで出荷し、別タスクには分けません。

単独でrevertできる

巻き戻しても他のタスクに影響しません。

一度に読み切れる

レビュアーが中断せずに読み通し、全体を理解できる分量に収まっています。

まとめるか分けるか

境界を決めるのは凝集度です。二つの作業が同じタスクに属すかは、次の3つのチェックを順に当てて判断します。いずれかの答えがまとめるよう促すなら、まとめます。
1

独立性を確認する

タスクAなしでタスクBは正しく動くか。動かないなら同じタスクにします。
2

完結性を確認する

タスクAは単体で意味のある、マージ可能な単位か。そうでないなら、意味を持たせるタスクへまとめます。
3

revert影響を確認する

タスクAをrevertするとタスクBも壊れるか。壊れるなら同じタスクにします。
チェックを当てるまでもなく、常に同じ側に落ちる組み合わせもあります。

常にまとめる

  • 実装とそのテスト
  • APIエンドポイントとルーティング
  • データモデルとマイグレーション

常に分ける

  • リファクタリングと新機能
  • ライブラリ更新と機能開発
  • パフォーマンス改善と機能開発
  • データマイグレーションと機能開発
  • Feature Flagの各段階(追加 → 有効化 → 削除)
  • 互いに依存しない複数の機能

分解の手順

分解は反復的です。最初のリストが完璧である必要はなく、多くの人が見るほど、分解が進むにつれて精度は上がります。 ここでは「ユーザーデータの一覧を返すREST APIを追加する」タスクを例に、タスクリストを作成する実例を紹介します。
1

要件からタスクリストのたたき台を作る

ざっくりした要件を、Issueのタスクリストとして書き出すところから始めます。この時点では大枠の1行で構いません。
2

単体で出荷できるスライスに展開する

大枠の1行を、単体で出荷できる単位に分解します。一覧取得・絞り込み・ページネーション・ソートが、それぞれ独立したタスクになりました。すべてを一度に実装するのではなく、小さな追加を積み重ねて機能を組み上げます。
3

職種をまたいでリストをレビューする

コードを書く前に、フロントエンド・バックエンド・プロダクトのレビュアーにリストを見てもらいます。議論を通じて曖昧な項目が具体化されます。この例では、レビューでの認識合わせを経て、nameの絞り込みが「nameの部分一致」に確定しました。
4

実装中も分割し続ける

実装が現実を明らかにしたら、リストに反映してさらに分割します。この例では、テストを書く中で退会ユーザーの扱いという隠れた要件が見つかり、一覧取得のタスクが「全件返す」と「退会ユーザーは一覧から除外する」の2つに分かれました。1つのタスクを肥大化させず、リストの更新と分割を実装中も続けます。

依存関係と並列化

リストが固まったら、タスク間の依存関係を整理し、並列に進められるタスクを特定します。互いに依存しないタスクは、別々の人が、あるいは複数のAIエージェントが同時に実装できます。 たとえばユーザーの一括登録機能は5つのタスクに分かれます。ドメインモデルが最初にあり、パーサーとバリデーターはそれだけに依存するため並列に進められ、サービス層が両者を組み合わせ、APIエンドポイントが最上位に載ります。
このタスクリストの依存関係を図にすると、次のようになります。

タスクのIssue化

分解と依存関係の整理が終わったら、タスクごとにIssueを作ります。タスクリストのチェックボックスのままでは、担当者・議論・進捗をタスク単位で追跡できません。着手するタスクを独立したIssueに昇格させることで、1つのIssueが1つの作業単位になり、そのタスクに関する要件と議論が1箇所に集まります。 機能全体とタスクは、親子のIssueとして構成します。
  • 親Issue — 機能全体を表します。背景・目的と、分解したタスクリストの全体像を残し、各子Issueへのリンクで進捗を追えるようにします。
  • 子Issue — 分解した各タスクを表します。そのタスクの要件と完了条件を書き、依存関係と並列化で整理した依存を明記します。親子関係を紐づける機能がツールにあれば、子の進捗を親から集約して追跡できます。
GitHubの場合、この構成はSub-issuesと依存関係の2つの機能で表現できます。Sub-issuesが「どの機能に属するタスクか」という親子の関係を、依存関係が「どのタスクの後に着手するか」という着手順の関係を受け持ちます。 Sub-issues(親子関係) — 親Issueに子IssueをSub-issueとして追加すると、機能とタスクの階層が明示され、子Issueの完了状況が親Issueに進捗として集約されます。操作方法はGitHub Docsのサブイシューの追加を参照してください。一括登録機能では、親Issueの下に5つの子Issueがぶら下がる階層になります。

親Issue: ユーザーの一括登録機能を追加する

Sub-issues(0 / 5)
  • Task 1: ドメインモデルを定義する
  • Task 2: パーサーを実装する
  • Task 3: バリデーターを実装する
  • Task 4: サービス層を実装する
  • Task 5: APIエンドポイントを追加する
依存関係(着手順) — 子Issue同士の着手順は、Issueの依存関係として記録します。Task 2のIssueにTask 1への依存関係を設定すると、Task 1が完了するまでTask 2に着手できないことが一覧上でも分かります。本文に依存を書く代わりに使えます。操作方法はGitHub DocsのIssue依存関係の作成を参照してください。一括登録機能の依存関係は次の図のとおりで、矢印は依存先のIssue(先に完了すべきIssue)を指します。 依存関係を持たないTask 1から着手し、同じ依存先を持つTask 2とTask 3は並列に進められます。 こうして作ったIssueは、人にもAIエージェントにもそのまま渡せる作業単位になります。要件・完了条件・依存が1箇所にまとまっているため、実装者が必要とするコンテキストの質はこの時点でほぼ決まります。依存のないIssueから、並列に着手できます。