Maneja errores con elegancia
Mejora la robustez de la aplicación manejando errores. Aprende sobre excepciones, errores, `try/catch`, `throw` y `rethrow`.
En este capítulo, aprenderás a hacer que tu aplicación sea más robusta
manejando errores de forma elegante.
Explorarás excepciones, bloques try/catch y cómo
crear excepciones personalizadas para gestionar errores de manera estructurada.
Qué lograrás
Prerrequisitos
#Antes de comenzar este capítulo, asegúrate de:
- Haber completado el Capítulo 5 y tener un
entorno de desarrollo de Dart en funcionamiento con el proyecto
dartpedia. - Entender conceptos básicos de programación como funciones y clases.
Errors versus exceptions
#Before you start writing code to handle failures, you need to understand the two main types of failures in Dart: errors and exceptions.
- Exceptions represent conditions that you might expect to happen and that your program can recover from. Examples include invalid user input, a missing file, or a network timeout. Your code can catch and handle exceptions to keep running.
- Errors represent failure conditions that indicate bugs in the code.
Examples include calling a method on a
nullobject, passing an invalid index to a list, or failing to initialize a late variable before use. You don't catch errors. Instead, you let them crash the application, which helps you find and fix the underlying bug.
In Dart, any non-null object can be thrown as an exception.
However, by convention, exceptions implement Exception,
while errors extend or implement Error.
When you design an API, throw subtypes of Exception for
conditions that callers can catch and handle.
Tareas
#En este capítulo, mejorarás la robustez de
tu paquete command_runner implementando el manejo de errores.
Crearás una clase de excepción personalizada
y agregarás manejo de errores al CommandRunner para
gestionar de forma elegante los errores que puedan ocurrir durante la ejecución del comando.
Tarea 1: Crear una ArgumentException personalizada
#Primero, define una clase de excepción personalizada llamada ArgumentException para
representar errores relacionados con los argumentos de línea de comandos.
-
Crea el archivo
command_runner/lib/src/exceptions.dart. Este archivo contendrá la definición de tu claseArgumentException. -
Define una clase llamada
ArgumentExceptionque extienda (extends) deFormatException.command_runner/lib/src/exceptions.dartdartclass ArgumentException extends FormatException { /// The command that was parsed before discovering the error. /// /// This will be empty if the error was on the root parser. final String? command; /// The name of the argument that was being parsed when the error was /// discovered. final String? argumentName; ArgumentException( super.message, [ this.command, this.argumentName, super.source, super.offset, ]); @override String toString() { return 'ArgumentException: $message'; } }Esta clase extiende de
FormatException, que es una clase de excepción integrada en Dart. Incluye propiedades adicionales para almacenar el comando y el nombre del argumento asociados con el error. Esto proporciona más contexto al manejar la excepción.command: El comando que se estaba procesando cuando ocurrió la excepción.argumentName: El nombre del argumento que causó la excepción.
Tarea 2: Implementar el manejo de errores en CommandRunner
#A continuación, actualiza la clase CommandRunner para
manejar posibles errores con elegancia.
Esto implica añadir un callback de manejo de errores,
usar try/catch para gestionar excepciones y
lanzar tu nueva ArgumentException cuando el usuario proporcione una entrada incorrecta.
-
Agrega los imports necesarios.
En
command_runner/lib/src/command_runner_base.dart, añade imports paradart:async(para usarFutureOr) y tu nuevo archivoexceptions.dart.dartimport 'dart:async'; // Add this line import 'dart:collection'; import 'dart:io'; import 'arguments.dart'; import 'exceptions.dart'; // Add this line -
Añade un callback
onErroralCommandRunner.Modifica el CommandRunner para aceptar una función
onErroropcional en su constructor. Esto permitirá al usuario de tu paquete definir su propia lógica de manejo de errores.dartclass CommandRunner { // Add a constructor that accepts the optional callback. CommandRunner({this.onError}); final Map<String, Command> _commands = <String, Command>{}; UnmodifiableSetView<Command> get commands => UnmodifiableSetView<Command>(<Command>{..._commands.values}); // Define the onError property. FutureOr<void> Function(Object)? onError; // The rest of the class implementation... }Este cambio introduce una propiedad
onErrorque acepta nulos. El tipoFutureOr<void> Function(Object)?significa que es una función que toma unObjecty devuelve unFutureo nada (void), y podría ser null. -
Actualiza el método run para usar
try/catch.Envuelve la lógica dentro del método run en un bloque
try/catch. Si ocurre una excepción, este bloque la "atrapará" y la pasará al callbackonErroro la volverá a lanzar (rethrow) si no se proporcionó ningún callback.rethrowconserva el error y la traza de pila (stack trace) originales.dartFuture<void> run(List<String> input) async { // [Step 6 update] try/catch added try { final ArgResults results = parse(input); if (results.command != null) { Object? output = await results.command!.run(results); print(output.toString()); } } on Exception catch (exception) { if (onError != null) { onError!(exception); } else { rethrow; } } } -
Añade validación al método
parse.Finalmente, reemplaza el método
parseexistente encommand_runner_base.dartcon la siguiente versión actualizada. Esta nueva versión es mucho más robusta. Está llena de comprobaciones que lanzarán tuArgumentExceptionpersonalizada cada vez que detecte una entrada de usuario no válida.dart// [Step 6 update] This method is replaced entirely. ArgResults parse(List<String> input) { ArgResults results = ArgResults(); if (input.isEmpty) return results; // Throw an exception if the command is not recognized. if (_commands.containsKey(input.first)) { results.command = _commands[input.first]; input = input.sublist(1); } else { throw ArgumentException( 'The first word of input must be a command.', null, input.first, ); } // Throw an exception if multiple commands are provided. if (results.command != null && input.isNotEmpty && _commands.containsKey(input.first)) { throw ArgumentException( 'Input can only contain one command. Got ${input.first} and ${results.command!.name}', null, input.first, ); } // Section: Handle options, including flags. Map<Option, Object?> inputOptions = {}; int i = 0; while (i < input.length) { if (input[i].startsWith('-')) { var base = _removeDash(input[i]); // Throw an exception if an option is not recognized for the given command. var option = results.command!.options.firstWhere( (option) => option.name == base || option.abbr == base, orElse: () { throw ArgumentException( 'Unknown option ${input[i]}', results.command!.name, input[i], ); }, ); if (option.type == OptionType.flag) { inputOptions[option] = true; i++; continue; } if (option.type == OptionType.option) { // Throw an exception if an option requires an argument but none is given. if (i + 1 >= input.length) { throw ArgumentException( 'Option ${option.name} requires an argument', results.command!.name, option.name, ); } if (input[i + 1].startsWith('-')) { throw ArgumentException( 'Option ${option.name} requires an argument, but got another option ${input[i + 1]}', results.command!.name, option.name, ); } var arg = input[i + 1]; inputOptions[option] = arg; i++; } } else { // Throw an exception if more than one positional argument is provided. if (results.commandArg != null && results.commandArg!.isNotEmpty) { throw ArgumentException( 'Commands can only have up to one argument.', results.command!.name, input[i], ); } results.commandArg = input[i]; } i++; } results.options = inputOptions; return results; } String _removeDash(String input) { if (input.startsWith('--')) { return input.substring(2); } if (input.startsWith('-')) { return input.substring(1); } return input; }Este método parse actualizado ahora se defiende activamente contra las entradas incorrectas. Específicamente, las nuevas sentencias throw manejan varios casos de error comunes:
- Comandos desconocidos:
El primer bloque
if/elseasegura que el primer argumento sea un comando válido. - Múltiples comandos: Verifica que el usuario no haya intentado ejecutar más de un comando a la vez.
- Opciones desconocidas:
El parámetro
orElsedentro defirstWhereahora lanza una excepción si un usuario proporciona una bandera u opción (como--foo) que no ha sido definida para ese comando. - Valores de opción ausentes:
Asegura que una opción (como
--output) vaya seguida de un valor y no de otra opción o del final de la entrada. - Demasiados argumentos: Impone la regla de que los comandos solo pueden tener un argumento posicional.
- Comandos desconocidos:
El primer bloque
Tarea 3: Actualizar cli.dart para usar el nuevo manejo de errores
#Modifica cli/bin/cli.dart para usar el nuevo manejo de errores en CommandRunner.
Abre el archivo
cli/bin/cli.dart.-
Actualiza la función
mainpara pasar una funciónonErroralCommandRunner:cli/bin/cli.dartdartimport 'package:command_runner/command_runner.dart'; const version = '0.0.1'; void main(List<String> arguments) { // [Step 6 update] Add onError method var commandRunner = CommandRunner( onError: (Object error) { if (error is Error) { throw error; } if (error is Exception) { print(error); } }, )..addCommand(HelpCommand()); commandRunner.run(arguments); }This code passes in an
onErrorcallback function to theCommandRunnerconstructor. If an error occurs during the execution of a command, theonErrorcallback function is called with the error object. The callback checks whether the error is anErroror anException. Because anErrorindicates a bug in the code, the callback rethrows it. This lets the application crash so you can fix the bug. For anException, which represents a recoverable failure, the callback handles it gracefully by printing it to the console instead of rethrowing it.
Tarea 4: Actualizar las exportaciones de la librería command_runner
#Haz que ArgumentException esté disponible para la librería command_runner.
-
Abre
command_runner/lib/command_runner.darty añade las siguientes exportaciones:command_runner/lib/command_runner.dartdart/// Support for doing something awesome. /// /// More dartdocs go here. library; export 'src/arguments.dart'; export 'src/command_runner_base.dart'; export 'src/help_command.dart'; export 'src/exceptions.dart'; // Add this line // TODO: Export any libraries intended for clients of this package.Esto asegura que la
ArgumentExceptionesté disponible para los consumidores del paquetecommand_runner.
Tarea 5: Probar el nuevo manejo de errores
#Prueba el nuevo manejo de errores ejecutando la aplicación con argumentos inválidos.
Abre tu terminal y navega al directorio
cli.-
Ejecuta el comando
dart run bin/cli.dart invalid_command.Deberías ver la siguiente salida:
bashArgumentException: The first word of input must be a command.Esto confirma que la
ArgumentExceptionse está lanzando y capturando correctamente.
Revisión
#Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Distinguiste errores de excepciones
You learned that subtypes of Error indicate programming bugs and aren't intended to be caught, while subtypes of
Exception represent recoverable failures that your code can handle gracefully.
Usaste bloques try-catch para manejar fallos
Envolviste el código de riesgo en bloques try-catch para interceptar excepciones. Usaste
on ExceptionType catch (e) para manejar tipos específicos y rethrow para volver a propagar excepciones conservando la traza de pila (stack trace).
Creaste y lanzaste excepciones personalizadas
Creaste una clase ArgumentException que extiende de FormatException para proporcionar información de error enriquecida con contexto. Luego usaste throw para señalar fallos de validación con mensajes significativos para una mejor depuración y comentarios para el usuario.
Cuestionario
#Pon a prueba tu comprensión
1 / 3Devolver
nully dejar que el llamador lo verifique.No exactamente.
Devolver
nullobliga a los llamadores a acordarse de verificarlo. Si se olvidan, pueden ocurrir errores de forma silenciosa. Hay una forma más explícita de señalar problemas.Lanzar una excepción que describa lo que salió mal.
¡Correcto!
Lanzar una excepción detiene inmediatamente la ruta del código inválido, proporciona un mensaje de error claro y obliga al llamador a manejar el error explícitamente.
Imprimir un mensaje de error y continuar la ejecución.
No exactamente.
Imprimir no evita que se utilicen datos inválidos. La función aún necesitaría devolver algo, lo que potencialmente causaría errores más adelante.
Establecer una bandera de error global que otro código pueda verificar.
No exactamente.
El estado global es propenso a errores y fácil de olvidar. Este enfoque no obliga a los llamadores a manejar el error.
throw and rethrow in a catch block?throwes para errores;rethrowes para excepciones.No exactamente.
Ambos funcionan con cualquier objeto lanzable. La diferencia no radica en el tipo de objeto que se lanza.
throwsiempre crea una nueva traza de pila;rethrowconserva la traza de pila original.¡Correcto!
Usa
rethrowcuando quieras registrar o manejar parcialmente una excepción pero aún así dejar que se propague con su traza de pila original intacta para la depuración.throwfunciona dentro de los bloques catch, mientras querethrowsolo funciona fuera de ellos.No exactamente.
Es casi lo contrario.
rethrowsolo funciona dentro de bloques catch.throwfunciona en cualquier lugar.throwcrea una nueva excepción;rethrowsolo imprime la actual.No exactamente.
Ninguno de los dos imprime nada.
rethrowpropaga la excepción, no solo la muestra.
try { riskyOperation(); } on FormatException catch (e) { print(e); }. What happens if riskyOperation() throws an HttpException?La
HttpExceptionse propaga hacia arriba, sin ser capturada por este try/catch.¡Correcto!
La cláusula
onfiltra por tipo de excepción. Dado queHttpExceptionno es unaFormatException, evade este bloque catch por completo.La excepción se ignora silenciosamente y la ejecución continúa.
No exactamente.
Las excepciones nunca se ignoran silenciosamente en Dart. Si un bloque catch no coincide, la excepción no desaparece simplemente.
El programa falla (se bloquea) porque no puedes usar
onconcatch.No exactamente.
on Type catch (e)es una sintaxis válida que filtra qué excepciones capturar. El problema es la discrepancia de tipos, no la sintaxis.La
HttpExceptiones capturada e impresa.No exactamente.
La cláusula
on FormatExceptionsolo capturaFormatExceptiony sus subtipos.HttpExceptiones un tipo diferente.
Siguiente lección
#En la siguiente lección, aprenderás sobre características avanzadas orientadas a objetos en Dart, incluyendo enums mejorados y extensiones. Mejorarás el formato de salida y añadirás color a tu aplicación de CLI.
Unless stated otherwise, the documentation on this site reflects Dart 3.12.2. Page last updated on 2026-07-08. Ver fuente oreportar un problema.