¿Cuál es la mejor manera de preparar su diseño y código para esos errores "desconocidos desconocidos" desde el primer día?


8

Me pregunto, ¿hay algún método o técnica práctica o incluso trucos para evitar esos errores "desconocidos desconocidos", especialmente aquellos desagradables y aleatorios que a menudo surgen en los últimos minutos o al menos para mantener estas cosas en un nivel mínimo. Pregunto esto porque cuando trabajas en una nueva plataforma o usas cierta tecnología nueva por primera vez, ¿cómo justificas que tu diseño y código sean lo suficientemente robustos? ¿O estas cosas solo se pueden aprender con el tiempo y los errores?

(Uso C ++ para la mayor parte de mi tiempo de trabajo)

¡Gracias!

Respuestas:


5

Hace casi veinte años, obtuve mucha información sobre esto del excelente libro de David Thielen "Sin errores: entrega de código libre de errores en C y C ++", que ahora está disponible como PDF gratuito .

Me enseñó dos grandes ideas ...

Los errores no vienen de la nada. Todos los programadores nos sentamos y los escribimos en nuestro código con nuestros propios dedos.

"Bug" connota que alguna agencia externa decidió infestar su programa con errores y que si vive una vida limpia y sacrifica pequeños animales peludos al pie de su computadora, desaparecerán ... Este concepto es importante porque colorea su enfoque para depurar su código. Si ve los errores como "errores", espera que no se encuentre ninguno. (Esperas que el buen hada haya venido, rociado con polvo de duendecillo y los insectos se hayan ido).

Los errores no deberían llamarse errores, deberían llamarse Fuck-Ups masivos [MFU] ... Las MFU existen porque los programas están escritos por personas y las personas cometen errores ... Usted escribirá MFU. Se sentará y, con total malicia de previsión, colocará MFU en su código. Piénselo: sabe que usted es quien está poniendo los errores allí. Entonces, si te sientas a codificar, insertarás algunos errores.

Como el destino ineludible de todos los programadores es escribir errores, necesito codificar defensivamente, incluidas las cosas que saltarán, gritarán y agitarán banderas rojas cuando detecten un error.

Después de haber sido escrito a principios de los 90, los detalles sobre esto en el libro de Thielen son bastante anticuados. Por ejemplo, en Linux y Mac OS X, ya no necesita escribir su propio contenedor para el nuevo operador de C ++; puedes usar valgrind para eso.

Pero hay algunas cosas que hago habitualmente para C / C ++ / ObjC:

  1. Cuando pueda razonablemente, active la opción "Advertencias son errores" del compilador y corríjalos todos. (Mantengo un proyecto heredado donde arreglarlos todos a la vez llevaría semanas, por lo que solo arreglo un archivo cada pocas semanas, y en unos pocos años, puedo activar esa opción).
  2. Use una herramienta de análisis de código estático, como PC-Lint de Gimpel o la muy ingeniosa ahora integrada en Xcode de Apple. La cobertura es aún mejor, pero el costo es para grandes corporaciones, no para simples mortales.
  3. Use herramientas de análisis dinámico, como valgrind, para verificar problemas de memoria, fugas, etc.
  4. Como dice Thielen (y aún vale la pena leer el capítulo): Afirma el mundo . Por supuesto, nadie más que un idiota llamará a su función con un puntero nulo, y eso significa que alguien, en algún lugar, es un idiota que hará exactamente eso. Incluso podrías ser tú en tres años cuando lo que estabas haciendo hoy se ha empañado. Entonces, solo agregue una afirmación al comienzo de la función para validar ese argumento de puntero: toma tres segundos escribir y desaparece en el ejecutable de lanzamiento.
  5. En C ++, RTTI es tu amigo. Nuevamente, nadie más que un idiota llamará a su función con un puntero al tipo de objeto equivocado, lo que significa que, inevitablemente, algún idiota lo hará, y el costo para defenderse es insignificante. En el código basado en C derivado de GObject, puede hacer lo mismo con las macros de conversión dinámica defensiva.
  6. Las pruebas automatizadas de unidad y regresión ahora son una parte clave de mi repertorio. En un proyecto, son una parte integral del sistema de compilación de lanzamiento, y la compilación no se completará a menos que todos pasen.
  7. Otra parte clave es el código de registro tanto en los ejecutables de depuración como de liberación que pueden habilitarse en tiempo de ejecución mediante una variable de entorno.
  8. Escriba pruebas defensivas para que los programadores que ejecutan ejecutables de depuración no puedan ignorarlas si fallan. Los mensajes de tiempo de ejecución a la consola se pueden ignorar. El programa que falla con una afirmación no puede ser ignorado.
  9. Al diseñar, proporcione API públicas e implementaciones privadas que el código externo no puede alcanzar. De esa manera, si tiene que refactorizar, nadie depende de alguna variable mágica de estado interior o algo así. En las clases de C ++, soy un gran fanático de lo protegido y lo privado para esto. También creo que las clases proxy son geniales, pero realmente no las uso yo mismo.

Por supuesto, lo que harás para un nuevo idioma o tecnología variará en los detalles. Pero una vez que entiendes en tu corazón las nociones de que los insectos son Fuck-Ups masivos que escribiste con tus propios dedos, y tu código está bajo el asalto constante de un ejército de idiotas, contigo a la cabeza como general, estoy seguro de que Descubriré técnicas defensivas adecuadas.


14

Bueno, si sabes eso, entonces se deslizan en la categoría de "errores desconocidos conocidos" (es decir, sabes que ocurrirá algo de "esta" naturaleza). Cualquier cantidad de pruebas unitarias no las atraparán, solo son realmente útiles para casos conocidos.

La forma en que lidiamos con esto es poner un servicio de registro de errores en la aplicación en ejecución, informar a la base cuando ocurra y lidiar con él cuando surja. De lo contrario, puede pasar años y aún así no cubrir nada ... en algún momento solo espera a ver qué sucede en el mundo real y evoluciona rápidamente.

Lado del diseño, usted diseña para la mantenibilidad como uno de los factores clave.

  • Patrones de aprendizaje que funcionan y patrones a evitar. Cuantos más patrones coherentes y coherentes tenga, más cómodo se sentirá que ocurrirá o no una clase específica de problemas.
  • Haz las cosas obvias. La obscuridad conduce a la confusión, conduce a errores.
  • Fuertes convenciones de nomenclatura hasta el final. Si nombras las cosas bien y de manera consistente, hay una gran cantidad de beneficio cuando tratas de cambiar las cosas o explicarlas allí ... todo lo que se llama Factory hará X.
  • Deletrea las cosas por completo. Tenemos autocompletado en estos días, no utilizamos siglas cuando la palabra completa elimina la confusión.
  • Separar en capas o abstracciones ... por lo que se producirán estilos específicos de problemas en una capa específica en lugar de "en algún lugar"
  • Aislar las clases de problemas en capas y aspectos. Cuanto menos una parte tenga que ver con otra parte del código, mejor en general. Incluso si toma un poco más de tiempo escribir cosas similares dos veces.

Y la clave ... Encuentre un mentor que haya cometido todos los errores anteriormente O haga varios problemas hasta que descubra qué funciona y qué no.


1
Me gusta el "diseño para la mantenibilidad". Especialmente bueno es rápido giro cuando los problemas no con el tiempo surgen. Eso significa buenas pruebas unitarias completas, una buena estrategia de implementación y buenos procesos de seguimiento de errores / control de calidad.
Dean Harding

4

Todo lo anterior son buenos puntos. Pero hay algo que no se menciona. Debe hacer que sus módulos y funciones sean paranoicos. Prueba de rango de todos los parámetros de la función. Tenga cuidado con las cadenas con comienzos o finales en blanco o que son demasiado cortas o demasiado largas. Tenga cuidado con los booleanos que son muy verdaderos, no falsos. En lenguajes sin tipo como PHP, tenga cuidado con los tipos de variables inesperados. Cuidado con NULL.

Este código paranoico a menudo se codifica como afirmaciones que se pueden deshabilitar en una compilación de producción para acelerar las cosas. Pero definitivamente evitará los errores de pánico de última hora.


¿Cómo puede un booleano no ser verdadero ni falso?
Zhehao Mao

@Zhehao Mao: si el booleano es una columna en una base de datos, puede ser Verdadero, Falso o NULO.
Mike Sherrill 'Cat Recall'

Cuando era GI, teníamos un dicho. "Cuando todo el mundo realmente está tratando de hacerte daño, la paranoia es sólo bueno, pensamiento sano." Algunas autoridades llaman a esto programación defensiva .
Mike Sherrill 'Cat Recall'

Oh ya veo. Rareza SQL.
Zhehao Mao

3

Rob tiene razón al decir que las pruebas unitarias no te salvarán de errores desconocidos, PERO las pruebas unitarias te ayudarán a evitar la introducción de errores cuando corrijas los errores desconocidos y te evitarán volver a introducir errores viejos accidentalmente. TDD también lo obligará a diseñar su software desde el principio para que sea comprobable y eso tenga un gran valor positivo continuo.


Este aspecto de las pruebas unitarias parece ser el más incomprendido: no demuestra la exactitud de su código con las pruebas unitarias, falsifica la corrección de los siguientes cambios. Pero cada vez que un error en el código unittested se encuentra, alguien grita 'ver, las pruebas no tienen valor, que dio encontrar este error'
keppla

Luego agrega una prueba para reproducir el defecto. Corregir el defecto y que siempre será realizando una prueba de que el error cada vez que se ejecuta el conjunto de pruebas ...
mcottle

eso es lo que haría, pero eso a menudo conduce a 'sí, ahora es demasiado tarde, el error ya sucedió'. El hecho de que el error no se vuelva a introducir a menudo será supervisado
keppla

Esto es cierto, pero para entonces han migrado a lo desconocido desconocido :)
Robin Vessey

2

Evite el estado / "efectos secundarios" cuando sea posible. Mientras que las computadoras son deterministas y ofrecen la misma salida para la misma entrada, la descripción general de la entrada siempre es incompleta. Lamentablemente, la mayoría de las veces no nos damos cuenta de lo incompleto que es.

Cuando se habla de aplicaciones web, toda la base de datos, la solicitud actual, la sesión del usuario, las bibliotecas de terceros instaladas y mucho más es parte de la entrada. Cuando se habla de subprocesos, es aún peor: todo su sistema operativo, con todos los demás procesos administrados por el mismo planificador, es 'parte de la entrada'.

Los errores son causados ​​al juzgar mal la forma en que se maneja la entrada o al juzgar mal la entrada. Estos últimos son, en mi experiencia, los difíciles: solo puedes observarlos 'en vivo', a menudo, ya no tienes la entrada.

Al aprender nuevas tecnologías, infraestructuras, etc., en mi opinión, es una buena práctica obtener una visión general, qué componentes contribuyen a la entrada y luego tratar de evitar la mayor cantidad posible de ellos .


+1: Los efectos secundarios a menudo se pueden evitar aplicando principios SÓLIDOS y creando métodos atómicos. Además, el código debe estar cubierto por afirmaciones.
Halcón

0

A medida que su software se vuelve más complejo, es inevitable que ocurran algunos errores. La única forma de evitarlo por completo es desarrollar un software trivial, y aun así, es probable que cometas un error de vez en cuando.

Lo único práctico que puede hacer es evitar una complejidad innecesaria : hacer que su software sea lo más simple posible, pero no más simple que eso.

Eso es básicamente de lo que se tratan todos los principios y patrones de diseño más específicos: hacer que las cosas sean lo más simples posible. El problema es que "simple de qué manera" puede ser subjetivo: ¿se refiere al diseño más simple para los requisitos actuales, o simple de modificar para requisitos futuros? Y hay principios para eso también.

Las pruebas unitarias son un ejemplo de esta incertidumbre. Por un lado, son una complejidad innecesaria: código que debe desarrollarse y mantenerse, pero que no hace el trabajo. Por otro lado, son una forma simple de automatizar las pruebas, reduciendo la cantidad de pruebas manuales mucho más difíciles que deben hacerse.

No importa cuánta teoría de diseño aprenda y cuánta experiencia obtenga, el principio fundamental (y a veces la única guía que tiene) es apuntar a la simplicidad.


0

Los errores de software no tienen nada de aleatorio , las causas raíz son de naturaleza perfectamente determinista, instrucciones incorrectas para la computadora.

Los errores de subprocesos pueden ser no deterministas en el comportamiento de ejecución, pero no son aleatorios en la causa raíz.

Suceden por exactamente la misma razón solo en momentos aparentemente impredecibles en el tiempo, pero eso no los hace aleatorios, simplemente aparentemente impredecibles, una vez que se conoce la causa raíz, se puede predecir de manera determinista cuándo sucederán.

Dije aparentemente e hice la distinción por una razón. Aleatorio significa una cosa, Hecho, hecho, sucediendo o elegido sin método o decisión consciente , que implica que hay una toma de decisiones independiente por parte de la computadora, no está haciendo exactamente lo que se le dijo que hiciera, usted simplemente no le dije que hiciera lo correcto en algunos casos muy deterministas.

La semántica de las palabras está ahí por una razón, aleatorio no significa algo diferente solo porque alguien lo usa correctamente, siempre significa lo mismo. Un término mejor sería en errores lógicos involuntarios o no obvios .

Considerar los errores como aleatorios es casi como aceptar que hay otra fuerza incomprensible en el trabajo que no se entiende completamente y que actúa independientemente de su aporte a la computadora, y eso no es muy científico. Quiero decir, ¿están enojados los Dioses y están enviando tu solicitud por capricho?


+1 para un punto válido, pero -1 para nitpicking. Entonces, +/- 0. Creo que la mayoría de las personas que leyeron la pregunta tomaron "aleatorio" no en el sentido completamente literal (¿qué significa exactamente "aleatorio" realmente, llevado a su extremo?), Sino más bien como algo así como "I no entiendo cómo este comportamiento podría haberse infiltrado o por qué el software hace esto mal ".
un CVn

@Micheal: es por eso que aparentemente dije e hice la distinción. No hay nada extremo al azar , significa una cosa: Hecho, hecho, sucediendo o elegido sin método o decisión consciente, la semántica de las palabras está ahí por una razón, aleatorio no significa algo diferente solo porque alguien lo usa de manera incorrecta, siempre significa lo mismo. Lo que probablemente querían decir era involuntario , debido a las razones que establezco en mi explicación en mi respuesta.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.