paint-brush
El fracaso es necesario, así que acéptelo: comprensión de las estrategias de software a prueba de fallos y rápidopor@shai.almog
461 lecturas
461 lecturas

El fracaso es necesario, así que acéptelo: comprensión de las estrategias de software a prueba de fallos y rápido

por Shai Almog7m2024/05/07
Read on Terminal Reader

Demasiado Largo; Para Leer

Descubra cómo aceptar el error puede mejorar la calidad de su aplicación, lo que lleva a una detección temprana de errores, un manejo sólido de errores y una mejor estabilidad general.
featured image - El fracaso es necesario, así que acéptelo: comprensión de las estrategias de software a prueba de fallos y rápido
Shai Almog HackerNoon profile picture
0-item

Los fallos en los sistemas de software son inevitables. La forma en que se manejan estas fallas puede afectar significativamente el rendimiento del sistema, la confiabilidad y el resultado final de la empresa. En esta publicación, quiero discutir las ventajas del fracaso. Por qué debería buscar el fracaso, por qué el fracaso es bueno y por qué evitarlo puede reducir la confiabilidad de su aplicación. Comenzaremos con la discusión sobre falla rápida versus falla segura; Esto nos llevará a la segunda discusión sobre los fracasos en general.

Como nota al margen, si te gusta el contenido de esta y otras publicaciones de esta serie, consulta mi libro de depuración , que cubre este tema. Si tienes amigos que están aprendiendo a codificar, agradecería una referencia a miLibro de conceptos básicos de Java. Si quieres volver a Java después de un tiempo, consulta mi Libro Java 8 a 21. .

Fallar rapido

Los sistemas a prueba de fallas están diseñados para dejar de funcionar inmediatamente al encontrar una condición inesperada. Esta falla inmediata ayuda a detectar errores tempranamente, lo que hace que la depuración sea más sencilla.


El enfoque a prueba de fallos garantiza que los errores se detecten de inmediato. Por ejemplo, en el mundo de los lenguajes de programación, Java encarna este enfoque al producir una NullPointerException instantáneamente cuando encuentra un valor null , deteniendo el sistema y aclarando el error. Esta respuesta inmediata ayuda a los desarrolladores a identificar y abordar los problemas rápidamente, evitando que se agraven.


Al detectar y detener los errores tempranamente, los sistemas rápidos reducen el riesgo de fallas en cascada, donde un error conduce a otros. Esto hace que sea más fácil contener y resolver problemas antes de que se propaguen por el sistema, preservando la estabilidad general.


Es fácil escribir pruebas unitarias y de integración para sistemas rápidos. Esta ventaja es aún más pronunciada cuando necesitamos comprender el fallo de la prueba. Los sistemas a prueba de fallas generalmente apuntan directamente al problema en el seguimiento de la pila de errores.


Sin embargo, los sistemas rápidos conllevan sus propios riesgos, particularmente en entornos de producción:


  • Interrupciones en la producción: si un error llega a la producción, puede causar interrupciones inmediatas y significativas, lo que podría afectar tanto el rendimiento del sistema como las operaciones de la empresa.
  • Apetito al riesgo: los sistemas rápidos requieren un nivel de tolerancia al riesgo tanto por parte de los ingenieros como de los ejecutivos. Deben estar preparados para manejar y abordar las fallas rápidamente, a menudo equilibrando esto con los posibles impactos comerciales.

A prueba de fallos

Los sistemas a prueba de fallos adoptan un enfoque diferente, con el objetivo de recuperarse y continuar incluso ante condiciones inesperadas. Esto los hace particularmente adecuados para entornos inciertos o volátiles.

Los microservicios son un excelente ejemplo de sistemas a prueba de fallas, que adoptan la resiliencia a través de su arquitectura. Los disyuntores, tanto físicos como basados en software, desconectan la funcionalidad de fallas para evitar fallas en cascada, lo que ayuda a que el sistema continúe funcionando.


Los sistemas a prueba de fallos garantizan que los sistemas puedan sobrevivir incluso en entornos de producción hostiles, lo que reduce el riesgo de fallos catastróficos. Esto los hace particularmente adecuados para aplicaciones de misión crítica, como dispositivos de hardware o sistemas aeroespaciales, donde la recuperación fluida de errores es crucial.


Sin embargo, los sistemas a prueba de fallos tienen desventajas:


  • Errores ocultos: al intentar recuperarse de los errores, los sistemas a prueba de fallos pueden retrasar la detección de problemas, haciéndolos más difíciles de rastrear y potencialmente provocando fallos en cascada más graves.
  • Desafíos de depuración: esta naturaleza retrasada de los errores puede complicar la depuración, lo que requiere más tiempo y esfuerzo para encontrar y resolver problemas.

Elegir entre a prueba de fallos y a prueba de fallos

Es un desafío determinar qué enfoque es mejor, ya que ambos tienen sus ventajas. Los sistemas a prueba de fallas ofrecen depuración inmediata, menor riesgo de fallas en cascada y detección y resolución de errores más rápida. Esto ayuda a detectar y solucionar problemas a tiempo, evitando que se propaguen.

Los sistemas a prueba de fallas manejan los errores con elegancia, lo que los hace más adecuados para sistemas de misión crítica y entornos volátiles, donde las fallas catastróficas pueden ser devastadoras.

Equilibrando ambos

Para aprovechar las fortalezas de cada enfoque, una estrategia equilibrada puede resultar eficaz:


  • Fail-Fast para servicios locales: al invocar servicios locales como bases de datos, Fail-Fast puede detectar errores temprano, evitando fallas en cascada.
  • A prueba de fallas para recursos remotos: cuando se depende de recursos remotos, como servicios web externos, la seguridad contra fallas puede evitar interrupciones causadas por fallas externas.

Un enfoque equilibrado también requiere una implementación clara y consistente en todos los procesos de codificación, revisiones, herramientas y pruebas, asegurando que se integre sin problemas. La falla rápida puede integrarse bien con la orquestación y la observabilidad. Efectivamente, esto mueve el aspecto a prueba de fallas a una capa diferente de OPS en lugar de a la capa de desarrollador.

Comportamiento de capa consistente

Aquí es donde las cosas se ponen interesantes. No se trata de elegir entre lo seguro y lo rápido. Se trata de elegir la capa adecuada para ellos. Por ejemplo, si un error se maneja en una capa profunda utilizando un enfoque a prueba de fallos, no se notará. Esto podría estar bien, pero si ese error tiene un impacto adverso (rendimiento, datos basura, corrupción, seguridad, etc.), tendremos un problema más adelante y no tendremos ni idea.

La solución correcta es manejar todos los errores en una sola capa; en los sistemas modernos, la capa superior es la capa OPS y tiene más sentido. Puede informar el error a los ingenieros más calificados para solucionarlo. Pero también pueden proporcionar una mitigación inmediata, como reiniciar un servicio, asignar recursos adicionales o revertir una versión.

Los reintentos no son a prueba de fallos

Recientemente, estuve en una conferencia donde los oradores enumeraron su arquitectura de nube actualizada. Eligieron tomar un atajo hacia los microservicios mediante el uso de un marco que les permita volver a intentarlo en caso de falla. Lamentablemente el fracaso no se comporta como nos gustaría. No se puede eliminar por completo únicamente mediante pruebas. Reintentar no es a prueba de fallos. De hecho, puede significar una catástrofe.


Probaron su sistema y "funciona", incluso en producción. Pero supongamos que ocurre una situación catastrófica, su mecanismo de reintento puede operar como un ataque de denegación de servicio contra sus propios servidores. La cantidad de formas en que arquitecturas ad-hoc como ésta pueden fallar es alucinante.


Esto es especialmente importante una vez que redefinimos los fracasos.

Redefiniendo el fracaso

Las fallas en los sistemas de software no se deben solo a fallas. Un accidente puede verse como una falla simple e inmediata, pero hay cuestiones más complejas a considerar. De hecho, los accidentes en la era de los contenedores son probablemente los mayores fracasos. Un sistema se reinicia sin problemas y sin apenas interrupción.

Corrupción de datos

La corrupción de datos es mucho más grave e insidiosa que una caída. Lleva consigo consecuencias a largo plazo. Los datos corruptos pueden generar problemas de seguridad y confiabilidad que son difíciles de solucionar, lo que requiere una revisión exhaustiva y datos potencialmente irrecuperables.


La computación en la nube ha llevado a técnicas de programación defensiva, como disyuntores y reintentos, enfatizando pruebas y registros integrales para detectar y manejar fallas con elegancia. En cierto modo, este entorno nos hizo retroceder en términos de calidad.


Un sistema rápido a nivel de datos podría evitar que esto suceda. Abordar un error va más allá de una simple solución. Requiere comprender su causa raíz y evitar que vuelva a ocurrir, ampliando el registro, las pruebas y las mejoras de procesos integrales. Esto garantiza que el error se solucione por completo, lo que reduce las posibilidades de que vuelva a ocurrir.

No arregles el error

Si se trata de un error en producción, probablemente debería revertirlo si no puede revertir la producción instantáneamente. Esto siempre debería ser posible y, si no lo es, es algo en lo que deberías trabajar.


Las fallas deben entenderse completamente antes de emprender una solución. En mis propias empresas muchas veces me saltaba ese paso por presión, en una pequeña startup eso es perdonable. En las empresas más grandes, debemos comprender la causa raíz. Es esencial una cultura de información sobre errores y problemas de producción. La solución también debería incluir la mitigación del proceso que impida que problemas similares lleguen a producción.

Fallo de depuración

Los sistemas rápidos son mucho más fáciles de depurar. Tienen una arquitectura inherentemente más simple y es más fácil identificar un problema en un área específica. Es fundamental lanzar excepciones incluso para infracciones menores (por ejemplo, validaciones). Esto evita tipos de errores en cascada que prevalecen en sistemas flexibles.

Esto debería reforzarse aún más mediante pruebas unitarias que verifiquen los límites que definimos y verifiquen que se generen las excepciones adecuadas. Se deben evitar los reintentos en el código, ya que dificultan excepcionalmente la depuración y su lugar adecuado es en la capa OPS. Para facilitar esto aún más, los tiempos de espera deben ser cortos de forma predeterminada.

Evitar fallas en cascada

El fracaso no es algo que podamos evitar, predecir o contrastar por completo. Lo único que podemos hacer es suavizar el golpe cuando se produce un fallo. A menudo, este "ablandamiento" se logra mediante el uso de pruebas de larga duración destinadas a replicar condiciones extremas tanto como sea posible con el objetivo de encontrar los puntos débiles de nuestras aplicaciones. Esto rara vez es suficiente. Los sistemas robustos a menudo necesitan revisar estas pruebas en función de fallos reales de producción.

Un gran ejemplo de seguridad sería un caché de respuestas REST que nos permita seguir trabajando incluso cuando un servicio no funciona. Desafortunadamente, esto puede llevar a problemas de nicho complejos, como el envenenamiento de la caché o una situación en la que un usuario prohibido todavía tenía acceso debido a la caché.

Híbrido en producción

La seguridad contra fallas se aplica mejor solo en producción/puesta en escena y en la capa OPS. Esto reduce la cantidad de cambios entre producción y desarrollo; queremos que sean lo más similares posible, pero sigue siendo un cambio que puede afectar negativamente a la producción. Sin embargo, los beneficios son enormes, ya que la observabilidad puede obtener una imagen clara de las fallas del sistema.


La discusión aquí está un poco influida por mi experiencia más reciente en la construcción de arquitecturas de nube observables. Sin embargo, el mismo principio se aplica a cualquier tipo de software, ya sea integrado o en la nube. En tales casos, a menudo optamos por implementar la seguridad contra fallas en el código; en este caso, sugeriría implementarlo de manera consistente y consciente en una capa específica.


También hay un caso especial de bibliotecas/marcos que a menudo proporcionan comportamientos inconsistentes y mal documentados en estas situaciones. Yo mismo soy culpable de tal inconsistencia en algunos de mis trabajos. Es un error fácil de cometer.

Palabra final

Esta es mi última publicación sobre la serie de teoría de depuración que forma parte de mi libro/curso sobre depuración. A menudo pensamos en la depuración como la acción que realizamos cuando algo falla. No lo es. La depuración comienza en el momento en que escribimos la primera línea de código. Tomamos decisiones que afectarán el proceso de depuración a medida que codificamos; a menudo simplemente no somos conscientes de estas decisiones hasta que fallamos.


Espero que esta publicación y esta serie te ayuden a escribir código preparado para lo desconocido. La depuración, por su naturaleza, se ocupa de lo inesperado. Las pruebas no pueden ayudar. Pero, como ilustré en mis publicaciones anteriores, hay muchas prácticas sencillas que podemos llevar a cabo y que facilitarían la preparación. Este no es un proceso único, es un proceso iterativo que requiere una reevaluación de las decisiones tomadas a medida que nos encontramos con el fracaso.