チーム開発の作業分担で失敗しないコツ—「機能単位」で分ける理由
python 16 min read

チーム開発の作業分担で失敗しないコツ—「機能単位」で分ける理由

avatar-m-1

karrinn

著者

「Aさんがフロントエンド、Bさんがバックエンド」と分けたはずなのに、なぜかいつも待ち合わせが発生する。マージしようとしたらコンフリクトだらけ—そんな経験はありませんか?
この記事では、複数人でのソフトウェア開発において、どう作業を分担すればスムーズに進められるのか、その考え方とコツをお伝えします。

KEY TAKEAWAYS

この記事でわかること

  • 「水平分割」より「垂直分割」が並行開発に適している理由
  • コンフリクトを防ぐディレクトリ構造の設計方法
  • Conway's Law や Team Topologies など、分担設計の理論的背景

まず知っておきたい用語

TERM 01

水平分割(Horizontal Slicing)

アーキテクチャのレイヤーごとに担当を分ける方法。「フロントエンド担当」「バックエンド担当」「DB担当」のように分けるやり方です。

TERM 02

垂直分割(Vertical Slicing)

機能・ドメイン単位で担当を分ける方法。「ユーザー機能担当」「商品機能担当」のように、一人(または一チーム)がその機能のフロントからバックまで一貫して担当します。

なぜ「フロントとバックで分ける」はうまくいきにくいのか

一見すると合理的に見える水平分割ですが、実際の開発現場ではいくつかの問題を引き起こしがちです。

待ち合わせが常に発生する

フロントエンド担当がフォームを作っても、バックエンド担当がAPIを完成させるまでテストできません。バックエンド担当が別の作業で忙しければ、フロントエンド担当は待つことになります。モックで先に進めたとしても、実際のAPIレスポンス形式が想定と違っていて手戻り…という展開は珍しくありません。

horizontal-split-bottleneck
mismatched-api-assumptions

認識のズレが生まれやすい

フロントエンドの視点とバックエンドの視点では、同じ機能でも見え方が異なります。「こういうレスポンスが返ってくるはず」という前提が食い違っていることに、結合テストの段階で気づく—このパターンは思った以上に多いものです。

垂直分割なら「待ち」がなくなる

機能単位で担当を分ける垂直分割では、一人(または一チーム)が機能の全レイヤーを担当します。

水平分割の場合

ユーザープロフィール編集機能を作る例

  • Aさん(フロント): フォーム作った、API待ち…
  • Bさん(バック): まだ別機能やってる…
  • Aさん: モックで進めるか…
  • Bさん: API完成!
  • Aさん: レスポンス形式が違う、手戻り…

垂直分割の場合

同じ機能をAさんが一人で担当

  • 1. APIのI/F設計
  • 2. バックエンド実装
  • 3. フロントエンド実装
  • 4. 結合確認 → 完結
  • 待ちがなく、判断も早い

Source: monday.com - What is a Vertical Slice?

「フロント専門」「バック専門」のメンバーがいる場合は?

専門性を持つメンバーがいる場合でも、機能単位でペアやチームを組むと効果的です。「決済機能チーム」としてフロント1人 + バック1人でペアを組めば、その機能内での待ち時間を最小化できます。または、OpenAPIなどでAPIの契約(インターフェース)を先に決めておけば、フロントとバックが並行して実装を進められます。

物理的な境界がコンフリクトを防ぐ

どれだけ分担を決めても、同じファイルを複数人が触ればマージ時にコンフリクトが起きます。これを防ぐには、ディレクトリ構造レベルで境界を設けることが有効です。

分担しやすい構造の例

機能(feature)ごとにディレクトリを分けることで、担当者が触るファイルの範囲が物理的に分離されます。これにより、コンフリクトのリスクを大幅に下げられます。

  • user/: Aさん担当のユーザー関連機能
  • product/: Bさん担当の商品関連機能
  • order/: Cさん担当の注文関連機能
PROJECT STRUCTURE
src/
  features/
    user/           ← Aさん担当
      components/
      api/
      hooks/
    product/        ← Bさん担当
      components/
      api/
      hooks/
    order/          ← Cさん担当
      components/
      api/
      hooks/
  shared/           ← 共通部品(最小限に)
    ui/
    utils/

共有コード(utils, common)は最小限に

shared/common/ が肥大化すると、結局そこに全員が触ることになり、コンフリクトの温床になります。「これは本当に共通化すべきか?」を常に問いかけ、むやみに共通化しないことが大切です。多少のコード重複があっても、分担のしやすさを優先したほうがチーム全体の生産性は上がることが多いです。

契約を先に決めて並行開発する

APIの仕様(契約)を先に決めておけば、フロントエンドとバックエンドが並行して開発を進められます。これを「Contract-First(契約ファースト)」アプローチと呼びます。

OpenAPIで仕様を先に定義

OpenAPI(旧Swagger)でAPIの仕様を先に定義しておけば、フロントエンドチームはモックサーバーを使って開発を進められます。バックエンドチームは仕様に沿って実装するだけ。結合時のサプライズを大幅に減らせます。

  • フロントはモックサーバーで先行開発可能
  • バックは仕様どおり実装するだけ
  • 結合時の「レスポンス違う問題」を回避
openapi.yaml
paths:
  /users/{id}/profile:
    put:
      summary: ユーザープロフィール更新
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProfileUpdate'
      responses:
        '200':
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

Source: Open Practice Library - Contract-First Development

ライブラリ化で強制的に疎結合にする

より大きなチームや長期プロジェクトでは、コードをライブラリ(パッケージ)として分離する「モジュラーモノリス」アプローチが効果的です。

パッケージベース設計

各機能を独立したパッケージとして切り出すことで、import できる範囲が明確になり、意図しない依存関係を防げます。テストも独立して実行でき、将来的にマイクロサービスへ移行する際の準備にもなります。

  • 強制的な疎結合
  • 独立したテストが可能
  • 必要に応じてサービス分離できる
MONOREPO STRUCTURE
packages/
  auth/           ← 認証ライブラリ(Aさん)
    src/
    package.json
    tests/
  payment/        ← 決済ライブラリ(Bさん)
    src/
    package.json
    tests/
  notification/   ← 通知ライブラリ(Cさん)
    src/
    package.json
    tests/
apps/
  web/            ← 上記パッケージを利用
  api/
言語/環境ツール例
JavaScript / TypeScriptnpm workspaces, Turborepo, Nx
PythonPoetry workspaces, namespace packages
Go標準のモジュールシステム
JavaGradle / Maven マルチモジュール

Source: ByteByteGo - Monolith vs Microservices vs Modular Monoliths

いきなりフルのライブラリ化を目指さない

2-3人の小さなチームでマイクロサービス20個を作っても、管理コストに押しつぶされるだけです。まずはディレクトリで論理的に分け、「いつでもライブラリに切り出せる構造」を保つことから始めましょう。中規模以上(5人〜)のチームや長期プロジェクトになってから、本格的なライブラリ化を検討しても遅くありません。

1 Issue = 1 PR = 完結した変更

分担作業を円滑に進めるには、作業の粒度(Issue の大きさ)も重要です。

良い粒度

  • 1〜数日で完了できる
  • PRが小さく、レビューしやすい
  • その Issue だけで価値が完結する
  • 他のIssueとの依存が少ない

大きすぎる粒度

  • 数週間かかる
  • PRが巨大でレビュー困難
  • マージするまで他の作業がブロック
  • 途中で仕様変更があると大ダメージ

大きなタスクは階層で分解する

「ユーザー管理機能の実装」のような大きなタスクは、Epic → Story → Task の階層に分解します:

  • Epic: ユーザー管理機能
  • Story: ユーザー登録機能 / ログイン機能 / プロフィール編集機能
  • Task: 登録フォームのバリデーション実装 / 登録APIの実装 / …

背景にある考え方を知る

ここまで紹介してきた考え方は、ソフトウェア業界で長年研究・実践されてきた理論に基づいています。興味があれば、ぜひ原典にも触れてみてください。

Conway's Law(コンウェイの法則)

"Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations."

「システムを設計する組織は、その組織のコミュニケーション構造をコピーした設計を生み出すことを余儀なくされる」

つまり、チームの分け方がそのままシステムの構造に反映されるということです。逆に言えば、望ましいシステム構造に合わせてチームを編成する(Inverse Conway Maneuver)ことで、より良いアーキテクチャを実現できます。

Source: Wikipedia - Conway's law

Domain-Driven Design(DDD)の境界づけられたコンテキスト

DDDでは、大きなシステムを「境界づけられたコンテキスト(Bounded Context)」に分割することを推奨しています。各コンテキストは独自のドメインモデルを持ち、チームはそのコンテキストのオーナーシップを持ちます。

  • 「ユーザー」の意味が「認証コンテキスト」と「決済コンテキスト」で異なっても良い
  • コンテキスト間は明確なインターフェース(API、イベント)で連携
  • チームはコンテキストごとに独立してリリース可能

これは垂直分割の考え方と合致します。機能ドメインごとにチームを分け、そのドメインの全レイヤーを担当させるのです。

Source: Martin Fowler - Bounded Context

Team Topologies

Matthew SkeltonとManuel Paisによる「Team Topologies」は、チーム構造を4つの基本タイプに分類し、価値の流れ(フロー)を最適化するアプローチを提唱しています。

  • Stream-aligned Team: ビジネス価値を直接デリバリーするチーム(メイン)
  • Platform Team: Stream-aligned Teamを支援する基盤を提供
  • Enabling Team: 他チームのスキル向上を一時的に支援
  • Complicated Subsystem Team: 専門知識が必要な複雑なサブシステムを担当

重要なのは「Stream-aligned Team が最も一般的であるべき」という点です。つまり、ほとんどのチームは特定の機能・ドメインを端から端まで担当する—これもやはり垂直分割の考え方です。

Source: Team Topologies - Key Concepts

"If the architecture of the system and the architecture of the organization are at odds, the architecture of the organization wins."

— Ruth Malan

Source: IT Revolution - Conway's Law: Critical for Efficient Team Design

プロジェクト開始時にやっておきたいこと

  • 01

    境界を決める会議をする

    プロジェクト初期に「誰がどの領域を担当するか」を明確にします。後から境界を引き直すのは、最初に決めるよりはるかに大変です。ドメインや機能の一覧を洗い出し、担当を割り当てましょう。

  • 02

    ディレクトリ構造のルールを決める

    境界に合わせたディレクトリ構造を最初に設計します。「新しい機能はどこに作るか」のルールが明確なら、迷いなくコードを書けます。

  • 03

    「誰が何を触っているか」を可視化する

    GitHubのCODEOWNERSファイルやプロジェクトボードを活用し、担当領域を可視化しましょう。レビュワーの自動アサインにも使えて便利です。

CODEOWNERSファイルの活用

GitHubのCODEOWNERSファイルを設定すると、特定のディレクトリやファイルの変更時に自動でレビュワーがアサインされます。担当領域の明確化と、適切な人によるレビューの両方を実現できます。

  • @alice: user関連の変更を担当
  • @bob: product関連の変更を担当
  • @charlie: order関連の変更を担当
.github/CODEOWNERS
# 機能ごとのオーナー設定
/src/features/user/    @alice
/src/features/product/ @bob
/src/features/order/   @charlie

# 共有コードは全員でレビュー
/src/shared/           @alice @bob @charlie

# インフラ・CI設定は専任者
/.github/              @devops-team
/infra/                @devops-team

もっと詳しく知りたい人へ(参考文献)

関連トピック

コメント (0)

まだコメントはありません。最初のコメントを残しませんか?

コメントを投稿

メールアドレスが公開されることはありません。必須項目には * が付いています