Consideraciones sobre la creación de Excepciones
Reflexiones sobre cuándo y cómo crear Exceptions en Java. Discute los costos de fillInStackTrace y estrategias de manejo de excepciones.
Introducción
Durante el desarrollo, es necesario considerar cuidadosamente cuándo y cómo crear excepciones (
Exception).
Generalmente, crear excepciones indiscriminadamente no es una buena idea.
Por el contrario, no crear excepciones cuando son necesarias puede dificultar la depuración y el seguimiento de errores.
Consideraciones principales
1. Evitar la creación indiscriminada de Exceptions
- En código donde no necesitas
throwun error, piensa dos veces antes de crear unaexception. - El costo de crear una
exceptionvaría según la situación, pero generalmente es alto.
2. Comprender los costos de creación de Exception
- Cuando ocurre una excepción, surgen costos al recorrer la pila de llamadas de excepción para encontrar un método que pueda manejar la
exception. Exceptiones una implementación deThrowable- Se generan costos adicionales al recopilar información de la pila de llamadas a través del método
fillInStackTrace() - El proceso de recorrer la pila de llamadas para recopilar nombres de clases, nombres de métodos, números de línea, etc. para crear un
stacktracetambién contribuye al aumento de costos
-> Ten en cuenta que el costo real depende de la profundidad y tamaño del stack trace
- Se generan costos adicionales al recopilar información de la pila de llamadas a través del método
3. Considera si realmente necesitas una Exception
Como ejemplo algo extremo, en el código siguiente que verifica memoria, la verificación del estado actual de memoria tiene etapas:
Situación de advertencia(log.warn()) - GC puede liberar suficiente memoriaSituación severa(log.error()) - La memoria no se ha liberado suficientemente debido a la carga, el servidor podría fallar
En una situación donde solo un warn es suficiente, ¿realmente necesitas crear una exception?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Ejemplo inapropiado
public void checkMemoryUsage() {
long usedMemory = totalMemory - freeMemory;
if (usedMemory > MEMORY_THRESHOLD) {
Exception memoryException = new Exception("¡Se requiere atención de memoria!");
throw memoryException;
}
}
// Enfoque mejorado
public void checkMemoryUsage() {
long usedMemory = totalMemory - freeMemory;
if (usedMemory > MEMORY_THRESHOLD) {
log.warn("Se requiere atención de memoria. Memoria usada: {}", usedMemory);
// Puede enviar alertas vía webhook, etc.
}
}
Mejoras
Entonces, si se necesita crear una excepción pero se espera que el costo sea alto, ¿cómo podemos mejorarlo?
1. Sobreescribir fillInStackTrace
- Sobreescribir fillInStackTrace para eliminar el costo de crear un stacktrace
1
2
3
4
5
6
public class OptimizedException extends Exception {
@Override
public Throwable fillInStackTrace() {
return this; // Evitar el costo de generación de stack trace
}
}
2. Cachear Exceptions que ocurren frecuentemente
1
2
3
public class ExceptionCache {
public static final CommonException COMMON_EXCEPTION = new CommonException("Situación de excepción común que ocurre frecuentemente");
}
Advertencias
- OptimizedException o excepciones cacheadas solo deben usarse para excepciones donde no se necesitan stack traces
- Excepciones que ocurren como parte del flujo normal de la aplicación - como excepciones de etapa de validación
- Excepciones que ocurren repetidamente: Excepciones que ocurren frecuentemente en la misma situación
- Para situaciones donde se necesita seguimiento real de problemas, es mejor usar Exceptions regulares.
- Las excepciones optimizadas por rendimiento podrían causar confusión durante la revisión de código o depuración.
Lo realmente importante
- Es más importante describir claramente cuál es el problema al crear excepciones (lo mismo aplica para
logging) - El código que simplemente termina con
throw new Exception(e.getMessage())solo dificulta el seguimiento. - Ya que serás tú quien rastree el problema cuando ocurra… es importante escribir claramente.
- Además, antes de crear una
customException, considera si puedes expresarlo claramente usando las subclases estándar deRuntimeExceptionproporcionadas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Código problemático
try {
categoryService.update(id, requestDto));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
// Código mejorado
try {
log.info("[@{}] Solicitud de actualización de categoría para id={}: {}", name, id, requestDto);
categoryService.update(id, requestDto));
} catch (Exception e) {
log.error("[@{}] Ocurrió un error al actualizar categoría con id={}", name, id, e);
// Si fue un argumento incorrecto, IllegalArgumentException podría ser mejor
throw new IllegalArgumentException("[@{}] Ocurrió un error al actualizar categoría con id={}", name, id, e));
}