mcpField
K
kmp-architecture-footguns

kmp-architecture-footguns

github.com/maxrave-dev開発1.7k86 回インストール2 週間前に更新
リポジトリ

kmp-architecture-footguns について

Kotlin Multiplatform のアーキテクチャと言語に関するトラブルシューティングスキルです。モジュール構成、依存性注入、共有コードの落とし穴を扱い、プロジェクト内の境界を検証する方法も示します。

主な機能

  1. 01マルチモジュール構成を解説
  2. 02依存性注入と境界を扱う
  3. 03Kotlin Multiplatform の言語上の落とし穴
  4. 04コード検証用コマンド

活用シーン

  1. 01Kotlin Multiplatform プロジェクトの設計や分割
  2. 02依存性注入の実行時問題の調査
  3. 03マルチプラットフォーム共有コードの保守
  4. 04Gradle モジュールとプロジェクト境界の確認

インストール

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

$npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill kmp-architecture-footguns

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
bitmask-event-wrapper.md
clean-arch-kmp-readiness.md
combining-chars-break-char-literals.md
empty-sentinel-instance.md
expect-actual-composable-capability.md
foss-vs-proprietary-module-pairs.md
hilt-to-koin-migration.md
identity-compare-immutable-setting.md
jvmname-disambiguate-erased-overloads.md
kmp-git-submodule-module-mapping.md
kmp-gradle-settings-catalog.md
kmp-html-entity-decoder.md
kmp-module-split-for-packaging.md
koin-viewmodel-scoping-traps.md
kotlinx-datetime-helper-kit.md
levenshtein-fuzzy-match-pure-kmp.md
marker-interface-nested-enum-polymorphism.md
model-entity-mapping-extension-layer.md
no-use-case-layer-decision.md
repository-resource-flow-pattern.md
restricted-marker-is-not-an-opt-in.md
script-aware-romanization-pipeline.md
small-collection-utilities.md
string-resource-format-limits.md
two-vendors-one-package-kmp-api-skew.md

詳細

Kotlin Multiplatform のアーキテクチャと言語の落とし穴

本番環境の Kotlin および Compose Multiplatform アプリから掘り起こした25個の落とし穴です。各項目は references/ にある1つの短いファイルにまとめられています。各ファイルには、適切なパターン、落とし穴(実際にどのように失敗するのか、その理由)、およびユーザー自身のツリーに対して実行するコマンドを記載した「検証方法」セクションがあります。

このインデックスの使い方。 これから書くコード、または調査中の症状を以下の項目と照らし合わせてください。各項目にはトピックと、その項目を参照すべき症状が記載されています。コードや修正を提案する前に、該当する可能性のあるファイルをすべて Read ツールで読んでください。各ファイルは150行未満です。「検証方法」のコマンドにソースプロジェクトのパスが記載されている場合は、ユーザーのツリーにある同等のパスに置き換えてください。

相互参照。 この一覧にないファイル内でバッククォートで囲まれた落とし穴の名前は、隣接する領域のスキルにあります。../*/references/<name>.md で glob 検索して開いてください。

モジュール構成、依存性注入、アーキテクチャ

  • kmp-module-split-for-packaging — 1 つの Kotlin Multiplatform UI モジュールを、共有アプリ LIBRARY と、薄いプラットフォーム別ランチャーモジュールに分割します。Android アプリケーションモジュールはパッケージングのみを担当し、JVM/デスクトップモジュールは main() を持ち、ライブラリ内の公開関数に処理を引き渡します。Android Gradle Plugin によってアプリモジュールをマルチプラットフォームターゲットとしても使えない場合、パッケージング設定と共有 UI が 1 つのビルドスクリプト内で複雑に絡み合っている場合、またはモジュールをライブラリにした後にリソースアクセサークラスが生成されなくなった場合に活用してください。
  • foss-vs-proprietary-module-pairs — 1 つのコードベースから 2 種類のビルドを提供します。プロプライエタリな統合や認証情報が必要な統合を含む完全版と、何もしないスタブを含むオープン版です。Android 以外のマルチプラットフォームターゲットには存在しないプロダクトフレーバーではなく、同じ公開 API を持つ 2 つのモジュールを Gradle プロパティで切り替えます。トラッキング、キャスト、有料サービスの依存関係をオープンソースビルドから除外する必要がある場合、呼び出し箇所にビルドフレーバーの分岐が散在している場合、または「クリーン」ビルドでも推移的な依存関係を通じてプロプライエタリアーティファクトが取り込まれる場合に活用してください。
  • clean-arch-kmp-readiness — 実際に機能するレイヤー構成で Kotlin Multiplatform アプリを設計します。インターフェースとモデルを持つドメインモジュール、データモジュール内に実装を閉じ込めたリポジトリ、変更の影響が広がらないよう外部統合ごとに設けるモジュールを解説します。さらに、図を信じるのではなく grep で各境界を検証する方法も紹介します。マルチプラットフォームアプリの開発を始めるとき、モノリシックなモジュールを分割するとき、または共有機能コードにプラットフォーム型が現れ始めたときに活用してください。
  • no-use-case-layer-decision — 中規模アプリにインタラクター層やユースケース層が本当に必要かどうか、層がなくてもクリーンな境界をどう保つか(リポジトリインターフェースと純粋なマッピング関数)、存在しないことを仮定せずに確認する方法、そして層を再び追加すべきタイミングを示す具体的な兆候を解説します。この層が効果のない記述作業に感じられるとき、層のないアーキテクチャをレビューするとき、または同じオーケストレーション処理を 3 つ目のビューモデルにコピーしたときに活用してください。
  • hilt-to-koin-migration — アノテーション処理を行うコンパイル時フレームワーク(Hilt/Dagger)から、マルチプラットフォーム対応のランタイムコンテナ(Koin)へ依存性注入を移行します。プロバイダー、ビューモデル、修飾子の機械的な対応付け、アシスト付き注入の扱い、そして移行によって生じる 2 つの問題を解説します。すなわち、コンパイル時に失敗しなくなった依存関係グラフと、コンテナの起動スレッドをブロックするモジュール定義です。移行を計画するとき、移行後にバインディングが実行時に解決されなくなったとき、または移行後にアプリの起動が遅くなったときに活用してください。
  • koin-viewmodel-scoping-traps — ビューモデルのライフタイムを見失わずに、ランタイム依存性注入コンテナ経由でビューモデルを接続する方法を解説します。サービスロケーターの基底クラスとその代償、コンパイルはできても何も登録しないアノテーションベースの定義、各アクセサーが参照するストアオーナーと、アクセサー名ではなくそれによって 2 つの画面がインスタンスを共有するかどうかが決まる理由、そしてビューモデルをプロセス全体のシングルトンとして登録すると何を引き受けることになるかを紹介します。共有されるべきビューモデルが新しいインスタンスとして解決されるとき、明らかにアノテーションが付いているクラスの検索が実行時に失敗するとき、または画面間で状態が消えるときに活用してください。
  • repository-resource-flow-pattern — リポジトリの結果を表すエンベロープ群を紹介します。リモート用ラッパー、読み込み状態を持つローカル用ラッパー、ペイロードのないバリアントに加え、各ビューモデルで同じ分岐を手書きせずに済むラップ側と収集側のヘルパー、転送モデルからドメインモデルへのマッピングを置くべき場所、そしてエンベロープが決して握りつぶしてはならないものを解説します。リポジトリの戻り値の型を設計するとき、画面ごとにエラー処理がばらばらになってきたとき、またはキャンセルされた画面が実際には発生していない失敗を報告するときに活用してください。
  • kmp-gradle-settings-catalog — 多数のモジュールを持つ Kotlin Multiplatform リポジトリ向けの設定ファイルパターンを解説します。リポジトリ内の深い階層にあるディレクトリをフラットな Gradle プロジェクトパスに対応付ける方法、型安全なプロジェクトアクセサーを有効にして名前がどう変換されるかを把握する方法、必要な 2 か所でリポジトリを宣言する方法、実行時にしか問題が現れない競合に備えて推移的なアーティファクトをリポジトリ全体で固定する方法を紹介します。編集していないモジュールから Gradle がプロジェクトは「存在しない」と報告するとき、プロジェクトアクセサーを解決できないとき、または追加したリポジトリがプラグインの解決時に認識されないときに活用してください。
  • kmp-git-submodule-module-mapping — マルチプラットフォームリポジトリで、git サブモジュールを Gradle モジュール群として利用する方法を解説します。サブモジュール内の階層ディレクトリをフラットなプロジェクトパスに対応付け、再帰的なクローンを暗黙知ではなく必須条件にし、ビルドを構成するすべての継続的インテグレーションジョブでサブモジュールを有効にします。新しいクローンでプロジェクトは「存在しない」というエラーが発生するとき、ローカルではビルドが成功するのにランナーでは失敗するとき、または記録されたサブモジュールのポインターが更新されず共有コードの変更が反映されないときに活用してください。
  • two-vendors-one-package-kmp-api-skew — 2 つのベンダーが、同じパッケージ名を異なるバージョンで 1 つのマルチプラットフォームビルドの別々のソースセットに提供するケースを解説します。一方のベンダーではメンバー関数がトップレベルの拡張関数に変更されているため、特定のターゲットでのみメンバー関数が解決され、Android ではコンパイルできる特定のインポートがデスクトップでは失敗する、またはその逆の問題が起こります。重複した座標の見つけ方、ここではワイルドカードインポートが問題ではなく正しい解決策である理由、そして 2 つの依存関係を読みやすく保つためのバージョン固定のルールを扱います。通常の依存関係更新後に共有 UI コードが 1 つのターゲットだけでコンパイルできなくなったとき、未解決参照エラーに表示されたシンボルが別のターゲットのソースには明らかに存在するとき、または同じアーティファクト名を持つカタログ項目が 2 つあるときに活用してください。

Kotlin とマルチプラットフォーム言語の落とし穴とユーティリティ

  • levenshtein-fuzzy-match-pure-kmp — 2 行の編集距離ループを使い、依存関係なしで文字列を候補リストと照合するため、マッチャーをマルチプラットフォームの共通コードに置けます。2 行のメモリ構成、比較前の正規化、最も近い候補を返すのではなく拒否する類似度しきい値、インデックスを壊さずに上位 N 件を選ぶ方法、ファジーマッチングが適切でないケースを扱います。タイトル同士の検索で確信度の高い誤候補が選ばれる場合、「上位 3 件」を返すヘルパーが誤った行や -1 を指すインデックスを返す場合、または使用中のライブラリがプラットフォーム専用のためマッチャーを共通コードに移せない場合に使用します。
  • kmp-html-entity-decoder — プラットフォーム独自のマークアップ用ヘルパーが使えないマルチプラットフォームの共通コードで、名前付き、16 進数、10 進数の文字エンティティをデコードします。名前付き文字のテーブル、2 つの数値変換パス、範囲外のコードポイントによって処理が終了しないようにする範囲チェック、パスを特定の順序で実行すると過剰にデコードされる理由、デコードは境界で一度だけ行うというルールを扱います。&#39; や &amp; などのエンティティ文字列がデコードされずに画面に表示される場合、テキストを二度デコードするとユーザーが入力した文字が失われる場合、または大きなコードポイントで解析が止まる場合に使用します。
  • kotlinx-datetime-helper-kit — マルチプラットフォームの日付時刻ライブラリによる instant と local date-time の変換を、現在時刻、epoch 変換、比較、時間幅をずらすヘルパー、相対的な経過時間フォーマッターなどの名前付きヘルパーでラップし、呼び出し側を冗長な記述ではなく意図が伝わる形にします。各ラッパーで明示的に固定すべき内容と、その背後にある落とし穴の種類を扱います。たとえば、ヘルパーと永続化処理で別のタイムゾーンを読み込む、壁時計型で算術演算を行う、コンポジション中にしか実行できないフォーマッター、解析に失敗しても有効な値を返すケースです。保存したタイムスタンプがデバイスのオフセット分ずれて戻る場合、時計変更の前後または一部のユーザーだけで期間が誤る場合、相対時間のラベルが画面上で更新されない場合に使用します。
  • bitmask-event-wrapper — 下位レイヤーから渡される整数のフラグセットを、contains と containsAny を公開する単一フィールドの値クラスでラップし、呼び出し側でライブラリ定数に対する生のビット演算を記述しなくて済むようにします。ラッパーを割り当てなしで維持する方法、フラグ定数をラッパーと一緒に扱う必要がある理由、「これらのビットのいずれか」と「これらのビットすべて」の違い、フラグではない定数をフラグ検査に渡した場合の動作を扱います。同じビット演算式が複数の呼び出し箇所にコピーされている場合、フラグ検査が等価性チェックとして書かれている場合、またはラッパー型があるのに一度も呼び出されない場合に使用します。
  • empty-sentinel-instance — モデルの companion object に正規の空インスタンスを定義して、保持側のフィールドを nullable にせずに済むようにし、すべてのレイヤーで nullable を受け渡す必要をなくします。これによって null チェックの一群を本当に取り除ける場合と、既存のチェックに加えて二重のチェックが増えるだけの場合、併せて用意すべき空判定、センチネルを永続化や描画リストに含めない方法、nullable が正直なシグナルとなる場面を扱います。呼び出し側で null とセンチネルの両方を検査している場合、空キーの行がストレージに現れる場合、または起動時にリストへ空の項目が 1 つ描画される場合に使用します。
  • jvmname-disambiguate-erased-overloads — ジェネリックなレシーバーの型引数だけが異なり、同じ JVM メソッドにコンパイルされる 2 つの拡張関数を、片方に @JvmName を付けて区別します。このアノテーションが変更する内容、Kotlin 関数の名前変更よりも適している理由、その後に Kotlin 以外の呼び出し側からどう見えるかを扱います。明らかに異なる宣言なのにコンパイラーがプラットフォーム宣言の衝突を報告する場合、同じコレクション型を受け取る 2 つ目のコンバーターを追加したことで昨日までコンパイルできたファイルが失敗する場合、または Kotlin の呼び出し側が使える関数を Java の呼び出し側が見つけられない場合に使用します。
  • model-entity-mapping-extension-layer — すべての変換を、転送ペイロード、ドメインモデル、永続化行の間で行う専用ファイルに、純粋な拡張関数として配置します。関数ごとに変換方向は 1 つだけとし、サスペンド処理もログ記録も含めず、ファイルにほかのものを置かず、grep でレイヤーの健全性を保ちます。変換コードがデータクラス、データアクセスオブジェクト、サービスクライアントに広がっている場合、あるフィールドが別のフィールドに属する値を保持していることが判明した場合、または行を読み直すとユーザーが設定したフラグが消える場合に使用します。
  • marker-interface-nested-enum-polymorphism — 空のインターフェースで無関係なクラスにタグを付け、それぞれの実装クラスが、描画側で分岐が必要な場所を示すネストした enum に回答することで、異種の項目リストを 1 つのコンポーザブルで描画します。sealed 階層よりタグが適する場合、その代わりに失われる網羅性、項目がひそかに描画されなくなる仕組みを扱います。項目が 1 つのファイル内の sealed 階層にまとめられないモジュールから来る場合、新しい項目型が誰にも気付かれず空行として表示される場合、または 2 つのタグが同じアクセサー名を要求し、1 つのクラスが両方を必要とする場合に使用します。
  • expect-actual-composable-capability — @Composable の expect 関数を使ってデバイスの機能を Compose の共通コードに公開し、その機能を持つプラットフォームでは完全な実装を、持たないプラットフォームでは中立値を返すスタブを用意します。これらが取る 3 つの形(測定値、元に戻す処理を伴うエフェクト、状態として読み取るサブスクリプション)と、スタブも正しくなければならない理由を扱います。共有 UI でウィンドウの測定値、スリープ防止フラグ、ウィンドウモードの状態が必要な場合、画面を初めて描画した時点で一方のプラットフォームがアプリを停止する場合、または機能があるプラットフォームでも共通画面がその機能を無効として扱う場合に使用します。
  • small-collection-utilities — 共通モジュールに持たせる価値のある 4 つの小さなヘルパーと、それぞれが誤解を招く 1 つのケースを紹介します。2 つの ID セットの差分を取る対称差、定数時間で検索できる位置インデックス、例外を投げず null を返すタイムスタンプ付きトークン用の寛容な解析・シリアライズの組、外部リンクを独自スキームに書き換える変換器です。差分ですべての項目が変更済みと報告される場合、同じ要素が繰り返されていると位置検索が誤ったインデックスを返す場合、不正な行が 1 つあるだけで画面全体が停止する場合、または検索ボックスにリンクを貼り付けるとリンクそのものが検索される場合に使用します。
  • identity-compare-immutable-setting — 複数フィールドの設定を 1 つの不変値にまとめてサプライヤーとして高頻度のコンシューマーに渡すことで、バッファーごとに N 個の数値を差分比較する代わりに、単一の参照比較で「変更されたか」を判定できるようにし、フィールドが半分だけ更新された状態をコンシューマーが観測できないようにします。バッファーごとまたはフレームごとのコンシューマーがユーザー設定に反応する必要がある場合、または複数フィールドで構成される設定が一貫しない状態で読み取られる場合に使用します。
  • restricted-marker-is-not-an-opt-in — 抑制を追加する前に、opt-in マーカーと restricted-to マーカーを見分けます。前者はコンパイラーによって強制され、明示的な承認を求めます。後者は何も要求せず、異なるうえ、より望ましくない意味を持ちます。キャッシュ済みの成果物から API がどちらを持つか直接読み取る方法と、1 つのライブラリで両方の規約が共存する理由も扱います。@OptIn が必要に見えるのに同じ API が別の場所ではなしでコンパイルできる場合、読んだことのないアノテーションに対して IDE が抑制を提案する場合、またはライブラリの呼び出しに依存して安全か判断する場合に使用します。
  • string-resource-format-limits — マルチプラットフォームのリソースフォーマッターが置換するのは単純な位置指定プレースホルダーだけで、フラグ、幅、エスケープされたパーセント記号には対応しません。そのため、パディング、丸め、単位、記号はコード側で処理し、リソースでは整形済みの要素を結合するだけにします。ほかの 2 つの形式にも同じ制限があることを扱います。生の保存数値をそのまま画面に出力すること、日付時刻ライブラリの月名がロケール検索ではなく定数であることです。書式指定子が画面にそのまま表示される場合、言語にかかわらずラベルが英語で表示される場合、またはデータベースに保存されている単位のまま画面に数値が表示される場合に使用します。
  • script-aware-romanization-pipeline — ICU を取り込まずに Kotlin Multiplatform モジュール全体で多数の非ラテン文字体系に対応する「読み方を表示」や音訳の機能を追加するには、ロケールではなく Unicode ブロック範囲に基づく文字体系ごとのディスパッチ、曲単位ではなく行単位の処理、テーブルに還元できる文字体系と本格的な辞書が必要な 1、2 種類の文字体系の明確な切り分けが必要です。誤った言語の音訳結果が推測される場合、未対応プラットフォームでフォールバックせず失敗する場合、または 2 つの文字体系が混在する行(歌詞と英語の補足など)で誤った文字体系が選ばれる場合に使用します。
  • combining-chars-break-char-literals — デーヴァナーガリー文字、グルムキー文字、その他の発音区別符号付き文字は、エディターでは 1 つの字形に見えても、基底文字と独立した結合文字の 2 つの Char であるため、'x' の文字リテラルにすると「Too many characters in a character literal」というエラーでコンパイルに失敗します。Char をキーとする検索テーブルに、通常のラテン文字以外の発音区別符号付き文字やアクセント付き文字を追加する必要がある場合、または文字単位のテキスト処理でアクセントを含む単語だけが文字化けする場合に使用します。

コメント