Control de flujo: if, else, ternarios y patrones

En Dart, el control de flujo es la base para tomar decisiones en tu código. A diferencia de otros lenguajes como JavaScript, Dart es estrictamente tipado; esto significa que la condición de un if debe ser estrictamente de tipo bool. No existe la “coerción de tipos” donde un número o un objeto se evalúen como verdadero o falso; si intentas pasar algo que no sea un booleano, el compilador te detendrá antes de que el programa corra.

Para manejar decisiones complejas, usamos if-else o cadenas de else if. Sin embargo, cuando la lógica es simple (como asignar un valor basado en una condición), el operador ternario es mucho más limpio. Es importante entender que el operador ternario condición ? valorSiEsVerdad : valorSiEsFalso es una expresión, lo que significa que su resultado puede ser asignado directamente a una variable o pasado como argumento.

Con la llegada de Dart 3.0, el control de flujo se volvió mucho más potente con el if-case [disponible desde Dart 3.0]. Esta estructura permite realizar pattern matching (coincidencia de patrones), combinando la comprobación de un tipo de dato con la destructuración de sus propiedades en una sola línea.

Un aspecto crítico para evitar errores es el análisis de flujo (flow analysis) del compilador. Si compruebas que una variable nullable (como String?) no es nula mediante un if, el compilador “promociona” la variable dentro de ese bloque, tratándola como si fuera no-nullable. Esto te permite acceder a sus métodos sin usar el operador de aserción !. Si intentas hacer esto de forma errónea con variables que no son locales (como campos de una clase), el análisis de flujo no funcionará porque el compilador no puede garantizar que el valor no cambie entre la comprobación y el uso.

Por último, el compilador es capaz de detectar código inalcanzable (unreachable code). Si escribes una estructura de control que garantice que un bloque else nunca se ejecutará (por ejemplo, después de un if (true)), el compilador te avisará para que limpies esa lógica redundante.

void main() {
  // Datos de ejemplo
  String? email = "usuario@ejemplo.com";
  int intentos = 2;
  var respuestaServidor = {'status': 200, 'mensaje': 'Conexión exitosa'};

  // 1. Flow analysis y promoción de tipos
  // Al verificar que 'email' no es nulo, el compilador lo promociona
  // a un tipo String no-nullable dentro de este bloque.
  if (email != null) {
    print('Enviando notificación a: ${email.toLowerCase()}');
  } else {
    print('No hay correo configurado.');
  }

  // 2. Operador ternario
  // Es una expresión que retorna un valor directamente.
  String estadoAcceso = intentos > 5 ? 'Bloqueado' : 'Activo';
  print('El estado del usuario es: $estadoAcceso');

  // 3. Collection if
  // Podemos usar 'if' directamente dentro de la definición de listas o mapas.
  var logs = [
    'Inicio de sesión',
    if (email != null) 'Correo: $email',
    'Fin de sesión',
  ];
  print('Historial de logs: $logs');

  // 4. if-case (Pattern Matching) [disponible desde Dart 3.0]
  // Combinamos la validación de estructura y la asignación (destructuring).
  if (respuestaServidor case {'status': 200, 'mensaje': String msg}) {
    print('Servidor responde: $msg');
  }

  // 5. Caso de código inalcanzable
  if (true) {
    print('Esto siempre se ejecutará.');
  } else {
    // El compilador detectará este bloque como inalcanzable.
    print('Este mensaje nunca se imprimirá.');
  }
}

Desglose del ejemplo

En el primer bloque, fíjate en email.toLowerCase(). Como comprobamos previamente con if (email != null), el compilador ya no trata a email como un String? (que podría ser nulo), sino como un String seguro. Esto es el análisis de flujo en acción.

En el operador ternario, estadoAcceso recibe directamente el resultado de la comparación. No es una sentencia de control que “hace algo”, es una expresión que “devuelve algo”.

La lista logs utiliza un collection if. Esto es extremadamente útil para construir colecciones de forma dinámica sin tener que declarar una lista vacía y luego usar .add(). El elemento 'Correo: $email' solo se incluirá en la lista si la condición se cumple.

La parte más potente es el if-case. En la línea if (respuestaServidor case {'status': 200, 'mensaje': String msg}), estamos haciendo tres cosas a la vez:
1. Verificamos que respuestaServidor coincida con la estructura de un mapa que tenga la clave status con valor 200.
2. Comprobamos que la clave mensaje exista y sea de tipo String.
3. Extraemos el valor de ese mensaje directamente en una nueva variable llamada msg.

Finalmente, el bloque else después de if (true) es un ejemplo de código muerto. El compilador de Dart analiza las ramas posibles y, al ver que la condición es una constante true, marca el else como un error de lógica o un aviso de que el código es inalcanzable.

Dominar el análisis de flujo te permitirá escribir código mucho más limpio, evitando el uso excesivo de ! o casts manuales.

20

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio