validating-compose-stability について
Jetpack Compose を使うチーム向けの開発スキルです。コミット済みのベースラインと Gradle タスクで、Composable とパラメーターの安定性を CI で検証します。不安定なコードの修正方法ではなく、回帰の検出に重点を置きます。
主な機能
- 01可読性のある .stability ベースライン生成
- 02Gradle タスクによる安定性変更の検査
- 03CI で安定性回帰を失敗扱い
- 04パッケージ、クラス、プロジェクトの除外
- 05ローカルで警告のみのチェック
活用シーン
- 01Composable がスキップ不可になる変化の検出
- 02プルリクエストでの安定性変更レビュー
- 03Kotlin コンパイル後の安定性チェック
- 04プレビュー関数をベースラインから除外
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill validating-compose-stabilityインストール後、Claude Code で/validating-compose-stabilityを入力して読み込まれたか確認します。
詳細
Compose の安定性を検証する — スキップ可能性とパラメーターの安定性を CI ゲートで確認
Compose の安定性、つまりコンポーザブルがスキップ可能か、再起動可能か、またそのパラメーターが安定しているかは、本番環境で再コンポジションのプロファイラーを使うまで見えません。このスキルでは、現在のすべてのコンポーザブルの安定性を .stability ベースラインにスナップショットとして保存し、変更によって安定性が 低下 した場合に CI を失敗させることで、これをビルドゲートにします。使用するツールは Compose Stability Analyzer Gradle プラグインです。(@Immutable/@Stable、コンパイラー設定ファイル、読み取りの遅延など、安定性を考慮したコンポーザブルの作成については、compose-performance-skills リポジトリで扱っています。このスキルは検証/CI レイヤーのみを対象としています。)
このスキルを使用する場合
- ユーザーが、コンポーザブルがスキップ不可能になった場合や、パラメーターが安定から不安定に変わった場合に、
./gradlew stabilityCheckで CI を失敗させたい。 - ユーザーが、すべてのコンポーザブルの安定性を確認できる、コミット済みでレビュー可能なベースライン(
.stabilityファイル)を作成し、変更を PR の差分で確認したい。 Listパラメーターや、ホットなコンポーザブルに追加された@Immutableのないデータクラスが検出されず、再コンポジションのリグレッションがリリースされた。- ユーザーが
stabilityDump、stabilityCheck、composeStabilityAnalyzer、@IgnoreStabilityReport、または「安定性ベースライン」に言及している。 - ユーザーが、ローカルでは警告のみ、CI ではエラーにしたい。
このスキルを使用しない場合
- ユーザーが不安定なパラメーターを 修正 したい(
@Immutable/@Stable、kotlinx.collections.immutable、外部型向けのstability_config.conf、状態の読み取りの遅延、derivedStateOf)。これは Compose のパフォーマンスを考慮したコンポーザブル作成の範囲です。compose-performance-skillsリポジトリの安定性関連スキルを参照してください。このスキルは検証とゲート設定のみを行います。 - ユーザーが実行時に どの 再コンポジションが発生しているかを特定したい(Layout Inspector の再コンポジション回数、
Recomposerのメトリクス、コンポジションのトレース)。これは別のツールであり、ビルド時ではなく実行時に使用します。 - ユーザーが UI の動作テストやスクリーンショットテストを行っている。
../../setup/choosing-test-rule-vs-runtest/SKILL.mdと../../preview/capturing-preview-screenshots-in-ci/SKILL.mdを参照してください。
前提条件
- Compose Stability Analyzer Gradle プラグインが各 Compose モジュールに適用されていること(plugins ブロックで適用します。座標とバージョンは、プロジェクトのカタログまたはプラグインのドキュメントを参照してください)。
- Compose コンパイラーのメトリクスを利用できること。このプラグインはコンパイル済みの出力を分析するため、
stabilityCheck/stabilityDumpの実行前に、対象モジュールの Kotlin コンパイルを実行する必要があります。CI では、needs: buildを指定するか、先に:module:compileDebugKotlinを実行してください。コンパイル前にチェックを実行すると、結果が誤るか、失敗します。 - バージョン管理にコミットされたベースラインがあること:
<module>/stability/<module>.stability(stabilityDumpで生成)。
ワークフロー
- 1. 検証ブロックを設定します。 各 Compose モジュールの
build.gradle.ktsで設定します。
composeStabilityAnalyzer {
stabilityValidation {
enabled.set(true)
outputDir.set(layout.projectDirectory.dir("stability")) // where the .stability baseline lives
includeTests.set(false)
ignoredPackages.set(listOf("com.example.internal"))
ignoredClasses.set(listOf("PreviewComposables"))
ignoredProjects.set(listOf("benchmarks", "examples"))
failOnStabilityChange.set(true) // build fails on stability changes; false => warning-only
ignoreNonRegressiveChanges.set(false) // true => only regressions count; new-stable / improvements ignored
allowMissingBaseline.set(false) // true => no baseline yet is not a failure (bootstrap only)
stabilityConfigurationFiles.add( // same format as the Compose compiler's stability config
rootProject.layout.projectDirectory.file("stability_config.conf")
)
}
}
主な設定項目:failOnStabilityChange(ゲート)、ignoreNonRegressiveChanges(回帰のみ報告し、改善や新たに追加された安定したコンポーザブルは無視します)、stabilityConfigurationFiles(外部型を安定として宣言する Compose コンパイラー形式のファイル)、および契約の対象外とするパッケージ/クラス/モジュール向けの ignored* リストです。
- 2. ベースラインを一度生成してコミットします。 モジュールをコンパイルした状態で、次を実行します。
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump # writes app/stability/app.stability
git add app/stability/app.stability
git commit -m "Add Compose stability baseline"
.stability ファイルは人間が読める形式です。各コンポーザブルについて、完全修飾シグネチャ、スキップ可能/再起動可能のステータス、各パラメーターの安定性分類とその理由が記載されています。Android プロジェクトではバリアント別のタスク(debugStabilityDump、releaseStabilityCheck、…)が生成されます。マルチモジュールプロジェクトではモジュールごとに .stability ファイルが1つずつ生成されます。./gradlew stabilityDump ですべてのモジュールを実行するか、対象のモジュールを指定してください。
- 3. コンパイル後に CI でチェックを実行します。
stabilityCheckは現在のコンパイル結果をコミット済みのベースラインと比較し、3種類の変更を報告します。~は安定性の回帰(コンポーザブル/パラメーターの安定性が低下)、+はコンポーザブルの追加、-はコンポーザブルの削除です。回帰があると、影響を受けるコンポーザブルと安定性の変化を示すメッセージとともにビルドが失敗し、変更が意図的なものであればstabilityDumpを実行するよう案内されます。
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'zulu' }
- run: ./gradlew :app:compileDebugKotlin
stability_check:
name: Compose Stability Check
runs-on: ubuntu-latest
needs: build # MANDATORY — the analysis reads compiled output
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'zulu' }
- run: ./gradlew stabilityCheck
- 4. 意図した安定性の変更を慎重に受け入れます。 回帰が実際に発生し、妥当な理由で受け入れる場合(正当な
Listパラメーターや、本当に安定にできない外部型など)は、変更を記録したコミットとしてベースラインを更新します。決して黙って更新しないでください。
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: PokemonList now takes List<Pokemon> — justified by [reason]"
.stability ファイルの差分がレビュー用の成果物であり、コミットメッセージが監査記録になります。
- 5. 契約の対象外のものを除外します。
@Preview関数や内部の補助コードなど、追跡対象にしないコンポーザブルには@IgnoreStabilityReportを付けてください。これらはダンプとチェックの両方から除外されます。パッケージ/クラス/モジュール全体を除外するには、ignoredPackages/ignoredClasses/ignoredProjectsを使用します。
@IgnoreStabilityReport
@Preview
@Composable
fun UserCardPreview() { UserCard(user = User("John", 30)) }
- 6. (任意)CI では厳格に、ローカルでは警告のみとします。 CI サーバーでは強制し、開発者のマシンではブロックしないようにします。
composeStabilityAnalyzer {
stabilityValidation { failOnStabilityChange.set(System.getenv("CI") == "true") }
}
ほとんどの CI プラットフォームでは CI=true が設定されています。
パターン
パターン:コンパイル前に stabilityCheck を実行する
# WRONG
stability_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./gradlew stabilityCheck # no compiled output yet
# WRONG because: the analyzer reads the Compose compiler's output. With nothing compiled it
# either fails or reports incorrect results. The check must depend on a build step.
# RIGHT
stability_check:
runs-on: ubuntu-latest
needs: build # or: - run: ./gradlew :app:compileDebugKotlin stabilityCheck
steps:
- uses: actions/checkout@v4
- run: ./gradlew stabilityCheck
パターン:「CI を通すため」にベースラインを再生成する
# WRONG — CI failed on a stability regression, so:
./gradlew stabilityDump && git commit -am "fix stability check"
# WRONG because: stabilityDump rewrites the baseline to whatever the code currently produces, so
# the check trivially passes — the regression ships, undocumented. The dump is for *accepting* a
# change you understand, with a commit message that says why; it is not a way to silence the gate.
# RIGHT — investigate the ~ entries first; only then, if the change is justified:
./gradlew :app:compileDebugKotlin :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: <composable> <what changed> — justified by <reason>"
パターン:@Preview 関数がベースラインに混入する
// WRONG — preview functions in the .stability baseline, churning it on every preview edit
@Preview @Composable fun ProfilePreview() { Profile(sampleUser) }
// WRONG because: previews are tooling entry points, not API. Tracking them means the baseline
// diffs every time someone tweaks a preview, drowning real regressions.
// RIGHT — annotate them out (or ignoredClasses = listOf("...Preview...") for a naming convention)
@IgnoreStabilityReport @Preview @Composable fun ProfilePreview() { Profile(sampleUser) }
必須ルール
- 必須: 生成された
.stabilityベースラインファイルをバージョン管理にコミットしてください。これらは共有される契約です。ベースラインがコミットされていなければ、ゲートは何も制御しません。 - 必須:
stabilityCheckは Kotlin のコンパイル後にのみ実行してください(CI ではビルドジョブにneeds:を指定するか、同じ Gradle 呼び出し内でコンパイルしてください)。解析ツールはコンパイル済みの出力を使用します。 - 必須:
stabilityDumpによるベースラインの更新は、根拠をメッセージに記載した、意図的でレビュー済みのコミットとして扱ってください。チェック失敗を解消する手段として使ってはなりません。 - 必須:
@IgnoreStabilityReport(またはignoredClasses/ignoredPackages)を使って、@Previewやその他の非 API コンポーザブルを契約から除外してください。これにより、実際の変更があった場合にのみベースラインの差分が生じます。 - 禁止:
failOnStabilityChange = false(またはallowMissingBaseline = true)を恒久的に設定してはなりません。これらは初期設定用です。ベースラインが作成された後は、回帰時にゲートで失敗させる必要があります。 - 禁止: このスキルを不安定性の修正に使ってはなりません。それは Compose の作成方法(
@Immutable/@Stable、不変コレクション、コンパイラーの安定性設定)の領域です。compose-performance-skillsリポジトリを参照してください。 - 推奨: チームが回帰のみを重視し、
+/新たに安定した項目のノイズが気になる場合は、ignoreNonRegressiveChanges = trueにします。 - 推奨:
failOnStabilityChange.set(System.getenv("CI") == "true")を使い、CI では厳格に、ローカルでは警告のみとします。
検証
-
./gradlew stabilityDumpにより<module>/stability/<module>.stabilityが生成され、コミットされている(変更のないダンプ後にgit statusがクリーンである)。 - コミット済みのベースラインで
./gradlew stabilityCheckが成功し、コンポーザブルを意図的に不安定にすると(例:追跡対象のコンポーザブルにList<T>パラメーターを追加する)、その名前と~エントリーを示すメッセージとともに失敗する。 - CI ワークフローの安定性ジョブに
needs: buildが指定されている(またはstabilityCheckの前にコンパイルが行われる)。 -
@Preview/足場となるコンポーザブルに@IgnoreStabilityReportが付与されている(またはignoredClassesパターンに一致する)。また、.stabilityファイルに表示されない。 -
failOnStabilityChangeがtrue(またはCIに応じてtrueに設定)であり、ベースラインの作成後はallowMissingBaselineがfalseである。
参考資料
- skydoves.github.io/compose-stability-analyzer/gradle-plugin/stability-validation/ —
stabilityDump/stabilityCheckタスク、composeStabilityAnalyzer { stabilityValidation { … } }DSL とすべてのオプション、.stabilityベースライン形式、~/+/-の変更タイプ、@IgnoreStabilityReport、マルチモジュールの動作。 - skydoves.github.io/compose-stability-analyzer/gradle-plugin/ci-cd/ — GitHub Actions の設定、必須の
needs: build依存関係、ビルド失敗時の動作、ベースライン更新ワークフロー、failOnStabilityChange.set(System.getenv("CI") == "true")。 - github.com/skydoves/compose-stability-analyzer — プラグインのソース、座標、設定の完全なリファレンス。
- developer.android.com/develop/ui/compose/performance/stability — ベースラインの基盤となる Compose の安定性に関する概念(スキップ可能/再起動可能、安定したパラメーター)。
- developer.android.com/develop/ui/compose/performance/stability/fix — 不安定性の修正方法(
@Immutable/@Stable、不変コレクション、コンパイラーのstability_configuration_pathファイル)。このゲートが保護する作成者側の作業です。 - 関連スキル:
../../preview/capturing-preview-screenshots-in-ci/SKILL.md— Compose の別の CI ゲート(デバイスでレンダリングするプレビューカタログ)。同様にneeds: buildのような順序に関する考慮事項があります。