Prohíbe GlobalScope.launch en los code reviews
Tu asistente de IA acaba de filtrar una corrutina, y nunca vas a ver el error. GlobalScope.launch compila, corre y se ve inofensivo en un diff — exactamente por eso el código Kotlin generado por IA lo usa tan seguido. Conviértelo en un rechazo automático de review.
Por qué GlobalScope es una fuga por diseño
GlobalScope es un CoroutineScope sin ningún Job detrás. Todo lo que lances ahí vive durante toda la vida de la aplicación y no está atado a nada que pueda cancelarlo. La documentación oficial de Kotlin señala tres modos de falla concretos:
- Trabajo sin control — la corrutina sigue corriendo después de que desaparece lo que la inició (una pantalla, un request, un job).
- Fugas de recursos — una corrutina que nunca se reanuda ni se cancela retiene su memoria, sockets y file handles para siempre.
- Crashes silenciosos — no hay padre que reciba las fallas, así que sin un
CoroutineExceptionHandlerexplícito la excepción aparece de forma específica de la plataforma, o no aparece nunca. Esa es la parte de "nunca vas a ver el error".
La API es tan fácil de usar mal que Kotlin la marca como @DelicateCoroutinesApi: hay que hacer opt-in explícito con @OptIn(DelicateCoroutinesApi::class) para usarla sin warning. Trata esa anotación en un diff como una bandera roja, no como una formalidad.
Qué te da la concurrencia estructurada
Concurrencia estructurada significa que las corrutinas forman un árbol: cada corrutina tiene un padre, el padre espera a sus hijos, y cancelar al padre cancela recursivamente a todos los hijos. De ahí salen dos garantías:
- El trabajo se detiene cuando su dueño se detiene. Cancela el scope y todo lo lanzado en él se cancela — sin huérfanos.
- Los errores tienen adónde ir. La falla de un hijo se propaga a su padre en lugar de desaparecer.
GlobalScope.launch se sale de todo el árbol. Ese es el bug completo.
El checklist de review
| Ves en el diff | Pide en su lugar |
|---|---|
GlobalScope.launch { ... } | Un scope que pertenece al llamador (viewModelScope, un CoroutineScope inyectado, coroutineScope { }) |
Trabajo secuencial envuelto en launch | Una función suspend normal — no hace falta una corrutina nueva |
Trabajo concurrente dentro de una función suspend | coroutineScope { launch { } ; launch { } } |
catch (e: CancellationException) sin relanzarla | Relánzala — tragársela rompe la cancelación de toda la rama |
GlobalScope hardcodeado en una clase | Un CoroutineScope inyectado como parámetro del constructor (la práctica oficial recomendada de Android — además hace la clase testeable) |
Esa última fila importa especialmente para código generado por IA: hardcodear GlobalScope suele significar Dispatchers hardcodeados también, y código que ningún test puede controlar.
La trampa de la cancelación
La cancelación en corrutinas es cooperativa y viaja como una CancellationException. Dos formas en que el código generado la rompe en silencio:
- Un
catch (e: Exception)amplio alrededor de undelayo una llamada de red atrapa laCancellationExceptiony sigue de largo. Relánzala siempre. - Los bucles de CPU sin punto de suspensión nunca observan la cancelación. Agrega
ensureActive()oyield()dentro de los bucles largos.
Cualquiera de los dos bugs más GlobalScope se potencian: trabajo sin dueño que además se niega a morir.
Nivel avanzado: automatiza la prohibición con un linter
No dependas de que un revisor humano lo detecte. detekt incluye la regla GlobalCoroutineUsage (rule set de coroutines) que marca GlobalScope.launch y GlobalScope.async — viene desactivada por defecto, así que actívala:
# detekt.yml
coroutines:
GlobalCoroutineUsage:
active: true
Después pon la misma regla en las instrucciones de proyecto de tu asistente de IA ("nunca uses GlobalScope; lanza en un scope que pertenezca al llamador"), para que el código llegue correcto en lugar de rebotar en review. La única excepción legítima que permite la documentación de Kotlin: un job de fondo que de verdad dura toda la vida de la aplicación, lanzado una sola vez, con un CoroutineExceptionHandler explícito y la anotación @OptIn haciendo visible la decisión.
Recursos
- GlobalScope — kotlinx.coroutines API docs — el warning de API delicada, las tres trampas y los patrones de reemplazo
- Coroutine basics: structured concurrency — por qué los ciclos de vida padre/hijo evitan las fugas
- Cancellation and timeouts — cancelación cooperativa,
CancellationExceptiony por qué hay que relanzarla - Android coroutines best practices — inyectar scopes, evitar
GlobalScope, guía deviewModelScope - detekt coroutines rule set — GlobalCoroutineUsage — la regla de lint que automatiza la prohibición
¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y entrega sistemas LLM de producción.