Pull Requestを小さくする戦略 - 開発チームのパフォーマンス向上のための第一歩

Pull Requestを小さくする戦略メインカット

Agile Journeyをご覧の皆さん、こんにちは。ZOZOの御立田です。
私が所属する株式会社ZOZOは、「世界中をカッコよく、世界中に笑顔を。」を企業理念として掲げ、ファッションEC「ZOZOTOWN」、ファッションコーディネートアプリ「WEAR」などの各種サービスの企画・開発・運営や、「ZOZOSUIT」「ZOZOMAT」「ZOZOGLASS」などの計測テクノロジーの開発・活用をおこなっています。また、カスタマーサポート、物流拠点「ZOZOBASE」を運営しています。

ファッションコーディネートアプリ「WEAR」やショップスタッフの販売サポートツール「FAANS」を手がける、私が所属するブランドソリューション開発本部では、「開発生産性を3倍に」を目標に掲げ、多くの改善を進めています。

「開発生産性」をどのように定義するかには議論がありますが、まず私たちが向き合ったのは「仕事量の生産性」を向上することです。そして、そのために選んだ手段の一つが、Pull Request(以下、プルリク)を小さくすることでした。本稿では、私たちがなぜプルリクに着目し、そしてどのようにパフォーマンス改善を実施していったかをお伝えしたいと思います。

なぜプルリクのサイズに注目したか

目指すべき指標の設定

開発生産性を向上するといっても、それを実現するためのアプローチはさまざまです。一般化しつつあるFour Keysはその名の通り4種のメトリクスで構成されており、そのうち、どの領域から改善に着手するかは組織によって異なります。数ある選択肢の中から、私たちがまず注目したのは「デプロイ頻度」です。デプロイ頻度が高い組織=ハイパフォーマンスであると考え、下記の2つを指標として設定しました。

  • 「プルリクのマージ数」
  • 「プルリクの平均マージ時間を24時間以内にする」

ご覧の通り、上記指標は厳密には「デプロイ頻度」とは言えません。私たちが「デプロイ頻度」そのものではなく、プルリクを指標化したのは、チームにアプリやWeb、バックエンドなどさまざまな役割のエンジニアが在籍しているからです。チームや役割ごとにリリースサイクルや運用の仕方は異なります。たとえば「月に1度の定期リリース」を担うチームのパフォーマンスを「デプロイ頻度」の高低で評価することは困難です。部署全体を一貫して定量的に観測できる指標として「プルリクのオープンからマージまでの平均時間24時間以内」でした。

課題の認識。まずは、計測から

実際にプルリクを多く、素早くマージしていくことを考えたときに、まずは現在のチームの状況を測定し、正確に把握する必要があります。幸運なことに弊社ではFindy Team+が既に導入済みでしたので、こちらを駆使して数値の分析からはじめることにしました。

まず、Findy Teams+で計測された改善着手前2ヶ月分(2021/10/01〜2021/11/30)の数値を見ることにしました。

Pull Requestを小さくする戦略図版1

  • 2021/11/30 (火)時点の結果
    • オープンからマージまでの平均時間*1 → 321.3h
    • プルリク作成からレビューまでの平均時間 → 74.4h
    • レビュー後クローズまでの平均時間 → 285.5h
    • プルリク作成数 → 1.8件

ご覧のとおり、目標にはほど遠い数値です。これらの結果に基づき、なんらかの改善が必要であることがわかりました。

問題点を推測し、ヒアリングを通じて具体的な課題に落とし込む

数値が得られれば、以下のように、さらに具体的な問題点が推測できるようになります。

  • プルリク作成数が少なく、作成までに時間がかかっているのは、1つのプルリクでの変更が多いためではないか?
  • レビュー完了までに時間がかかっているのは、変更が大きすぎることでレビュアーに負担がかかっているからではないか?
  • マージするまでに時間がかかっているのは、変更の大きさゆえに指摘事項が多くなるためではないか?

次に、こうした問題が発生する要因を探るためにチームへのヒアリングと話し合いを行いました。対話を重ねる中で、メンバーから以下のような意見が挙げられてきます。

  • レビューに時間がかかるため、まとまった時間を取るのが難しい
  • レビューするブランチを手元でビルドすると時間がかかる
  • 複雑な仕様の場合、仕様を理解するまでに時間がかかる

計測、問題の推測、話し合いという段階を経て、プルリクをスピーディに処理できない要因は、レビューのプロセス、そして「レビュアーに優しくないプルリク」が作成されていることに課題があるという結論が得られました。

合言葉は「レビュアーに優しいプルリク」

ここまでのプロセスから、私たちの取り組みの大方針が定まりました。大切なのは「レビュアーに優しいプルリク」を作成することであるという目的をチームで共有し、現在なにが「レビュアーにとって優しくないのか」をまとめます。

  • プルリクの変更行数が多く、巨大であること
  • レビュー環境が整っていないこと

これらの問題への対応策を考え、実施していきます。

巨大なプルリクの弊害と対策

そもそもプルリクが巨大化してしまうのは、「その方がラクだから」という要因があります。レビュイーにとって、一時的なコミットを積み重ね、機能としてまとめて完成させるほうが考えることが少なくなるからです。しかし、こうして生み出されたプルリクは、私たちが直面したようにいくつかの弊害をもたらします。

巨大なプルリクの弊害

【弊害】フィードバックを受け取るのが遅くなる

そもそもプルリクを使ってレビューを依頼する意味は自分の作業内容に不備がないかチェックしてもらい、品質を維持することにあるはずです。では、それがほぼ全ての機能開発を終えたタイミングでレビュー依頼、つまり巨大なプルリクだった場合はどうでしょうか。そして、開発になにか問題があった場合はどうでしょう。

レビュー内容が単純な指摘であれば大きな問題にはなりませんが、そもそも設計上の問題があった場合は作り直すことも考えなければなりません。 こうした大きな手戻りを防ぐ意味で、レビュー依頼は開発プロセスの早い段階で依頼すべきであり、また、欠陥のあるコードを納期や予算の制約によってマージせざるを得ない状況を回避することを目指すべきです。

【弊害】レビュアーの時間拘束

変更行数が多い巨大なプルリクの場合、レビューにかかるコストが大幅に増加し、必然的にまとまった時間が必要となります。また、そのプルリクで「達成したいこと」が複数あるので、仕様の理解にも時間がかかります。

こうした大きなコストはレビュアーがレビュー作業に取り掛かる心理的ハードルを上げ、結果として、後回しにしてしまう要因にもなると考えられます。また、レビュアーも人間ですので集中力には限界があり、指摘漏れが増えます。

【弊害】コードの保守性の低下

機能単位で一挙に開発をした際、「機能が達成している、動作しているからOK」と、品質に対する意識が低下することが往々にしてあります。テストを書くことが文化的に根付いていないチームであれば特に顕著な傾向です。プルリクが巨大であるがゆえに、こうした保守性の低いコードが混入してしまう可能性があります。

逆に、粒度を小さくし、作業スコープを絞り込むと実機やエミュレータ操作で確認できない場面に遭遇しますが、そうなると必然性に「テストを書く」という意識が上がり、テストコード込みでプルリクを作ることになります。テストを通すことが当たり前になれば、仕様変更やAPIの変更でテストが機能しすぐに検知、修正することが可能となり、品質と保守性が向上します。

プルリクをどこまで小さくするかを考える

巨大なプルリクはこのような問題点をはらんでいますが、一方で「どこまで小さくすればいいのか」という疑問もあるのではないでしょうか。プルリクのサイズはそのチームでの運用にも左右されますし、いきなり最適解を導くことは困難です。私たちのチームではGoogle社のCode Review Developer Guideを参考に少しずつルールを定めていきましたが、同時に大事にしたのは、「レビュアーに優しいプルリク」という私たちの大方針です。

極端な例ですが、1行ずつの変更でプルリクを出した場合、それはレビューしやすい「レビュアーに優しいプルリク」とはいえません。必ずしも“最小”のプルリクを目指すのではなく、目指すのは「レビュアーに優しいプルリク」であることを理解すると、サイズに対して柔軟に考えることができて楽になります。

【プルリク細小化戦略1】サブタスクで粒度を下げる

さて、ここからはプルリクのサイズを下げるための具体的な取り組みをお伝えしていきます。弊社では案件管理にJira Software(以下、Jira)を採用しており、普段からなるべく小さい粒度で課題チケットを作成するように心掛けていましたが、ブランチ運用上難しいパターンがありました。

ブランチ運用は、main、developの2つが基本で、原則的に2週間に1回の定期リリースをしており、開発フェーズではdevelopに各々対応したチケットをマージします。その後、テストフェーズでQAチームに共有してテスト、リリースの流れとなっています。

Pull Requestを小さくする戦略 - ブランチ図

小さな粒度のチケットといっても、あくまでもそれ単体でdevelopにマージして問題ない単位としていたため、必然的に大きくなる傾向がありました。

とくに、定期リリース2週間の期間内に収まらない案件(もしくはリリーススケジュールが先のもの)が進行している場合、プルリクは巨大化する傾向にありました。以前まではすべて完了するまで作業ブランチにコミットし、その作業ブランチをdevelopに向けてプルリクを作成するというフローだったからです。

つまり、案件内容が大きければ大きいほど差分が膨らみ、仕様の理解が難しくなり、結果、レビュアーの負担が大きくなりレビューの精度が低下してしまいます。この問題を解消するために私たちが活用したのが、Jiraのサブタスク機能です。

1つのチケットを実装者がサブタスクへと細分化することで、実践的な意味合いを保ちつつ、粒度の小さいものにできます。実装者はそのサブタスクを完了させたら親のチケットに対してプルリクを作成します。最後は、親のチケットをdevelopに向けてプルリク作成します。こうしたフローを採用したことで、実装者は以前の開発作業と同じように、一区切りついたタイミングでレビューをしてもらえ、フィードバックサイクルも短くなります。

また、作業が細分化され、明確になることでチームメンバーの進捗管理がしやすくなるという副産物があることも付記しておきます。

【プルリク細小化戦略2】Feature Flagの導入

サブタスク活用が戦略の基本ではありますが、これだけでは解決できないケースも存在します。たとえば中長期的な開発で、developにマージせず個人のブランチに残したままだと、マージの際にコンフリクトする可能性が上がり、デグレーションを引き起こしてしまうと、その修正に時間を費やす必要があります。

また、リリース直前まで進めていたブランチが、なんらかの理由でリリースをリスケジュールするというケースも存在します。こうしたケースではリバートする必要がありますが、リリース内容によってはさまざまな要素が複雑に絡み合い、QAを経ずでリリースせねばならない、というリスクになります。

さらに、開発人数が増加したことで並行開発が加速し、リファクタと機能実装の同時進行というケースも増加します。こうしたケースでは、リファクタの対応を機能実装の方に取り込むことが漏れてしまったり、取り込もうとしてもコンフリクトの対処に時間が取られてしまいます。こうしたブランチ運用に起因する課題に対しては、Feature Flagを導入し解決を目指しました。

Feature Flag(機能トグル、機能フラグ、フィーチャー・トグルとも呼ばれます)とは、簡単にいえば、コード内にフラグを設置し、ある機能のOn / Offを簡単に切り替えるための手法です。この手法を詳しく説明している記事を参照すると、トグルは「トグルが存続する期間(longevity)」と「トグルの決定がどの程度動的か(dynamism)」という2軸によって、4つのタイプに分類(以下図参照)されます。

Pull Requestを小さくする戦略 - Feature Toggles分類図

Feature Toggles (aka Feature Flags』より引用

この概念を参照しつつ、私たちはRelease Toggles(リリース・トグル)*2を利用する基本方針を採用しました。リリース・トグルの生存期間は一時的であるのが一般的で、1〜2週間以内にとどめることが推奨されています。また、トグルの決定は静的でリリース時に決定する、という仕様も推奨のとおりです。

ただし、このFeature Flagの運用に手間がかかっては元も子もありません。なるべくコストを掛けずに運用できる姿を目指し、

  • フラグの切り替えを、アプリ起動後デバッグメニューで可能にした
  • 不必要になったフラグの削除をできるだけ機械的(IDEの機能を利用)に行う工夫
  • 基本的な書き方のルールを制定し、チーム内で差分をなくす
  • その他細かな運用はドキュメントで補い、チーム内で共有

といった工夫をしています。まだ導入して間もないですが、最初に述べた問題点は解決できる見込みです。

レビュー環境を整備する

マージまでの時間を短縮するためにはプルリク、そしてレビューの両輪を整備する必要があります。この章では、私たちが実施し、効果的だった施策をいくつか具体的にご紹介します。

プルリク単位でビルドの共有

WEARは2013年にリリースされたアプリで、巨大なプロジェクトになっていることもあり、ビルドに時間を費やしてしまい、レビューする際に開発作業とのスイッチングコストがかかっている状態でした。

また、プルリクごとに各レビュアーが手元でビルドして確認する必要があり、すぐにレビューに取りかかれないという問題もあります。これら問題を解消するため、私たちは他チームにアプリを共有する際、DeployGateを活用し解決を目指しました。

プルリク作成後、GitHub ActionsでDeployGateへビルドをアップロードし、そのURLをプルリクのコメントへ記載することで、環境別のビルドをすぐインストールできる状態になります。また、プルリクがマージ、クローズされた際はそのURLを無効化する処理も行っています。

この結果、レビュー時にかかっていたビルド時間をゼロにすることができ、各々のPCに負荷をかけることなくレビューできる環境が得られます。

レビュー会の開催

レビュー会と称して、毎日1時間チーム全員で集まる会をスケジュールに加えました。Discordの音声チャンネルに集まり、その場で全員がレビューします。優先してほしいレビューの共有や、口頭での質問もこの会の中で行います。

この会の存在によって、レビューすることへの心理的ハードルが下がり、また、スケジュールに組み込むことでレビュー時間の確保と、チームでレビューする意識が醸成されます。もちろん、その場で口頭で相談できることからチーム内コミュニケーション活発化も期待できます。

この取り組みはメンバーからも好評で、レビューのやりやすさが向上したというフィードバックが得られただけでなく、レビュー件数の向上といった数値的結果も見られました。

プルリクテンプレートを充実させる

レビュー環境整備と並行し、プルリクそのものも改善していきます。まず着手したのは、プルリクのテンプレート整備です。人によって違いが出ることを防ぎ、また「なにを書くか」を迷わないようにすることを目的としています。テンプレート(一部抜粋)は以下の通りです。

  • Jiraのチケットリンクを必須で記載
    • ブランチ名をWEAR-****をprefixとして作成するルールにし、プルリクで作成したものと使用するブランチが一致するかをCIで自動的に確認する
  • 動画・画像キャプチャを必須で記載
    • ひと目で対応箇所の認識、動作の確認が容易になり認識のズレを減らす
  • テスト内容をレビュアーが再現できるもので表現する
    • 「〜〜の挙動が正しいことを確認」などの曖昧な表現をやめ、「〜〜の挙動が〜〜になる」と明示的な表現に
    • レビュアーがテストに記載されたものをなぞれば再現できる表現にする

こうしたテンプレートに基づく実際のプルリクが以下です。

Pull Requestを小さくする戦略 - プルリクサンプル画像

人によって解釈が異なる曖昧な要素を極力除くことで、本質的なコードのレビュー以外の確認項目も減ります。このようにして、レビュアーがコードレビューに集中できるプルリクづくりをチームに浸透させていきました。

コミットのルール策定

また、コミット単位の平準化も効果的な施策でした。一時的なコミットではなく、「そのコミット自体で1つの完結した意味あるもの」とするようにしています。たとえば、画面にボタンを追加するというプルリクならば、以下のようにコミットを積んでいきます。

  • ボタンのUI作成
  • クリックリスナーの設定
  • クリックされたときの処理を実行
  • ボタンの文言を定義
  • コードフォーマットをかける

このように、コミットを積み上げることでコミット単体のレビューが可能となり、レビュアーのコンテクスト理解速度がより向上します。

定常作業の自動化やドキュメント整備

プルリクを上げる、レビューをするというプロセスにはさまざまなルーティンが存在します。こうしたルーティンのなかで、人が管理する必要のないものを自動化することも時間、コスト削減に効果的です。もちろん、ヒューマンエラー対策としても有効です。私たちが自動化を行ったのは、以下のような項目です。

  • Slack通知
    • プルリクがOPENされたらレビュアーに通知する
    • GitHub Discussionsで議題が上がったら通知する
  • プルリクが作成されたら自動でレビュアーをアサインする
    • アサイン忘れ防止
    • 自動化によるレビュアーの均等な割り振り
  • PR Milestone Checkを導入
    • ブランチが並行稼動するため、マージ先のブランチを間違えないように対応プルリクを管理する
  • GitHub ラベルを活用
    • プルリク一覧上でマージできる状態を一目で認識するために、approveをもらったら、LGTMラベルを付与
    • レビューしてもらいたい優先度の高いプルリクの判別のために、priority_highラベルを付与。そのラベルが付与されたらSlackでレビュアーに通知
    • サブタスクの親プルリクは、実質的にただの待ち合わせ用のプルリクなので、除外用ラベルを設定し計測から除外
  • GitHub Wikiに集約
    • 運用ルール、アーキテクチャ、仕様、GitHub Discussionsで議論され決定したガイドラインなどをWikiに残す

改善結果とこれからの課題

こうした大小さまざまな取り組みによって、チームのパフォーマンスは明確に向上しています。いくつかの数字をご紹介します。

Pull Requestを小さくする戦略 - 結果図

  • オープンからマージまでの平均時間
    • 237.0h → 29.9h

目標にしていた24時間には届かないものの、対策前から7.9倍の速度を記録しています。

また、指標とはしていませんでしたが、サイクルタイム(最初のコミット作成〜マージまでの総時間)も大幅に減少しました。

  • サイクルタイム平均値(改善前:2021/10/01〜2021/11/30 改善後:2022/07/01〜2022/08/31)
    • 462.9h → 95.3h

より高いレベルでの生産性向上こそが、次の課題

このように、数値面では大きな手応えを感じていますが、「開発生産性」の本質を考えると、課題はまだまだたくさん存在します。数多くの技術組織の改善をサポートする広木大地氏は以下引用のように、開発生産性をレベル1〜3に整理しています。

開発生産性について議論する前に知っておきたいこと』より引用

この整理に照らせば、私たちが取り組んだのはまだ「レベル1:仕事量の生産性」の領域、開発チーム内で完結できる部分の改善にすぎません。

いままでやってきたことをブラッシュアップしていくことで、開発はより高速化しましたが、私たちが追求すべきは、提供する価値です。次なる段階[レベル2:期待付加価値の生産性]の領域に足を踏み入れるには、チームではなく「プロダクトが提供する価値」を評価をしなくてはいけません。つまり、プロダクト開発組織全体のアウトプットを考えねばならず、「各開発チーム」という単位ではなく、プロダクト全体を見渡す視野が必要となってきます。

なにをもって価値とするかはプロダクトによってさまざまです。いまWEARというプロダクトがどのような価値を提供すべきかを、プロダクトに関わる全員が思考し、価値提供に向けて動いていく組織を作ることが、いま向き合う課題だと考えています。

まとめ

以上がプルリクを小さくするために私たちが取り組んだことです。もしも、同じようにパフォーマンス改善に向き合う方がいたら、まずはぜひ現状の数値を計測してみてください。繰り返しになりますが、問題点を認識し、チームで共有することから改善は始まります。

改善は長期間にわたり少しずつ積み重ねていく取り組みです。もし、改善前の数値を出していなければ、なにか成果が出たとしても「なんか速くなった気がする」という体感的な結果で終わってしまい、せっかくメンバーが取り組んだことが評価できなくなってしまいます。取り組みを評価し、メンバーの改善活動へのフィードバック、組織への改善結果の共有といったプロセスが、改善を加速させます。

現在も私たちのチームでは、数値を見ながら、より改善していくための施策を日々考えています。これらの取り組みが皆さんの開発生産性向上の取り組みにおけるご参考になれば幸いです。

編集:はてな編集部

*1: 旧表記では平均プルリククローズ時間

*2: 進行中の機能をメインブランチにマージでき、そのブランチをいつでも本番環境にデプロイできるようにする。トグルを切り替えるだけで機能を提供できる状態。「[コード] デプロイから [機能] リリースを分離する」という継続的デリバリーの原則を実装する最も一般的な方法。

御立田 悠
御立田 悠(みたてだ・ゆう) @mita_335
2012年株式会社ZOZO(旧株式会社スタートトゥデイ)に新卒入社。ZOZOTOWN開発チームでバックエンドエンジニアとして企画案件を担当。翌年よりWEAR事業立ち上げに従事。バックエンド、Androidを担当。
2017年よりAndroidエンジニアとしてフリーランスを経て、2021年株式会社ZOZOに再入社。主にAndroidブロックで開発生産性の向上に取り組み、8倍近く生産性を向上させた。現在はブランドソリューション開発本部フロントエンド部ディレクターとしてWEAR、FAANSでマネジメントを行う。