Tema representativo de entrevista

Entrevista de Kotlin: ¿Cuándo deberían los context parameters superar a la inyección de dependencias explícita?

CodingDifícil
Equipo editorial de Offer.ccPublicado Actualizado

Pregunta

Explique la resolución de context parameters en Kotlin y decida cuándo es mejor que los parámetros ordinarios o un contenedor de inyección de dependencias.

Prompt y Contexto

Varias funciones en un servicio de Kotlin necesitan el mismo UserService, logger o contexto de transacción. El equipo quiere reducir el paso repetitivo de parámetros (parameter plumbing) sin ocultar dependencias en variables globales, y debe migrar gradualmente de context receivers a los context parameters de Kotlin 2.2. Explique la semántica, el comportamiento en el sitio de llamada (call-site), el manejo de ambigüedades y los límites. El objetivo es un ingeniero de Kotlin/JVM; no se asume ningún framework de DI.

Qué evalúa el entrevistador

Una respuesta sólida indica que un context parameter es una dependencia implícitamente disponible en tiempo de compilación, no un contenedor en tiempo de ejecución. La resolución ocurre en el sitio de llamada al encontrar un tipo coincidente en el ámbito (scope) actual; múltiples valores compatibles en el mismo nivel son ambiguos. El candidato distingue los context receivers de los context parameters, reconoce la migración y los límites de la característica, y explica por qué los parámetros ordinarios suelen ser más claros en los límites públicos.

Aclaraciones que se deben hacer primero

  1. ¿La dependencia cruza la API pública de un módulo o biblioteca? Si es así, los parámetros explícitos son más fáciles de descubrir, probar y llamar desde otro lenguaje.
  2. ¿La dependencia es estable dentro de un DSL local o una operación de transacción? Si es así, un context parameter puede eliminar el paso repetitivo de parámetros.
  3. ¿Pueden estar en el ámbito dos implementaciones del mismo tipo? Si es así, diseñe argumentos de contexto nombrados o tipos envoltorios semánticos (semantic wrapper types).
  4. ¿El proyecto ya utiliza context receivers? Si es así, confirme los flags del compilador y el alcance de la migración; los dos modos no se pueden mezclar en un mismo módulo.

Estructura de respuesta de 30 segundos

“Un context parameter declara una dependencia en la firma de la función mientras que el ámbito del sitio de llamada la suministra. El compilador la resuelve por tipo; dos coincidencias en el mismo nivel son un error. Se adapta a un DSL local, transacción o contexto de logging y evita el paso repetitivo de parámetros. Para APIs públicas, llamadas entre lenguajes o dependencias reemplazadas con frecuencia, mantengo parámetros explícitos. Durante la migración, habilito la opción de compilador correspondiente e inspecciono las llamadas a receivers y los casos a nivel de clase porque las dos características no son intercambiables.”

Análisis detallado paso a paso

Declare la dependencia y luego proporciónela en un ámbito:

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))
  }
}

La firma todavía declara la dependencia, por lo que quien llama puede verla; context(service) suministra el valor en el sitio de llamada. La resolución coincide con los tipos en el ámbito actual, no con los nombres de las variables. Si serviceA y serviceB del mismo tipo compatible están presentes en el mismo nivel, la compilación falla en lugar de elegir uno silenciosamente. Use un argumento de contexto explícito o tipos envoltorios semánticos cuando ambos significados sean válidos.

El límite con los parámetros ordinarios tiene que ver con la densidad de información. Si un DSL local tiene muchas funciones que comparten un contexto inmutable, un context parameter elimina el paso repetitivo de parámetros. Si una función usa la dependencia una sola vez, un parámetro ordinario es más claro. Las APIs de bibliotecas públicas también deben considerar las llamadas desde Java, la capacidad de descubrimiento en el IDE y la documentación generada; cuanto más lejos viaje una fuente implícita, mayor será el costo de mantenimiento.

La migración desde context receivers es más que un reemplazo de palabras clave. JetBrains señala que los context parameters requieren nombres y que los context receivers a nivel de clase no tienen una contraparte directa. Un módulo no puede habilitar ambos modos. Cambie las opciones del compilador por módulo, luego inspeccione los sitios de llamada, las referencias llamables (callable references) y los receivers a nivel de clase. La documentación oficial también enumera restricciones: los constructores no pueden declarar context parameters, y las propiedades de contexto no pueden tener backing fields ni inicializadores.

Un parámetro anónimo evita un nombre local no utilizado, pero aún participa en la resolución:

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

La regla de decisión es: “Use contexto para un estado compartido local y estable; use parámetros ordinarios o DI explícita para dependencias entre límites, reemplazo explícito o múltiples implementaciones.” No convierta los context parameters en un service locator. Las pruebas aún proporcionan dependencias a través de un ámbito en el sitio de llamada, y un objeto externo sigue siendo el propietario de su ciclo de vida.

Ejemplo de respuesta de alta calidad

Un context parameter es una declaración en tiempo de compilación de una dependencia implícita. La función incluye UserService en su firma, mientras que quien la llama suministra una instancia dentro de context(service); el compilador busca en el ámbito actual por tipo y reporta ambigüedad cuando dos valores coinciden. Lo usaría para un DSL local, una transacción o una cadena de logging que comparte un contexto estable. Para APIs públicas, interoperabilidad con Java o dependencias que se reemplazan a menudo, mantendría parámetros explícitos o DI. Durante una migración de context receivers, cambiaría las opciones del compilador por módulo, inspeccionaría nombres, referencias llamables y receivers a nivel de clase, y tendría en cuenta restricciones como constructores y propiedades con backing fields.

Errores comunes

  • Error → llamar a los context parameters un contenedor de DI en tiempo de ejecución → Por qué falla: el compilador los resuelve en el sitio de llamada → Solución: explique el ámbito, la coincidencia de tipos y la ambigüedad en tiempo de compilación.
  • Error → asumir que los valores del mismo tipo se seleccionan por nombre de variable → Por qué falla: las coincidencias en el mismo nivel son ambiguas → Solución: use un argumento de contexto explícito o un tipo envoltorio semántico.
  • Error → reemplazar context receiver con context parameter mecánicamente → Por qué falla: los nombres, los casos a nivel de clase y las referencias llamables difieren → Solución: migre por módulo e inspeccione cada sitio de llamada.
  • Error → hacer que cada dependencia sea implícita → Por qué falla: la capacidad de descubrimiento en límites públicos disminuye → Solución: mantenga el contexto local y estable, y use parámetros ordinarios en los límites.

Preguntas de seguimiento y respuestas

Pregunta de seguimiento 1: ¿Cómo se reemplaza una dependencia de contexto en una prueba?

Cree un fake o stub que implemente la misma interfaz, luego ejecute el sujeto dentro de context(fake) { ... }. Si una prueba cambia de implementación con frecuencia, los parámetros explícitos suelen ser más claros y hacen visible la dependencia en el informe de la prueba.

Pregunta de seguimiento 2: ¿Qué sucede si dos servicios del mismo tipo deben coexistir?

Introduzca tipos envoltorios semánticos como PrimaryUserService y AuditUserService, o pase un argumento de contexto explícito en el sitio de llamada. No confíe en el orden de declaración porque la resolución no utiliza el orden como prioridad.

Pregunta de seguimiento 3: ¿Por qué un constructor no puede declarar un context parameter?

La documentación actual de Kotlin restringe explícitamente a los constructores declarar context parameters. Para dependencias a nivel de objeto, use un parámetro de constructor ordinario o una fábrica explícita; forzar un contexto global ocultaría los límites de ciclo de vida y de hilos (threads).

Fuentes públicas

Preguntas relacionadas

Herramienta de entrevista relacionada

Usa Captura para un ejercicio de código

Captura el problema y luego aborda en orden las restricciones, la solución, el código, los casos extremos y la complejidad.

Ver la herramienta