代表的な面接トピック

Kotlin 面接:Context Parameter が明示的な依存性注入(DI)に勝るべき状況とは?

コーディング難しい
Offer.cc 編集チーム公開日 更新日

質問

Kotlin の context parameter の解決メカニズムを説明し、通常のパラメータや依存性注入(DI)コンテナよりも優れているケースを判断してください。

プロンプトとコンテキスト

ある Kotlin サービス内の複数の関数が、同一の UserService、ロガー、またはトランザクションコンテキストを必要としています。チームは、グローバル変数に依存関係を隠すことなく、繰り返されるパラメータの受け渡し(plumbing)を減らしたいと考えており、context receiver から Kotlin 2.2 の context parameter へ段階的に移行する必要があります。セマンティクス、呼び出し側の挙動、あいまいさの処理、および適用境界について説明してください。対象は Kotlin/JVM エンジニアであり、特定の DI フレームワークは前提としません。

面接官が評価するポイント

優れた回答では、context parameter は実行時コンテナではなく、コンパイル時に利用可能な暗黙の依存関係であると説明します。解決は呼び出し側において現在のスコープ内で一致する型を見つけることで行われ、同一レベルに互換性のある複数の値が存在する場合はあいまい(ambiguous)となります。候補者は context receiver と context parameter を区別し、移行や機能の制限を理解し、パブリック境界においては通常のパラメータの方が明確であることが多い理由を説明します。

最初に確認すべき明確化のための質問

  1. その依存関係はパブリックモジュールやライブラリ API の境界を越えますか?もしそうなら、明示的なパラメータの方が検出やテストが容易であり、他言語からの呼び出しもしやすくなります。
  2. その依存関係はローカル DSL やトランザクション処理の内部で安定していますか?もしそうなら、context parameter によって反復的なパラメータの受け渡しを削減できます。
  3. 同じ型の2つの実装が同時にスコープ内に存在する可能性はありますか?もしそうなら、名前付きコンテキスト引数またはセマンティックなラッパー型を設計してください。
  4. プロジェクトではすでに context receiver を使用していますか?もしそうなら、コンパイラフラグと移行スコープを確認してください。同一モジュール内で両方のモードを混在させることはできません。

30秒の回答フレームワーク

「context parameter は関数のシグネチャ上で依存関係を宣言し、呼び出し側のスコープがそれを提供します。コンパイラは型によってそれを解決し、同一レベルに2つの一致がある場合はエラーになります。これはローカル DSL、トランザクション、ロギングコンテキストに適しており、反復的な受け渡しを回避できます。パブリック API、クロス言語呼び出し、または頻繁に置き換えられる依存関係については、明示的なパラメータを維持します。移行時は、両機能に互換性がないため、対応するコンパイラオプションを有効化し、レシーバ呼び出しやクラスレベルのケースを精査します。」

ステップごとの詳細解説

依存関係を宣言し、スコープ内でそれを提供します。

kotlin
interface UserService {
  fun findUser(id: Int): String
}

context(users: UserService)
fun greeting(id: Int): String = "Hello ${users.findUser(id)}"

fun main() {
  val service = object : UserService {
    override fun findUser(id: Int) = "user-$id"
  }
  context(service) {
    println(greeting(7))
  }
}

シグネチャには依然として依存関係が宣言されているため、呼び出し側から確認できます。context(service) は呼び出し側で値を提供します。解決は変数名ではなく現在のスコープ内の型と一致させます。同一の互換性のある型の serviceAserviceB が同じレベルに存在する場合、暗黙のうちに一方を選択するのではなくコンパイルエラーになります。両方の意味が有効である場合は、明示的なコンテキスト引数またはセマンティックなラッパー型を使用してください。

通常のパラメータとの境界は、情報密度に関係します。ローカル DSL に1つの不変コンテキストを共有する多数の関数がある場合、context parameter は反復的な受け渡しを削減します。ある関数が依存関係を1回しか使用しない場合、通常のパラメータの方が明確です。パブリックライブラリ API では、Java からの呼び出し、IDE での検出性、生成されるドキュメントも考慮する必要があります。暗黙的なソースが遠くに伝播するほど、保守コストが高くなります。

context receiver からの移行は、単なるキーワードの置き換えではありません。JetBrains は、context parameter には名前が必要であり、クラスレベルの context receiver には直接対応するものがないことを指摘しています。モジュール内で両方のモードを有効にすることはできません。モジュールごとにコンパイラオプションを切り替え、呼び出し側、呼び出し可能参照(callable references)、クラスレベルのレシーバを精査してください。公式ドキュメントには制限事項も記載されています。コンストラクタは context parameter を宣言できず、コンテキストプロパティはバッキングフィールドやイニシャライザを持つことができません。

無名パラメータを使用すると、未使用のローカル名を回避しながら、解決に参加させることができます。

kotlin
context(_: UserService)
fun logGreeting(id: Int) {
  println(greeting(id))
}

判断基準は次のとおりです。「ローカルで安定した共有状態にはコンテキストを使用し、境界を越える依存関係、明示的な置き換え、または複数の実装には通常のパラメータや明示的な DI を使用する。」context parameter をサービスロケータに変えてはなりません。テストでは呼び出し側のスコープを介して依存関係を提供し続け、外側のオブジェクトがそのライフタイムを所有し続けます。

高品質な回答サンプル

context parameter は、暗黙的な依存関係のコンパイル時宣言です。関数はそのシグネチャに UserService を含み、呼び出し側は context(service) 内でインスタンスを提供します。コンパイラは現在のスコープを型によって検索し、2つの値が一致した場合はあいまいさを報告します。安定したコンテキストを共有するローカル DSL、トランザクション、ロギングチェーンにはこれを使用します。パブリック API、Java 相互運用性、頻繁に置き換えられる依存関係には、明示的なパラメータまたは DI を維持します。context receiver からの移行中は、モジュールごとにコンパイラオプションを切り替え、名前、呼び出し可能参照、クラスレベルのレシーバを精査し、コンストラクタやバッキングフィールド付きプロパティなどの制限を考慮します。

よくある間違い

  • 間違い → context parameter を実行時 DI コンテナと呼ぶ → 失敗する理由: コンパイラが呼び出し側で解決するため → 修正: スコープ、型マッチング、コンパイル時のあいまいさについて説明する。
  • 間違い → 同じ型の値が変数名によって選択されると思い込む → 失敗する理由: 同一レベルの一致はあいまいになるため → 修正: 明示的なコンテキスト引数またはセマンティックなラッパー型を使用する。
  • 間違い → context receiver を機械的に context parameter に置き換える → 失敗する理由: 命名、クラスレベルのケース、呼び出し可能参照が異なるため → 修正: モジュールごとに移行し、各呼び出し側を精査する。
  • 間違い → すべての依存関係を暗黙的にする → 失敗する理由: パブリック境界での検出性が低下するため → 修正: コンテキストをローカルかつ安定した状態に保ち、境界では通常のパラメータを使用する。

フォローアップの質問と回答

フォローアップ 1: テストでコンテキスト依存関係をどのように置き換えますか?

同じインターフェースを実装するフェイクまたはスタブを作成し、context(fake) { ... } 内で対象を実行します。テストで実装を頻繁に切り替える場合、通常は明示的なパラメータの方が明確であり、テストレポートで依存関係が可視化されます。

フォローアップ 2: 同じ型の2つのサービスが共存しなければならない場合はどうしますか?

PrimaryUserServiceAuditUserService などのセマンティックなラッパー型を導入するか、呼び出し側で明示的なコンテキスト引数を渡します。解決処理は宣言順序を優先度として使用しないため、宣言順序に依存してはなりません。

フォローアップ 3: なぜコンストラクタで context parameter を宣言できないのですか?

現在の Kotlin のドキュメントでは、コンストラクタが context parameter を宣言することを明示的に制限しています。オブジェクトレベルの依存関係には、通常のコンストラクタパラメータまたは明示的なファクトリを使用してください。グローバルなコンテキストを強制すると、ライフタイムやスレッドの境界が不明瞭になります。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る