C贸mo hacer excepciones personalizadas en Java

    Visi贸n general

    En este art铆culo, cubriremos el proceso de creaci贸n de excepciones personalizadas marcadas y no marcadas en Java.

    Si desea leer m谩s sobre las excepciones y el manejo de excepciones en Java, lo cubrimos en detalle en: Manejo de excepciones en Java: una gu铆a completa con las mejores y las peores pr谩cticas

    驴Por qu茅 utilizar excepciones personalizadas?

    Aunque las excepciones de Java, tal como est谩n, cubren casi todos los casos y condiciones excepcionales, su aplicaci贸n puede generar una excepci贸n personalizada espec铆fica, 煤nica para su c贸digo y l贸gica.

    A veces, necesitamos crear las nuestras para representar excepciones de l贸gica empresarial, es decir, excepciones que son espec铆ficas de nuestra l贸gica empresarial o flujo de trabajo. Por ejemplo EmailNotUniqueException, InvalidUserStateExceptionetc.

    Ayudan a los clientes de la aplicaci贸n a comprender mejor qu茅 sali贸 mal. Son particularmente 煤tiles para realizar el manejo de excepciones para las API REST, ya que diferentes restricciones de l贸gica empresarial requieren que se env铆en diferentes c贸digos de respuesta al cliente.

    Si la definici贸n de una excepci贸n personalizada no proporciona ventajas sobre el uso de una excepci贸n regular en Java, no es necesario definir excepciones personalizadas y debe limitarse a las que ya est谩n disponibles, no es necesario inventar agua caliente nuevamente.

    Excepci贸n de verificaci贸n personalizada

    Consideremos un escenario en el que queremos validar un correo electr贸nico que se pasa como argumento a un m茅todo.

    Queremos comprobar si es v谩lido o no. Ahora podr铆amos usar la funci贸n integrada de Java IllegalArgumentException, lo cual est谩 bien si solo estamos verificando una sola cosa, como si coincide con un REGEX predefinido o no.

    Pero supongamos que tambi茅n tenemos una condici贸n comercial para verificar que todos los correos electr贸nicos en nuestro sistema deben ser 煤nicos. Ahora tenemos que realizar una segunda verificaci贸n (DB / llamada de red). Por supuesto, podemos usar el mismo IllegalArgumentException, pero no quedar谩 claro cu谩l es la causa exacta: si el correo electr贸nico fall贸 en la validaci贸n de REGEX o si el correo electr贸nico ya existe en la base de datos.

    Creemos una excepci贸n personalizada para manejar esta situaci贸n. Para crear una excepci贸n, como cualquier otra excepci贸n, tenemos que extender el java.lang.Exception clase:

    public class EmailNotUniqueException extends Exception {
    
        public EmailNotUniqueException(String message) {
            super(message);
        }
    }
    
    

    Tenga en cuenta que proporcionamos un constructor que toma un String mensaje de error y llama al constructor de la clase principal. Ahora bien, esto no es obligatorio, pero es una pr谩ctica com煤n tener alg煤n tipo de detalles sobre la excepci贸n que ocurri贸.

    Llamando super(message), inicializamos el mensaje de error de la excepci贸n y la clase base se encarga de configurar el mensaje personalizado, de acuerdo con el message.

    Ahora, usemos esta excepci贸n personalizada en nuestro c贸digo. Como estamos definiendo un m茅todo que puede lanzar una excepci贸n en la capa de servicio, lo marcaremos con el throws palabra clave.

    Si el correo electr贸nico de entrada ya existe en nuestra base de datos (en este caso una lista), throw nuestra excepci贸n personalizada:

    public class RegistrationService {  
        List<String> registeredEmails = Arrays.asList("[email聽protected]", "[email聽protected]");
    
        public void validateEmail(String email) throws EmailNotUniqueException {
            if (registeredEmails.contains(email)) {
                throw new EmailNotUniqueException("Email Already Registered");
            }
        }
    }
    
    

    Ahora escribamos un cliente para nuestro servicio. Dado que es una excepci贸n marcada, tenemos que cumplir con la regla de manejar o declarar. En el ejemplo anterior, decidimos “manejarlo”:

    public class RegistrationServiceClient {  
        public static void main(String[] args) {
            RegistrationService service = new RegistrationService();
            try {
                service.validateEmail("[email聽protected]");
            } catch (EmailNotUniqueException e) {
                // logging and handling the situation
            }
        }
    }
    
    

    Ejecutar este fragmento de c贸digo producir谩:

    mynotes.custom.checked.exception.EmailNotUniqueException: Email Already Registered  
        at mynotes.custom.checked.exception.RegistrationService.validateEmail(RegistrationService.java:12)
        at mynotes.custom.checked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:9)
    
    

    Nota: El proceso de manejo de excepciones se omite por brevedad, pero es un proceso importante, no obstante.

    Excepci贸n personalizada sin marcar

    Esto funciona perfectamente bien, pero nuestro c贸digo se ha vuelto un poco complicado. Adem谩s, estamos obligando al cliente a capturar nuestra excepci贸n en un try-catch bloquear. En algunos casos, esto puede obligar a los desarrolladores a escribir c贸digo repetitivo.

    En este caso, puede resultar beneficioso crear una excepci贸n de tiempo de ejecuci贸n personalizada. Para crear una excepci贸n personalizada sin marcar, tenemos que extender el java.lang.RuntimeException clase.

    Consideremos la situaci贸n en la que tenemos que verificar si el correo electr贸nico tiene un nombre de dominio v谩lido o no:

    public class DomainNotValidException extends RuntimeException {
    
        public DomainNotValidException(String message) {
            super(message);
        }
    }
    
    

    Ahora util铆celo en nuestro servicio:

    public class RegistrationService {
    
        public void validateEmail(String email) {
            if (!isDomainValid(email)) {
                throw new DomainNotValidException("Invalid domain");
            }
        }
    
        private boolean isDomainValid(String email) {
            List<String> validDomains = Arrays.asList("gmail.com", "yahoo.com", "outlook.com");
            if (validDomains.contains(email.substring(email.indexOf("@") + 1))) {
                return true;
            }
            return false;
        }
    }
    
    

    Tenga en cuenta que no tuvimos que usar el throws palabras clave en la firma del m茅todo ya que es una excepci贸n sin marcar.

    Ahora escribamos un cliente para nuestro servicio. No tenemos que usar un try-catch bloquear esta vez:

    public class RegistrationServiceClient {
    
        public static void main(String[] args) {
            RegistrationService service = new RegistrationService();
            service.validateEmail("[email聽protected]");
        }
    }
    
    

    Ejecutar este fragmento de c贸digo producir谩:

    Exception in thread "main" mynotes.custom.unchecked.exception.DomainNotValidException: Invalid domain  
        at mynotes.custom.unchecked.exception.RegistrationService.validateEmail(RegistrationService.java:10)
        at mynotes.custom.unchecked.exception.RegistrationServiceClient.main(RegistrationServiceClient.java:7)
    
    

    Nota: Por supuesto, puede rodear su c贸digo con un try-catch block para capturar la excepci贸n que surge, pero ahora el compilador no lo fuerza.

    Relanzamiento de una excepci贸n envuelta dentro de una excepci贸n personalizada

    A veces necesitamos detectar una excepci贸n y volver a lanzarla agregando algunos detalles m谩s. Esto suele ser com煤n si tiene varios c贸digos de error definidos en su aplicaci贸n que deben registrarse o devolverse al cliente en caso de esa excepci贸n en particular.

    Suponga que su aplicaci贸n tiene un est谩ndar ErrorCodes clase:

    public enum ErrorCodes {  
        VALIDATION_PARSE_ERROR(422);
    
        private int code;
    
        ErrorCodes(int code) {
            this.code = code;
        }
    
        public int getCode() {
            return code;
        }
    }
    
    

    Creemos nuestra excepci贸n personalizada:

    public class InvalidCurrencyDataException extends RuntimeException {
    
        private Integer errorCode;
    
        public InvalidCurrencyDataException(String message) {
            super(message);
        }
    
        public InvalidCurrencyDataException(String message, Throwable cause) {
            super(message, cause);
        }
    
        public InvalidCurrencyDataException(String message, Throwable cause, ErrorCodes errorCode) {
            super(message, cause);
            this.errorCode = errorCode.getCode();
        }
    
        public Integer getErrorCode() {
            return errorCode;
        }
    }
    
    

    Observe que tenemos varios constructores y deje que la clase de servicio decida cu谩l usar. Dado que estamos volviendo a generar la excepci贸n, siempre es una buena pr谩ctica capturar la causa ra铆z de la excepci贸n, por lo tanto, Throwable argumento que se puede pasar` al constructor de la clase padre.

    Tambi茅n estamos capturando el c贸digo de error en uno de los constructores y configuramos el errorCode dentro de la propia excepci贸n. los errorCode puede ser utilizado por el cliente para el registro o cualquier otro prop贸sito. Esto ayuda en un est谩ndar m谩s centralizado para el manejo de excepciones.

    Escribamos nuestra clase de servicio:

    public class CurrencyService {  
        public String convertDollarsToEuros(String value) {
            try {
            int x = Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw new InvalidCurrencyDataException("Invalid data", e, ErrorCodes.VALIDATION_PARSE_ERROR);
            }
            return value;
        }
    }
    
    

    Entonces atrapamos el est谩ndar NumberFormatExceptiony tiramos el nuestro InvalidCurrencyDataException. Pasamos la excepci贸n principal a nuestra excepci贸n personalizada para que no perdamos la causa ra铆z por la que ocurrieron.

    Escribamos un cliente de prueba para este servicio:

    public class CurrencyClient {  
        public static void main(String[] args) {
            CurrencyService service = new CurrencyService();
        service.convertDollarsToEuros("asd");
        }
    }
    
    

    Salida:

    Exception in thread "main" mynotes.custom.unchecked.exception.InvalidCurrencyDataException: Invalid data  
        at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:10)
        at mynotes.custom.unchecked.exception.CurrencyClient.main(CurrencyClient.java:8)
    Caused by: java.lang.NumberFormatException: For input string: "asd"  
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.parseInt(Integer.java:615)
        at mynotes.custom.unchecked.exception.CurrencyService.convertDollarsToEuro(CurrencyService.java:8)
        ... 1 more
    
    

    Como puede ver, tenemos un buen seguimiento de la pila de la excepci贸n que podr铆a ser 煤til para fines de depuraci贸n.

    Pr谩cticas recomendadas para excepciones personalizadas

    • Adhi茅rase a la convenci贸n de nomenclatura general en todo el ecosistema de Java: todos los nombres de clase de excepci贸n personalizados deben terminar con “Exception”
    • Evite hacer excepciones personalizadas si las excepciones est谩ndar del propio JDK pueden cumplir el prop贸sito. En la mayor铆a de los casos, no es necesario definir excepciones personalizadas.
    • Prefiera las excepciones en tiempo de ejecuci贸n a las excepciones marcadas. Frameworks como Spring han envuelto todas las excepciones marcadas en las excepciones en tiempo de ejecuci贸n, por lo que no han obligado al cliente a escribir c贸digo repetitivo que no quieren o no necesitan.
    • Proporcione muchos constructores sobrecargados en funci贸n de c贸mo se lanzar铆a la excepci贸n personalizada. Si se est谩 utilizando para volver a generar una excepci贸n existente, definitivamente proporcione un constructor que establezca la causa.

    Conclusi贸n

    Las excepciones personalizadas se utilizan para requisitos y l贸gica empresarial espec铆ficos. En este art铆culo, discutimos su necesidad y cubrimos su uso.

    El c贸digo de los ejemplos utilizados en este art铆culo se puede encontrar en Github.

    Etiquetas:

    Deja una respuesta

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