みなさんこんにちは。エンタープライズ第一本部の鈴木です。
この記事では、Feature Flag(フィーチャーフラグ)を用いた開発手法についてまとめていきます。Feature Flagは昔からある開発手法の一つですが、調査する中で面白い手法だと思ったため、改めてまとめたいと思います。
1.はじめに
まずはじめに、Feature Flagについて説明します。
Feature Flagとは、新たなコードをデプロイすることなく、実行時に特定の機能をオンまたはオフにするソフトウェア開発手法です。
フィーチャートグルやフィーチャースイッチなどとも呼ばれることがあります。
以下に簡単なTypeScriptの実装を掲載しています。
// app/page.tsx const featureFlag = true; export default function Home() { return ( {featureFlag ? ( <div> <p>🎉 新機能が有効になっています!</p> </div> ) : ( <div> <p>🚧 新機能は無効になっています。</p> </div> )} ); }
このように、特定の機能やコードブロックが実行されるかどうかを条件文にして、それをフラグによってオンまたはオフを切り替えます。
この例では分かりやすいようにfeatureFlag
という変数をハードコードしていますが、通常は設定ファイルやデータベース、管理サービスなどで保持します。
上記の通り、概要自体はとても単純ですが、Feature Flagを開発に取り入れることで得られるメリットは多くあります。
次章では、Feature Flagを開発に取り入れることで得られるメリット/デメリットについてまとめます。
2.メリット/デメリット
2-1.メリット
リリースサイクルとデプロイの分離
Feature Flagを採用することの1番のメリットは、リリースサイクルとデプロイの分離ができることだと思います。
Feature Flagによって、実装したコードをデプロイするタイミングと、その機能をユーザーに提供するタイミングを分離することができます。
言い換えると、機能リリースタイミングに依存せずに開発チームは任意のタイミングでコードをデプロイすることが可能になります。
デプロイの独立性が高まることで、開発チームは迅速に短いスパンでコード変更をメインブランチにマージしたり、本番環境にデプロイしたりしやすくなります。
また、ユーザーへの機能リリースは、任意のタイミングでFeature FlagをONにすることで、コードのデプロイに依存せずにリリースが可能になります。これによりリリースのタイミングとコードデプロイを合わせなくても良くなります。
安全なデプロイ
Feature Flagによって安全に本番環境へデプロイすることができます。
開発者はユーザーが新しく開発した機能を利用できない状態でデプロイすることができます。
また、Feature Flagは「キルスイッチ」としても機能します。仮に本番環境で新機能を有効化して問題が発生した場合、問題のある機能をピンポイントで、ロールバックなしに迅速に無効化することができます。
以上から、機能リリースのリスクを抑えつつ、安全に本番環境へデプロイすることができます。
本番環境での容易な検証と実験
Feature Flagによって本番環境を用いて、以下のような検証がしやすくなります。
①本番環境のトラフィックやデータ量を用いた動作検証
通常、検証環境では本番同等のトラフィックやデータ量を完全に再現して検証することができないため、本番環境で予想外のインシデントが発生する可能性があります。検証用ユーザー限定でFeature Flagを有効化すれば、本番環境のトラフィックやデータ量など実運用環境でシステムを検証することができます。
②カナリアリリースによる検証
カナリアリリースとは、新バージョンと旧バージョンのアプリケーションを並行稼働させ、新バージョンを段階的に少数のユーザーに提供し、問題がないことを確認してから全体にリリースする手法です。炭鉱でカナリアを用いて有毒ガスを検知していたことに由来します。
一部のユーザーでのみFeature Flagを有効化してリリースすることで、新機能のリリースの影響が全ユーザーに及ぶリスクを回避しながらリリースすることができます。
仮に問題が発生した場合は、Feature Flagを無効にすることで迅速にロールバックが可能となります。また、リリース後に問題が発生しない場合は、Feature Flagを有効にするユーザーの割合を増やすことで比較的安全に全体へのリリースが可能となります。
③A/Bテストによる検証
Feature Flagによって、本番環境で新機能と既存機能を比較し、データに基づいて意思決定を行うことができます。
例えば、ユーザーの半分は新機能を有効化し、残りは無効化にします。そして本番環境で特定のメトリクス(アプリの使用率、コンバージョン率など)やユーザーフィードバックを収集します。それらを分析して、新機能を完全にリリースするか、改善するか、削除するかなど、実データに基づいた精度の高い意思決定をすることができます。
2-2.デメリット
コードベースの乱雑化とテスト(CI)の複雑化
Feature Flagを採用すると、条件分岐(if文)の実装が増えることになります。
これによってコードベースが乱雑になり、冗長な実装となり得ます。
また、条件分岐が増えることでテスト(CI)の複雑性が高まります。単純に新機能と旧機能(もしくは新機能がない状態)をそれぞれ検証する必要が生じます。複数のFeature Flag同士で依存関係がある場合は、テストケース数が指数関数的に増加するため、設計時に注意する必要があります。
未使用のFeature Flagがコードベースに残ることで、技術負債を抱える可能性がある
Feature Flagの実装でコードベース上に不要なコードが残ることにより、技術負債を抱えることがあります。
適切にFeature Flagが管理されて、検証後にはきちんと削除すれば問題はありません。しかし、検証期間が長期にわたったり、削除漏れがたびたび起こったりすると不要なコードが残り続けることになります。
3.Feature Flagを用いた開発のライフサイクル
Feature Flagを用いた開発のライフサイクルの一例は以下のとおりです。
- Feature Flagを含む機能実装・検証
- コードベースでFeature Flagを定義して、機能を実装する。
- Feature Flagを定義ファイルや管理システムに設定する。
- 検証環境などで動作検証する。
- 本番環境へデプロイ、Feature Flagの有効化
- 開発したコードを本番環境にデプロイする。
- 任意のタイミングでFeature Flagを有効化して機能をリリースする。この時、リリース戦略に合わせて新機能を有効にするユーザーの割合やターゲットグループを設定する。
- 動作やパフォーマンスの検証
- 新機能の動作に問題がないことを確認する。
- 主要な指標(コンバージョン率、ユーザーエンゲージメントなど)や、ユーザーからのフィードバックを収集する。収集した情報を分析することで、実装した機能を完全にリリースするか改善するかの判断材料にする。
- Feature Flagの削除
- 機能が完全にリリースされたり、廃止されたりした場合、技術負債を防ぐためにFeature Flagを削除する。
- コードベースのみでなく設定ファイルや管理システムからも削除する。
4.開発のプラクティス
Feature Flagを開発に導入する際、以下の点に気をつける必要があります。
未使用のFeature Flagの定期的な削除
「2.メリット/デメリット > 2-2.デメリット」に記載したとおり、Feature Flagを削除しないでコードベースに残したままにすると、技術負債を抱え続けることになります。
対策としては、以下が考えられます。
- 機能実装のバックログに「Feature Flagの削除」を含める。
- Feature Flagに有効期限を持たせる。例えば少し乱暴ではあるが、期限切れのFeature Flagを用いたCIは失敗するように設定するなどが考えられる。
Feature Flagの命名と整理
ファイル名やクラス名などと同様に、Feature Flagにも一貫した命名規則を確立しておくことが重要です。
曖昧な命名をしたFeature Flagが乱立すると、何の機能に対するフラグかわからなくなります。結果的にリリース時の設定ミスにより意図しない機能が有効化されてしまったり、不要なFeature Flagがコードベースに残ってしまったりすることが考えられます。
各フラグについて、一貫した命名で、目的と想定動作を文書化して管理しておくことが重要です。
決定点と決定ロジックを分離する
少し実装寄りの話になりますが、Feature Flagによる機能の有効/無効をチェックする場所(決定点)と、Feature Flagの状態を決定する場所(決定ロジック)を分離するのが良いとされています。
例えば以下の実装を見てみましょう。
// featureFlags.ts (決定ロジック) export type FeatureFlagName = "isNewFeature1Enabled" | "isNewFeature2Enabled"; type FeatureFlagStates = Record<FeatureFlagName, boolean>; // 例: 設定ファイルやAppConfigのような管理サービスなどから取得した値 const featureFlagConfig: FeatureFlagStates = { isNewFeature1Enabled: true, isNewFeature2Enabled: false, }; // 決定ロジック export function isFeatureEnabled(flag: FeatureFlagName): boolean { // 引数で指定されるフラグ名に設定されたBoolean型を返却する。 return featureFlagConfig[flag]; }
// app/page.tsx(決定点) import { isFeatureEnabled } from "./featureFlags"; export default function Home() { return ( {isFeatureEnabled("isNewFeature1Enabled") && ( <div>🎉 新機能1が有効です</div> )} {isFeatureEnabled("isNewFeature2Enabled") && ( <div>🎉 新機能2が有効です</div> )} ); }
このように決定点と決定ロジックを分離することで以下のメリットがあります。
①メンテナンス性が向上する。
決定点と決定ロジックを分離することで、フラグの判定基準が変わってもフラグの利用箇所を修正する必要がありません。
そのため、フラグの利用箇所を探して一律変更する必要がなく、決定ロジックのみ修正すれば良くなります。
②テストが容易になる。
決定点と決定ロジックを分離することで、それぞれ単体でのテストが可能となります。
仮に決定点と決定ロジックが同時に記述されていた場合、テストケースが複雑になり、ロジック変更の際にソースコードやテストケースの修正に労力がかかります。そのためデザインパターンの「依存性の注入」の原則の通り、決定点と決定ロジックは分離して管理するのが良いと考えられます。
5.まとめ
今回はFeature Flagについて、概要からメリット・デメリット、そして開発に取り入れる際のプラクティスをまとめました。
Feature Flagについて調査する前は、単に「if文で新機能を有効/無効にするだけでしょう」と雑に理解していましたが、いざ調べると多くのメリットがあることがわかりました。
特に、リリースサイクルとデプロイの分離が実現できることはとても魅力的と感じました。
一方で実際にプロジェクトに導入する場合は、開発サイクルの改善やプロジェクト内の教育など、それなりの労力がかかります。そのため、プロジェクトに導入したときのメリット・デメリットをきちんと整理したうえで判断していくのが大切だと思いました。
この記事には載せていませんが、理解を深めるためFeature Flagを利用したデモアプリを構築してみました。
今後記事にまとめる予定のため、興味がある方はそちらも読んでいただけるとありがたいです!最後まで読んでいただきありがとうございました。
6.参考文献
- What are feature flags? - optimizely.com
- How to Use Feature Flags in Azure DevOps for Seamless Feature Management - www.featbit.co
- Feature Toggles (aka Feature Flags)
執筆:@suzuki.takuma、レビュー:@handa.kenta
(Shodoで執筆されました)