mcpField
K
kotlin-concurrency-expert

kotlin-concurrency-expert

github.com/new-silvermoon開発992661 回インストール13 週間前に更新
リポジトリ

kotlin-concurrency-expert について

kotlin-concurrency-expert は、Android プロジェクトの Kotlin Coroutines の問題をレビューし、修正するスキルです。構造化並行性とライフサイクル安全性を重視し、動作の変更を抑えます。

主な機能

  1. 01クラッシュ、リーク、ANR、競合の診断
  2. 02ライフサイクルに結び付いたスコープ選択
  3. 03ブロッキング処理を適切な Dispatcher へ移動
  4. 04キャンセル処理と Flow 状態のカプセル化
  5. 05後処理付き callbackFlow への変換
  6. 06テスト用 Dispatcher の注入

活用シーン

  1. 01Android のコルーチン関連 ANR の調査
  2. 02GlobalScope を置き換えてライフサイクルリークを防止
  3. 03UI ライフサイクルに沿った Flow の収集
  4. 04コルーチンのキャンセル処理とテスト容易性の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/new-silvermoon/awesome-android-agent-skills --skill kotlin-concurrency-expert

インストール後、Claude Code で/kotlin-concurrency-expertを入力して読み込まれたか確認します。

詳細

Kotlin Concurrency Expert

概要

構造化並行性、ライフサイクルの安全性、適切なスコープ設定、最新のベストプラクティスを適用し、動作への変更を最小限に抑えながら、Android のコードベースにおける Kotlin Coroutines の問題をレビューして修正します。

ワークフロー

1. 問題をトリアージする
  • 正確なエラー、クラッシュ、または症状(ANR、メモリリーク、競合状態、不正な状態)を把握します。
  • プロジェクトの Coroutines の設定を確認します:kotlinx-coroutines-android のバージョン、lifecycle-runtime-ktx のバージョン。
  • 現在のスコープコンテキスト(viewModelScope、lifecycleScope、カスタムスコープ、またはスコープなし)を特定します。
  • コードが UI に関連するもの(Dispatchers.Main)なのか、メインスレッド以外での実行を意図したもの(Dispatchers.IO、Dispatchers.Default)なのかを確認します。
  • テスト容易性のための Dispatcher のインジェクションパターンを検証します。
2. 最小限で安全な修正を適用する

構造化並行性とライフサイクルの安全性を確保しつつ、既存の動作を維持できる編集を優先します。

よくある修正:

  • ANR/メインスレッドのブロック:重い処理を withContext(Dispatchers.IO) または Dispatchers.Default に移動し、サスペンド関数がメインセーフであることを確認します。
  • メモリリーク/ゾンビコルーチン:GlobalScope をライフサイクルに紐付いたスコープ(viewModelScope、lifecycleScope、またはインジェクションされた applicationScope)に置き換えます。
  • ライフサイクルに応じた収集の問題:非推奨の launchWhenStarted を repeatOnLifecycle(Lifecycle.State.STARTED) に置き換えます。
  • 状態の公開:MutableStateFlow/MutableSharedFlow をカプセル化し、読み取り専用の StateFlow または Flow を公開します。
  • CancellationException の握りつぶし:汎用的な catch (e: Exception) ブロックで CancellationException を再スローするようにします。
  • 協調しないキャンセル:協調キャンセルを可能にするため、タイトなループに ensureActive() または yield() を追加します。
  • コールバック API:リスナーを callbackFlow に変換し、適切な awaitClose によるクリーンアップを行います。
  • Dispatcher のハードコード:テスト容易性のため、コンストラクター経由で CoroutineDispatcher をインジェクションします。

重要なルール

Dispatcher のインジェクション(テスト容易性)
// CORRECT: Inject dispatcher
class UserRepository(
    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) {
    suspend fun fetchUser() = withContext(ioDispatcher) { ... }
}

// INCORRECT: Hardcoded dispatcher
class UserRepository {
    suspend fun fetchUser() = withContext(Dispatchers.IO) { ... }
}
ライフサイクルを考慮した収集
// CORRECT: Use repeatOnLifecycle
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState.collect { state -> updateUI(state) }
    }
}

// INCORRECT: Direct collection (unsafe, deprecated)
lifecycleScope.launchWhenStarted {
    viewModel.uiState.collect { state -> updateUI(state) }
}
状態のカプセル化
// CORRECT: Expose read-only StateFlow
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}

// INCORRECT: Exposed mutable state
class MyViewModel : ViewModel() {
    val uiState = MutableStateFlow(UiState()) // Leaks mutability
}
例外処理
// CORRECT: Rethrow CancellationException
try {
    doSuspendWork()
} catch (e: CancellationException) {
    throw e // Must rethrow!
} catch (e: Exception) {
    handleError(e)
}

// INCORRECT: Swallows cancellation
try {
    doSuspendWork()
} catch (e: Exception) {
    handleError(e) // CancellationException swallowed!
}
協調キャンセル
// CORRECT: Check for cancellation in tight loops
suspend fun processLargeList(items: List<Item>) {
    items.forEach { item ->
        ensureActive() // Check cancellation
        processItem(item)
    }
}

// INCORRECT: Non-cooperative (ignores cancellation)
suspend fun processLargeList(items: List<Item>) {
    items.forEach { item ->
        processItem(item) // Never checks cancellation
    }
}
コールバックの変換
// CORRECT: callbackFlow with awaitClose
fun locationUpdates(): Flow<Location> = callbackFlow {
    val listener = LocationListener { location ->
        trySend(location)
    }
    locationManager.requestLocationUpdates(listener)
    
    awaitClose { locationManager.removeUpdates(listener) }
}

スコープのガイドライン

スコープ使用する場面ライフサイクル
viewModelScopeViewModel の操作ViewModel とともにクリアされます
lifecycleScopeActivity/Fragment での UI 操作ライフサイクルオーナーの破棄時に破棄されます
repeatOnLifecycleUI での Flow の収集ライフサイクルの状態に応じて開始/停止します
applicationScope(インジェクション)アプリ全体のバックグラウンド処理アプリケーションの存続期間
GlobalScope絶対に使用しない構造化並行性を損ないます

テストパターン

@Test
fun `loading data updates state`() = runTest {
    val testDispatcher = StandardTestDispatcher(testScheduler)
    val repository = FakeRepository()
    val viewModel = MyViewModel(repository, testDispatcher)
    
    viewModel.loadData()
    advanceUntilIdle()
    
    assertEquals(UiState.Success(data), viewModel.uiState.value)
}

参考資料

コメント