Apex Academy
Módulo 8·03·30 min

throw y propagación

Además de capturar excepciones, puedes lanzarlas tú con throw cuando un dato no cuadra. Y si el método que la lanza no la captura, la excepción sube hasta quien sí pueda hacer algo con ella.

En Admin esUna regla de validación que para el guardado con tu mensaje

✓ En esta sub-lección

  1. 01Lanzar una excepción con throw new cuando un dato no cumple una regla.
  2. 02Explicar cómo sube una excepción de método en método hasta un catch o hasta la plataforma.
  3. 03Decidir dónde capturar: en el sitio que sabe qué hacer con el fallo.

La nutria: ¿Te acuerdas? · Repaso de la lección 2

Account a = [SELECT Id FROM Account WHERE ERP_Code__c = 'ERP-404']; y ese código no existe. ¿Qué excepción salta?

Hasta ahora las excepciones las lanzaba Apex: un texto que no era un número, una consulta sin filas. Pero hay datos que Apex acepta sin rechistar y que tu negocio no: un importe vacío o negativo es un Decimal perfectamente válido para Apex, y un disparate para una renovación. Para esos casos, la excepción la lanzas tú.

throw: lanzar tú la excepción

throw new, seguido del tipo de excepción y un mensaje, para el método en seco en esa línea, igual que lo haría un fallo de Apex. Para errores del tipo «el dato que me has dado no es válido», Apex trae IllegalArgumentException, que puedes lanzar tú. En la lección 4 crearás las tuyas, con el nombre de tu negocio.

Apex
public static Decimal parseAmount(String raw) {
    if (String.isBlank(raw)) {
        throw new IllegalArgumentException('Importe vacío');   // el método se para aquí
    }
    Decimal amount = Decimal.valueOf(raw);
    if (amount <= 0) {
        throw new IllegalArgumentException('Importe no positivo: ' + raw);
    }
    return amount;                                             // solo llega si todo cuadra
}
throw lleva new: una excepción es un objeto (Módulo 5), así que primero se crea y después se lanza.

La nutria: Como tu regla de validación, pero en un método

Una regla de validación hace exactamente esto, y yo escribí cientos: comprueba una condición y, si no se cumple, para el guardado con el mensaje que tú escribiste. throw hace lo mismo dentro de un método: para en seco y entrega tu mensaje. La diferencia es que la regla solo protege el guardado de un registro, y el método protege cualquier cálculo, lo llame quien lo llame.

Propagación: la excepción sube hasta que alguien la atrapa

Cuando parseAmount lanza, no hace falta que la capture ella. La excepción hace un viaje hacia arriba (se llama ): sale del método que la lanzó y aparece en la línea que lo llamó. Si esa línea está dentro de un try con un catch del tipo adecuado, ahí se atrapa. Si no, sigue subiendo al método de más arriba, y así hasta llegar a la plataforma, que para la transacción. Por eso parseAmount puede ser pequeño y honesto: valida, lanza si algo no cuadra, y deja la decisión a quien lo llama.

LA PILA DE LLAMADAS · ARRIBA, QUIEN LLAMA

  1. Plataforma (la transacción)
  2. for (String raw : rawAmounts) { … }
  3. RenewalRow.parseAmount(raw)throw new IllegalArgumentException(…)

Paso 1 de 4

parseAmount recibe '' y lanza. Elige dónde está el catch y pulsa Reproducir.

Elige dónde está el catch y mira hasta dónde sube la excepción.
Apex
for (String raw : rawAmounts) {
    try {
        Decimal amount = RenewalRow.parseAmount(raw);   // puede lanzar: la excepción sube hasta aquí
        imported++;                                      // solo si parseAmount no lanzó nada
    } catch (IllegalArgumentException e) {
        rejected++;
        System.debug('Rechazado: ' + e.getMessage());
    }
}
El método lanza y el bucle captura. Una TypeException de Decimal.valueOf dentro de parseAmount también subiría hasta aquí: no hace falta declararla en ningún sitio.

La nutria: ¿Y por qué no un Flow? Porque un subflow no sabe «lanzar»

En la versión con clics sacamos la validación de cada fila a un subflow, y ahí vimos el problema: un subflow que valida datos no puede lanzar un error propio a quien lo llama. Lo habitual es devolver variables de salida (isValid, errorMessage) y cada flow que lo usa tiene que acordarse de comprobarlas; el que se olvida, sigue adelante con el dato malo. (Simplificación: los flows desencadenados por registro sí tienen el elemento Custom Error para parar un guardado con tu mensaje, pero eso no sirve para devolverle un fallo a otro flow.) Con throw, quien llama no puede olvidarse: o captura la excepción, o la transacción se para.