開発速度を落とさず品質を担保

前原理来氏(以下、前原):よろしくお願いします。

始めに自己紹介をしたいと思います。名前は前原理来と申します。現在PayPay株式会社のAndroid developerをやっております。2019年4月に法政大学情報科学部を卒業して、ヤフー株式会社に入社しました。研修の後、2019年7月からPayPayに出向しました。(※2020年7月にヤフー株式会社へ帰任)

本日はPayPayの開発スタイルについてお話します。PayPayは非常に早いスピードで開発を進めています。そんな中で開発上でぶつかった困難や苦労したことがいろいろあるわけですが、それらをどうやって乗り越えていったかということをお話できたらなと思います。

まず始めに、みなさんはもうご存知かもしれませんが、PayPayはQRやバーコードで決済ができるペイメントサービスです。ソフトバンク、ヤフー、インドのPaytmの3社で協力し、2018年10月に生まれたばかりの新規サービスです。2020年1月現在、最初にリリースされてから1年と3ヶ月ぐらい経っています。2018年10月にリリースされてから2回の「100億円キャンペーン」を行いました。

そこでグンとユーザ数が伸びました。その後も順調に右肩上がりにユーザが伸びており、昨年11月時点では2,000万人ユーザを突破しております。

PayPayを使う方法はAndroidとiOSアプリのみのため、アプリの機能開発や速度・品質担保が重要なポイントになってきております。さらに昨年、2019年度のユーザ投票によるベストアプリで1位を獲得しました。多くのユーザに使っていただけている決済サービスという性質上、開発速度を落とさず品質担保をしつつユーザに価値を提供することが重要になってきています。

先ほどから申し上げている通り、PayPayでは開発速度が重視されております。開発スタイルとしてはスピード型のアジャイル開発を採用しております。スプリントは週1で毎週リリースしております。なんとビックリ! 2019年はアプリをAndroid・iOSを合わせて合計120回以上リリースをしているという開発サイクルの早さです。

PayPayの開発環境と開発の流れ

続いて、PayPayの開発環境についてお話します。

PayPayは非常にグローバルな環境です。プロダクトチームでは英語も日本語も飛び交っていますが、基本的には英語で会話をしています。Androidチームも多国籍であり、インド、日本、タイ、ポーランド、中国、その他、出身はさまざまです。

会社として英会話や日本語レッスンを導入することにより、なるべく言語の壁をなくそうといった取り組みがあります。私も英語は流暢に喋れるレベルではないですが、特別苦労するとこなくコミュニケーションが取れています。

次に、開発の流れと工夫していることです。スプリントは週1です。月曜・火曜でそれぞれのフィーチャーの開発を進め、水曜から金曜まで開発とQAを進めます。水曜日から金曜日は直近のリリースの不具合修正と次回リリースの新規機能開発を並行して進めています。水曜日からQAをするわけですが、QAで上がってきた不具合はみんなで直します。たいていは、実装担当者が積極的に手を挙げて、不具合を素早くフィックスすることで、QAの不具合改修速度を上げています。QA期間も次回リリース予定の開発も並行して進めておりますので、なるべく早くQAから上がってきた問題に対処するのがポイントです。この1週間で1つのバージョンを仕上げて、翌週の火曜・水曜日にリリースします。

PayPayでは大きな施策が複数同時に進行していて、仕様の確認や調整を早めに行うことによりスムーズに進められるように工夫がされております。PayPayAndroidでは、毎週約100を超えるPRがマージされています。そのため、Androidチームではコードレビューがボトルネックになりがちなので、実装を始める前に不安なところはすぐにチームに相談しながら進めております。ユーザに価値を届けつつ、負債を残さないバランスを保つという事を意識しながらレビューをします。改善できるところは改善しながらトライアンドエラーを重ねて開発サイクルの改善も行っています。

ブランチ戦略の見直しも行っています。ブランチ戦略の見直しをしつつ、コードが不安なところはあらかじめ方針を相談してから開発に取り組むなど、チーム内で協力をしながら積極的に手を挙げて直しつつ、自分の開発を進めております。

PayPayのブランチ戦略

PayPayで採用しているブランチ戦略についてです。ブランチはmasterとrelease、hotfixブランチのみです。featureブランチはありません。releaseブランチは常に一つだけ存在しています。火曜日の夜に自動でmasterからreleaseブランチが切られます。その際、自動でバージョンがインクリメントされます。QAはreleaseブランチで行われ、QAで見つかった不具合はreleaseブランチに直接取り込んでいきます。他にも、1日2回、自動でreleaseブランチからmasterブランチにSyncするPRが作られることによって開発で起こるコンフリクトを少なくしています。

2バージョン以上先にリリースされる機能は基本的に本番に影響のない範囲でmasterに入れていくことによりfeatureブランチといった巨大なブランチができないようにしています。一方でmasterにマージできないものもあります。例えばライブラリのバージョン変更はマージできず、長い間別ブランチにいたことが問題となりました。

具体的に申し上げますと、ある機能を実装するためにMotionLayoutを使うためにContraintLayout2.0のbeta02で開発していました。そのbetaライブラリにはバグが多く、多くの画面でレイアウトが崩れたり特定のOSバージョンでは表示が崩れたりしていることがリリースする直前になって発覚した、ということがありました。

そこで、我々は対策としてマージできないブランチにある問題を早期発見できないかと考えました。普段のリリースから、アプリのAutomation Testチームにはmasterとreleaseブランチでテストをしてもらっています。そのAutomation Testチームと連携を取り、大きなfeatureブランチがある場合はそちらもテストをしてもらうようにしました。masterとreleaseブランチ以外でもAutomationテストをしてもらうことによって、長期間masterから離れた開発ブランチの問題の早期発見をできる仕組みを作りました。

PRをマージするときは初期は “Merge pull request” を使ってすべてのコミットをbaseブランチにマージしていました。すべてのコミットが残るとバグの発生などでロールバックするときにどこに戻ればいいかわからない、少し戻ったらビルドできないコミットがあって困るなどの問題がありました。

そこですべてのPRのマージをSquashに変えました。すると1PR 1commitになり、それらの問題は解消されましたが、新たな問題が生じました。それはreleaseブランチに直接マージされたコミットがmasterとコンフリクトした場合、コンフリクトの解消が大変だということです。1PR 1Commitのはずが、releaseにマージされたPRたちのコミットがまとめられてmasterに入ってしまいます。

先ほど申し上げたとおり、1日に2回、release ブランチからmasterブランチにSyncするPRが作られますが、一度コンフリクトすると、以後すべてのSyncのPRでコンフリクトが発生し、多くのコストがかかっていました。

そこで現在は、「すべてのPRはSquash、ただしreleaseからmasterへのSyncのPRはSquashしない」としています。これによって、masterは1PR 1commitを実現できますし、コンフリクトも少なくなります。Githubには、現時点ブランチやPRによってマージ方法を強制できないので、チームメンバーで認識を合わせる必要があります。

PayPayのAndroidのアーキテクチャ

PayPayAndroidアプリのアーキテクチャについてご説明します。PayPay Androidの98パーセントがKotlinのソースコードです。アーキテクチャとしてMVVMを採用しており、Single Activityです。しかしSingle ActivityでもFragmentは1つも使っておりません。

Fragmentは使っていないのにSingle Activityとはどういうことなのか? それはlyft社のScoopというライブラリを使用しております。ScoopというライブラリはFragmentなしでViewrベースで簡単に画面遷移と画面のパーツ化を実現することができる軽量なフレームワークです。

アプリケーションの複雑化が増すに連れて、Fragmentの大規模な使用は、高いコストが掛かることが多いです。Fragmentのライフサイクルは複雑で管理が難しく、デバッグが非常に困難です。Fragmentの参照がすぐに利用できず、返されたFragmentが存在しなかったりといったことが発生することの例えとして、lift社はFragmentのことを「シュレーディンガーのFragment」と表現しています。Scoopは、lyft社のそういった問題意識から生まれた軽量なフレームワークです。

Scoopは、Viewベースでシンプルなライフサイクルを実現しております。ライフサイクルはAttachとDetachのみです。Viewはインスタンス化を制御できるシンプルなライフサイクルを備えているし、Fragment Transactionによる競合を取り除くことができます。

一つ、Scoopの注意事項として、ScoopのReadMeにDeprecation noteがある通り、現在開発が止まっていますので、現時点での使用は十分に検討する必要があると思います。

ScoopはScreen、ViewController、Router、UIContainerの4つが登場します。それぞれについて説明していきます。

ScoopのRouterは画面スタック、ナビゲーションを管理して、画面の遷移等を管理しております。UIContainerはViewの一種です。UIContainerの中で画面遷移を行うことができます。Fragmentを乗せるレイアウトみたいなイメージです。

Screenは1つの画面の単位です。Routerによってバックスタックに積まれても破棄されないという特徴があり、データを保持しておくこともできます。ViewControllerはScreenの上に乗っています。AttachとDetachのライフサイクルのみです。Routerによって画面遷移が行われるたびに生成と破棄がされますので変数なども初期化されます。ScreenとViewControllerは一対一で紐付いております。

Routerによるナビゲーションについてです。Routerは主に5つの便利なメソッドが用意されております。goToと呼ぶとA、Bと積まれていたときに、CというScreenに対してgoToするとA、B、Cと画面が積まれます。replaceWithはA、Bと積まれているときにreplaceWith Cとすると、Topに積まれているBが消えてA、Cと画面が積まれます。

replaceAllWithを積むと今まで積まれていた画面スタックがすべて破棄され、指定されたスクリーンのリストが順番に積まれるかたちになります。resetToは指定されたところまで画面スタックを巻き戻すことができます。goBackはTopのスクリーンをpopすることができます。これらのメソッドを使ってActivityやFragmentで実現するのにコストがかかるスタックの操作を簡単に行うことができます。

アプリのスコープはアイスクリームのスクープと似ています。ナビゲーションスタックを深くして画面を積んでいくことはコーンにアイスクリームを追加することに似ています。Scoopは単にJavaオブジェクトであり、名前がついたサービスへのアクセスを提供します。例えばHome画面のスコープはプロフィール画面内のスタックを知る必要がありません。

チャレンジングな職場

最後にPayPayについて紹介できたらなと思います。今は入って6ヶ月なんですが、今まで一人で実装した機能として、請求書払い・スキャン回り、マップのUI/UX改善、Yahoo!マネーとの連携などを作りました。

非常にチャレンジングで、裁量がある職場です。圧倒的スピードでアプリを成長させています。ぜひ熱意を持った方の募集をお待ちしております。PayPayはこのように毎日刺激的で日々改善に取り組んでいます。興味をお持ちの方がいましたら、ぜひ一緒に働きましょう!

以上、ご清聴ありがとうございました。