
Nunca he tenido ninguna opinión particular sobre el manejo de errores. Si comencé a trabajar con el código existente, continué realizando la tarea en la que trabajó el autor de la fuente; Si escribí el código desde cero, hice lo que me pareció correcto.
Pero recientemente, me encontré con un problema, un error que se manifestó debido a un error "silencioso" en el código. Me di cuenta de que hay algo en lo que reflexionar. Tal vez no pueda cambiar la forma en que manejo los errores en toda la base de código en la que estoy trabajando, pero algo definitivamente se puede optimizar.
Le recordamos: para todos los lectores de "Habr": un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox con el código de promoción "Habr".
Skillbox recomienda: El curso educativo en línea "Profession Java-developer" .
No siempre vale la pena cortar el hombro
El primer paso en el manejo de errores debe ser comprender cuando "error" no es "error". Por supuesto, todo esto depende de la lógica empresarial de su aplicación, pero en general, algunos errores son explícitos y se pueden solucionar sin problemas.
- ¿Tiene un rango de fechas donde el "antes" es anterior al "de"? Reordenar
- ¿Tiene un número de teléfono que comienza con + o contiene un guión donde no espera que aparezcan caracteres especiales? Eliminarlos.
- ¿Problema de recolección nula? Asegúrese de inicializar esto antes del acceso (usando la inicialización diferida o el constructor).
No interrumpa la ejecución del código debido a errores que puede corregir y, por supuesto, no interfiera con sus acciones a los usuarios de su servicio o aplicación. Si puede comprender el problema y resolverlo sobre la marcha, simplemente hágalo.

Devolver nulos u otros números mágicos
Valores cero, –1 donde se espera un número positivo y otros valores mágicos de retorno: todo esto es producto del demonio, que transfiere la responsabilidad de verificar los errores a la función de llamada.
Con ellos, su código estará lleno de bloques que harán que la lógica de la aplicación no sea clara:
return_value = possibly_return_a_magic_value() if return_value < 0: handle_error() else: do_something() other_return_value = possibly_nullable_value() if other_return_value is None: handle_null_value() else: do_some_other_thing()
Bueno, o más simple, pero también malo:
var item = returnSomethingWhichCouldBeNull(); var result = item?.Property?.MaybeExists; if (result.HasValue) { DoSomething(); }
Pasar nulo a los métodos también es un problema, aunque en el código de algunos desarrolladores puede encontrar lugares donde los métodos comienzan con varias líneas de validación de entrada. Pero, de hecho, todo esto no es necesario. La mayoría de los lenguajes modernos proporcionan no una, sino varias herramientas a la vez, que le permiten indicar claramente lo que espera. También omiten las comprobaciones inútiles, por ejemplo, definiendo parámetros como distintos de cero o con el decorador correspondiente.
Códigos de error
Este es el mismo problema que con valores nulos y otros valores similares cuando se agrega una complicación de código innecesaria.
Por ejemplo, puede usar esta construcción para devolver un código de error:
int errorCode; var result = getSomething(out errorCode); if (errorCode != 0) { doSomethingWithResult(result); }
El resultado se puede mostrar de la siguiente manera:
public class Result<T> { public T Item { get; set; }
Este no es realmente el mejor código. Resulta que el artículo aún puede estar vacío. De hecho, no hay garantía (aparte de un acuerdo) de que cuando el resultado no contenga un error, pueda acceder de forma segura al Artículo.
Después de terminar con todo este procesamiento, aún tiene que convertir el código de error en un mensaje de error y hacer algo con él. A veces sucede que, debido a la excesiva complejidad del código, este mensaje en sí no se muestra como debería.
Existe un problema aún más grave: si usted u otra persona cambia la implementación interna para manejar un nuevo estado no válido con un nuevo código de error, entonces toda la estructura dejará de funcionar.
Si no funcionó la primera vez, intente nuevamente
Antes de continuar, vale la pena enfatizar que una falla "silenciosa" del programa es mala. Un problema inesperado puede ocurrir en el momento más inoportuno, por ejemplo, el fin de semana, cuando no puede solucionar todo rápidamente.

Si lees
Código limpio , entonces probablemente te estés preguntando ¿por qué no simplemente lanzar una excepción? Si no, entonces lo más probable es que pienses que las excepciones son la raíz del mal. Yo también pensaba eso, pero ahora pienso un poco diferente.
Un punto interesante, al menos para mí, es que la implementación predeterminada para el nuevo método en C # es lanzar una excepción NotImplementedException, mientras que el valor predeterminado para el nuevo método en Python es "pasar".
Como resultado, a menudo ingresamos la configuración de "error silencioso" para Python. Me pregunto cuántos desarrolladores pasaron mucho tiempo para comprender lo que está sucediendo y por qué el programa no funciona. Pero al final, después de muchas horas, descubrieron que olvidaron implementar el método de marcador de posición.
Pero mira esto:
public MyDataObject UpdateSomething(MyDataObject toUpdate) { if (_dbConnection == null) { throw new DbConnectionError(); } try { var newVersion = _dbConnection.Update(toUpdate); if (newVersion == null) { return null; } MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { throw new DbConnectionError(); } catch (MyDataObjectUnhappyException dou) { throw new MalformedDataException(); } catch (Exception ex) { throw new UnknownErrorException(); } }
Por supuesto, solo las excepciones no lo protegerán de la creación de código ilegible y no administrado. Debe aplicar excepciones como una estrategia bien pensada y bien equilibrada. De lo contrario, si el proyecto es demasiado grande, su aplicación puede estar en un estado inconsistente. Por el contrario, si el proyecto es demasiado pequeño, obtendrá el caos.
Para evitar que esto suceda, le aconsejo que tenga esto en cuenta:
Consistencia sobre todo. Siempre debe asegurarse de que la aplicación sea coherente. Si esto significa que tiene que ajustar cada par de líneas con un bloque try / catch, simplemente oculte todo en otra función.
def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource()
La consolidación de errores es necesaria. Es bueno si proporciona diferentes tipos de manejo de diferentes tipos de errores. Sin embargo, esto es para usted, no para los usuarios. Para ellos, lanza la única excepción para que los usuarios simplemente sepan que algo salió mal. Los detalles son su área de responsabilidad.
public MyDataObject UpdateSomething(MyDataObject toUpdate) { try { var newVersion = _dbConnection.Update(toUpdate); MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { HandleDbConnectionClosed(); throw new UpdateMyDataObjectException(); } catch (MyDataObjectUnhappyException dou) { RollbackVersion(); throw new UpdateMyDataObjectException(); } catch (Exception ex) { throw new UpdateMyDataObjectException(); } }
Vale la pena atrapar excepciones de inmediato. Se interceptan mejor en el nivel más bajo. Mantenerse constante y ocultar los detalles vale la pena como se describe anteriormente. El manejo de errores debe dejarse en el nivel superior de la aplicación. Si todo se hace correctamente, puede separar el flujo lógico de la aplicación del flujo de procesamiento de errores. Esto le permitirá escribir código claro y legible.
def my_api(): try: item = get_something_from_the_db() new_version = do_something_to_item(item) return new_version except Exception as ex: handle_high_level_exception(ex)
Eso es todo por ahora, y si desea analizar el tema de los errores y su procesamiento, bienvenido.
Skillbox recomienda: