🕌

チーム開発をうまくやるために技術リードとして取り組んだ内容

2024/12/10に公開

こんにちは、モバイルエンジニアのころむにーです。

普段、モバイルアプリを通じたユーザーへの価値提供を目指すプロジェクトに、技術リードという立場で参加しています。

本記事では、今入っているプロジェクトで取り組んでいる内容について、紹介してみます。

はじめに

私が現在入っているプロジェクトは、ビジネス向けサービスを提供しているプロジェクトです。
プロジェクトでは、モバイル開発の技術リードとしてさまざまな視点で開発や改善活動に取り組んできました。

本記事では、私が取り組んできた内容をまとめてみます。

前提

プロジェクトの概要は以下の通りです。

  • ビジネス向けサービスを提供している
  • Web、iOS、Android の 3 クライアントで各ユーザー向けのアプリを提供し、管理者向けの管理サイトを Web のみで提供しており、これらはすべてプロジェクト内で開発している
  • バックエンドもプロジェクト内で開発している

私は、モバイル開発の技術リードであり、モバイル開発のチームとしては、以下のような規模です。

  • iOS/Android それぞれ 4-5 人程度のエンジニアが所属している
  • また、モバイル専任の QA のメンバーが 4-5 人程度所属している

モバイル開発の技術リードとして、モバイル開発のチームメンバーと関わりつつ、その他にも以下のようなメンバーと関わり、連携してきました。

  • プロダクトオーナー
  • デザイナー
  • (モバイルではなく、プロジェクト全体を見ている)QA
  • サポート
  • 運用・監視
  • バックエンドエンジニア
  • Web フロントエンドエンジニア

本記事の読み方

私が取り組んできた技術リードとしてのノウハウを、構造化し、各項目ごとに独立した形で書いています。
そのため、目次を見て気になった項目だけを読んでいただいても、問題なく理解できるようになっています。

もちろん、上から順番に読んでいただいても問題ありません。

1. 価値提供の中身を、技術を踏まえて最適化する

プロダクトにおいて、技術を無視してユーザーに価値提供を続けていくことはできません。
複雑すぎる技術をプロダクトのいたるところで使うと、価値提供のスピードが遅くなり、最悪の場合プロダクトの寿命を縮めてしまうこともあります。
一方で、簡単な技術を優先しすぎるあまり UX が下がると、プロダクトの価値を下げることに繋がってしまいます。

このように、技術と価値提供の内容はうまくバランスを取る必要があります。

そこで、プロジェクトにおいて、以下のような取り組みを行いました。

1-1. 技術と仕様のバランスを取る

実現が技術的に難しい仕様は、サービスにとっての仕様の重要度と技術的なコストを踏まえて、妥協点を探る必要があります。
技術的に難しい仕様を開発者が人知れず頑張って実装してしまうと、機能開発時のスピードや今後のメンテナンス性の悪化につながるためです。

例えば、以下のような内容です。

各異常系で個別の親切なエラーメッセージを出そうとすると実装が困難である。
しかし、それらの異常系がかなりのレアケースでプロダクトとしても重要でないので、エラーメッセージを統一して簡潔なものにする

1-2. バックエンドとフロントエンドのバランスを取る

仕様の実現の際、バックエンドとフロントエンドのどちらにロジックを寄せるかは、よく検討する必要があります。
どちらに寄せるかで、工数や UX 上実現できること、運用のしやすさ、拡張性など様々な要素が変化してくるためです。

例えば、以下のような内容です。

Web、iOS、Android で共通のロジックは、バックエンドに寄せる

ユーザー操作にリアルタイムで反応させる機能の実現では、バックエンドへの負荷削減とレスポンス速度を優先しフロントエンドのロジックに寄せる

バックエンドの既存 API の修正に関して、iOS、Android の古いアプリから呼び出されても問題ないよう後方互換性を保証する

iOS、Android アプリに関して、今後バックエンドの API でレスポンスの内容が拡張された際に致命的なバグが起こらないよう、拡張を予見した異常系を実装しておくなどの前方互換性を保証しておく

1-3. 外部環境の変化に適応する計画を組み込む

外部環境により発生した対応は、きちんと計画に組み込んでいくことが重要です。
特に期限が決まっているものは、期限を過ぎると相応のリスクがあるためです。
場合によっては、新機能追加よりも、これらの対応を優先するようにロードマップを並び替えることも必要になってきます。

例えば、以下のような内容です。

  • モバイルの新しい OS バージョンへの対応
  • モバイルのアプリストアにおけるポリシー変更への対応
  • ライブラリのサポート終了に伴うバージョンアップ

また、モバイル OS は、新しいバージョンがリリースされると OS 自体に新しい機能が追加されます。
それらの機能を活用した新機能をアプリに追加する計画をプロダクトオーナーに提案し、ロードマップに組み込むことも、価値提供の一環として重要です。

2. 価値提供のプロセスを、技術を踏まえて最適化する

前章では価値提供の中身における、技術を踏まえた最適化について記載しました。

これに加えて、継続的な価値提供には、プロセスも最適化することが重要です。
どんなに魅力的な機能を発案できても、リリースされるまでの時間が長いと、ユーザーの機能に対する熱量は下がってしまいます。
また、ミスのリスクが高いプロセスになっていてバグが多いと、ユーザーの満足度は下がってしまいます。

そこでプロジェクトでは、以下のような取り組みを行いました。

2-1. 開発におけるブランチ管理の簡略化

ブランチ管理は、チーム開発する上で必要な要素です。
しかし、ブランチを活用してリリース内容を制御しようとすると、どんどんブランチ管理は複雑になっていきます。
その結果、オペレーションミスや意図しないバグが発生するリスクを高めてしまいます。

そのため、プロジェクトではトランクベース開発を導入しました。
トランクベース開発は、複雑なブランチ管理を極力行わないまま、継続的に安定したリリースを目指せる手法です。

トランクベース開発を実現するために、以下のような取り組みをしています。

  • 1、2 日以内で完了するタスク単位に分割
  • トランクブランチにおける高頻度の手動リグレッションテスト
  • トランクブランチでの 自動リグレッションテスト(E2E テスト)を実行し、失敗した場合はすぐに修正する
  • トランクブランチへの PR マージ前に静的解析、単体テストなどをチェックし、パスしていない場合はマージを禁止する

トランクベース開発についてはいくつか記事を書いたことがあるので、こちらも参考にしてみてください。

https://zenn.dev/sun_asterisk/articles/trunk-based-development-strategy

https://zenn.dev/sun_asterisk/articles/trunk-based-development-pros-and-cons

2-2. 機能のロールアウト方法の最適化

ユーザーの手元で動作するプログラムを置き換える「デプロイ」と、ユーザーの手元で新機能が発動する「ロールアウト」について、これら 2 つを切り離してプロセス設計することは重要です。
通常、デプロイとロールアウトを同時に行う方法が最も簡単でコストを抑えられます。
一方で、デプロイとロールアウトを分離させることで運用上のメリットがあります。

プロジェクトでは、デプロイとロールアウトを適宜分離するようにしています。
こうすることで、例えば以下のような新機能公開のオペレーションが行えるようになります。

新機能をデプロイした後、まずは 1 割のユーザーにだけ新機能をロールアウトする

新機能に問題があることが監視で判明したため、まずは新機能のロールアウトの拡大を停止し、被害を最小限に抑える

デプロイとロールアウトを分離するために、以下のような取り組みをしています。

  • 設計時点で、ロールアウトの具体的な方法を決める
    • フィーチャートグルを使用する or 使用しない
    • フィーチャートグルを使用する場合、そのライフサイクルはどうするか
      • 開発途中のコードが意図せず発動しないようにするためだけに利用し、リリース時には削除する(リリーストグル)
      • パーセンテージロールアウトや即時ロールバックなどの遠隔でロールアウト状況を制御するために利用するため、リリース後一定期間は保持する(Ops トグル)

3. 開発者のオーナーシップを実装以外の工程にも広げる

ここまでは、価値提供という営みについての最適化について記載しました。
この章は少し視点を変えて、チームメンバーについてフォーカスした内容を記載します。

変化の早い環境で素早く価値提供を続けていくには、開発者が開発プロセスの全ての工程に目を向けていくことが 1 つの理想形と考えます。
開発プロセスの全工程は、例えば、要件定義、設計、実装、テスト、運用・監視、サポートなどを指します。
技術的な知見を持つ開発者が、全工程に対して視線を合わせ最適化のための動きができるようになることで、プロセス全体を見通した効率化や提供価値の最大化ができるようになるためです。

これを目指し、プロジェクトでは以下のような取り組みを行いました。

3-1. 基本的仕様がデグレしていないことを確認する

開発者自身で基本的な仕様がデグレしていないことを意識しながら開発することは重要です。
開発者がデグレを見つければ、より早い工程で修正できるため、開発プロセス全体で見て効率が良くなります。

プロジェクトでは、E2E 自動テストを開発者自身で実装しメンテナンスしていくことを取り組みました。
日次で E2E テストを実行し、失敗した場合はすぐに原因究明し、修正するようにしました。

これにより、以下のようなデグレを開発者自身が発見し、修正するというシーンが見られるようになりました。

ダイアログが二重に表示されて操作性が悪くなってしまうというデグレを E2E 自動テストにより検知し、開発者自身が修正できた

3-2. 運用監視におけるエラーレポートを確認する

開発者自身で運用監視のレポートを定期的に確認し、重大なアラートがある場合はすぐに対応を検討することは重要です。
開発者が見つければ、発生している問題をすぐに解決できるだけでなく、潜在的なコードの品質問題を発見し、より頑健なコードを目指したブラッシュアップに繋げることができるためです。

プロジェクトでは、開発者自身でエラーレポート(クラッシュやフリーズなど)を定期的に確認し、改善のバックログに起票する取り組みを行うようにしました。

これにより、以下のような効果がありました。

フリーズが発生しているレポートを調査することで、UI スレッドなどの実行スレッドが適切に使い分けられていないというような、潜在的に問題のあるコードの箇所が見つかった

クラッシュが一定の割合で発生するライブラリを利用していることが分かり、ライブラリの置き換えなどの検討につながった

4. プロジェクトのリスクを下げる

この章では、価値提供にフォーカスするための環境づくりという観点で記載していきます。
後の章にも同様の内容がありますが、この章ではまず、リスクを下げるという観点の内容を記載していきます。

継続的な価値提供をするためには、予期しないトラブルなどをできるだけ抑え、価値提供の取り組みにフォーカスできる環境を作ることが重要です。

プロジェクトでは、以下のような取り組みを行いました。

4-1. アプリのメトリクスに基づく意思決定

アプリのパフォーマンスやエラー発生率などのメトリクスを収集し、データに基づいた意思決定を行うことは重要です。
感覚や経験だけに基づくよりも、より合理的にリスクを下げることができるためです。

前提として、プロジェクトで扱うアプリは、プッシュ通知が重要な機能の 1 つでした。
ある時、Android のプッシュ通知に関して、端末でプッシュ通知を表示する仕組みで利用していた SDK のメソッドにおける EoL 対応が必要になりました。

この際、サーバーからプッシュ通知を送信した後、Android 端末でどの程度正常に遅延なく表示されているかを収集できる仕組みを作りました。
以下のようにダッシュボードを作り、分析ができるようにしています。

これにより、以下のようなリスクを下げたロールアウトができました。

まず一部ユーザーに変更をロールアウトし、遅延状況をデータを元に確認した。
遅延が悪化していないことを確認できたので、その後全ユーザーに変更をロールアウトした。

4-2. 外部要因によりワークフローが動作しなくなる可能性を下げる

アプリや自動化ツールで利用している外部ライブラリやフレームワークは、適切に管理していくことが重要です。
外部のライブラリやフレームワークはそれぞれ定期的にバージョンアップが行われます。
どのバージョンを利用するかを明確に決めておかないと、ライブラリやフレームワークのバージョンアップにより、アプリや自動化ツールが動作しなくなるリスクを高めてしまいます。
また、バージョンアップにこまめに追従しないと、一気に多くのライブラリをバージョンアップすることになって対応工数が膨大になったり、サポート切れにより緊急でバージョンアップを迫られたりすることがあります。
このような状況になると、開発プロセスが停滞するリスクを高めてしまうため、適切な管理が必要です。

プロジェクトでは、以下のような取り組みをしています。

  • プロダクトコードやテストコード、自動化ツールで利用しているライブラリやフレームワークのバージョンをバージョン管理に置く
  • ライブラリやフレームワークのバージョンアップを定期的に行う

特に、自動化ツールなどで利用しているライブラリやフレームワークは、バージョン管理に置かれずゆるく扱われることが多いです。
しかし、自動化ツールが動かなくなることで開発プロセスが停滞するリスクもあるため、プロジェクトではこれらもバージョン管理に置いています。

これにより、以下のような効果がありました。

Ruby のバージョンを更新するメンテナンスを行うタイミングで、CI/CD のワークフローが動かなくなることが分かり、原因調査を行うことができた。

全てのライブラリは基本的に最新のバージョンが利用されている状態になり、ライブラリのバージョンアップを余裕を持って対応できるようになった。

4-3. ワークフローのオペレーションミスを排除する

開発の各種ワークフローのオペレーションミスを排除するように仕組み化を進めることは重要です。
例えば、チケットの管理方法や PR とチケットの紐付けなど、ワークフローには必ず決まりがありますが、それを人手で完璧に守りきるのは難しいためです。

プロジェクトでは、以下のような取り組みを行いました。

  • バックログのチケットと PR が紐づいているかを自動でチェックする

これには、Danger を利用しています。

https://danger.systems/ruby/

GitHub のブランチ保護ルールで、Danger によるチェックがパスしない場合はマージを禁止するようにしています。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule

これにより、以下のような成果がありました。

PR に必ずバックログのチケットが紐づけられるようになり、紐付け漏れが 0 になった

5. 内部品質を継続的に改善する

この章では前章と同様に、価値提供にフォーカスするための環境づくりの一環における、コードにフォーカスした内容を記載していきます。

中長期的に価値提供していくサービスにおいては、コードのメンテナンス性などの内部品質を高く保ち続ける取り組みが必須です。
コードは日々変化していくため、何もしないと内部品質は徐々に悪化していきます。
内部品質が悪化していくと、外部品質や開発スピードに悪影響を及ぼし、継続的な価値提供が難しくなっていくためです。

プロジェクトでは、以下のような取り組みを行いました。

5-1. 定量的に測れる内部品質が悪化しないようにするための CI

コードの内部品質は定量的に測れる観点があり、これを悪化しないようにしたり、改善していくことが重要です。
これらはメンテナンス性を高めるために必要なエッセンスが凝縮されているものであるため、従っていくことで基本的には内部品質が改善されていくためです。

プロジェクトでは以下のような取り組みを行いました。

  • PR 提出時、以下のような定量的に測れる内部品質の値が悪化しないことを必須条件とする
    • 静的解析による指摘事項の数
    • デッドコードの数
    • Typo の数

エンジニア同士のコードレビューで指摘していく方法もありますが、CI で自動的にチェックすることで、エンジニアの負担を減らし、コードの品質を保つことができます。

これにより以下のような効果がありました。

静的解析による警告とエラー数が 0 になり、0 で維持することが当たり前になった

デッドコードが増えないようになり、コードのメンテナンス性が向上した

静的解析やデッドコードなどを気にする動きがエンジニア間でみられるようになった

5-2. 開発プロセスやコード品質の定性的・定量的な測定と観察

リファクタリングや開発プロセスの改善の効果を定量的・定性的に測定し、観察することは重要です。
これらの改善活動が間違っている場合に適切な軌道修正することや、日々の取り組みにモチベーションや自信を保つことにつながるためです。

プロジェクトでは以下のような取り組みを行いました。

  • コード品質のメトリクス(単体テストのコードカバレッジ)の定量的な測定と観察
  • エンジニアメンバーによる開発プロセスに関する定性的なアンケートの実施と集計

これにより、以下のような効果がありました。

開発メンバーがコード品質のメトリクス(単体テストのコードカバレッジ)を意識する場面が見られた

開発者自身がコード品質が順調に維持・向上していると考えていることが確認でき、取り組みが間違っていないことを確認できた

6. 開発プロセスを効率化する

この章では前章と同様に、価値提供にフォーカスするための環境づくりの一環における、効率化にフォーカスした内容を記載していきます。

継続的な価値提供をするためには、開発プロセスを継続的に効率化し、価値提供の取り組みにフォーカスできる環境を作ることが重要です。

プロジェクトでは、以下のような取り組みを行いました。

6-1. 開発やテストを効率化するための仕組み

開発やテストでよく行う作業を効率化することは重要です。
これらは、繰り返し行う作業なので、小さい効率化でも中長期で見れば大きな効果があるためです。

iOS や Android アプリでは、API を通じてデータのやり取りすることが多いです。
その際に、API のリクエストやレスポンスを確認したり、遅延や改ざんなどをしたりすることで、開発やテストに役立つことが多くあります。

そのため、以下のような取り組みを行いました。

Fiddler や Charles などのプロキシーツールの使い方を簡単にドキュメントにまとめ、開発メンバーや QA が使いやすいようにした

Web ソケットの通信をプロキシーツールで確認できるようにするため、アプリ内でプロキシー設定ができるようなデバッグ機能を実装した

https://www.telerik.com/fiddler

https://www.charlesproxy.com/

実際に、プロキシーツールを使うことで以下のような成果が出ました。

ネットワークが遅い状況で再現するバグの調査がしやすくなった

API 修正において、レスポンスを改ざんすることで、効率よく影響範囲を調査できるようになった

6-2. ワークフロー効率化

開発内容の計画や、タスク管理なども繰り返し行う作業なので、細かい効率化でも大きな効果を得られます。

アプリに利用しているライブラリは、定期的にバージョンアップされています。
これを手動で行うと、手間がかかるだけでなく、対応漏れが発生する可能性もあります。

そのため、dependabot や Renovate などのツールを導入し、ライブラリのバージョンアップの PR が自動で作成されるようにしました。

https://docs.github.com/ja/code-security/dependabot/working-with-dependabot

https://docs.renovatebot.com/

最後に

以上、私が直近のプロジェクトで技術リードとして取り組んできた内容について紹介しました。
技術リードとして動くべき内容は、プロジェクトの規模や状況によって異なると考えられます。
しかし、各項目のような抽象的な視点を持ちながら、プロジェクトやチームの状況にあった施策を打っていくことで、効果的な価値提供に繋げていくことができると考えています。

今後も、技術リードとしての取り組みを続けていく中で、新たな知見やノウハウを得ていきたいと考えています。

GitHubで編集を提案
Sun* Developers

Discussion