Escritura de código robusto frente a sobre ingeniería

33

¿Cómo saben ustedes que está escribiendo el código más robusto posible sin ingeniería excesiva?

Me encuentro pensando demasiado en cada camino posible que puede tomar mi código, y a veces parece una pérdida de tiempo. Supongo que depende del tipo de programa que esté escribiendo, pero no quiero usar mucho de mi tiempo teniendo en cuenta situaciones que nunca sucederán.

Fran Sevillano
fuente
2
Codifíquelo en Agda2
SK-logic
Un ejemplo concreto sería de gran ayuda para hacer su punto. :)
João Portela
¿Puedo comprobar que realmente está preguntando acerca de la robustez, es decir, "la capacidad de un sistema para continuar funcionando en presencia de insumos no válidos o condiciones ambientales estresantes", porque algunas respuestas parecen pensar que está hablando de extensibilidad.
DJClayworth
Trabajo bajo plazos locos, además es para demo-ware, así que felizmente puedo cortar rápidamente sin parálisis de perfección.
Trabajo
1
Aquí hay un artículo que habla sobre el tema: code-tag.com/2017/04/02/…
San

Respuestas:

39

¿Cómo saben ustedes que está escribiendo el código más robusto posible sin ingeniería excesiva?

¿Qué consideras código robusto? ¿Código que ya es prueba de futuro y tan poderoso que puede manejar cualquier situación? ¡Incorrecto, nadie puede predecir el futuro! Y mal otra vez, porque será un desastre complicado e imposible de mantener.

Sigo varios principios: en primer lugar, YAGNI (todavía) y KISS , por lo que no escribo código innecesario. Eso también previene eficazmente la sobreingeniería. Refactorizo ​​la aplicación cuando se necesitan extensiones. Las modernas herramientas de refactorización le permiten crear interfaces e intercambiar implementaciones con bastante facilidad cuando las necesite.

Luego trato de hacer que el código que escribo sea lo más robusto posible, eso incluye eliminar tantas rutas que el programa pueda tomar (y también estados) como sea posible y un poco de programación espartana . Una gran ayuda son las funciones / métodos "atómicos" que no dependen de estados externos o al menos no dejan el programa en un estado inconsistente cuando fallan. Si lo haces bien, también es muy poco probable que alguna vez termines con un código de espagueti y también es una bendición para el mantenimiento. Además, en el diseño orientado a objetos, los principios SÓLIDOS son una gran guía para un código robusto.

Realmente descubrí que muchas veces puedes reducir la complejidad, por ejemplo, las explosiones combinatorias de las rutas o estados del programa, al pensar profundamente en cómo podrías diseñarlo como la ruta más directa posible. Intente mantener las combinaciones posibles al mínimo eligiendo el mejor orden de sus subrutinas y diseñándolas para este propósito.

El código robusto es siempre un código simple y limpio, pero la simplicidad es un rasgo que no siempre se logra fácilmente. Sin embargo, debes luchar por ello. Siempre escriba el código más simple posible y solo agregue complejidad cuando no tenga otra opción.

La simplicidad es robusta, la complejidad es frágil.

La complejidad mata.

Halcón
fuente
2
Porque no tiene toneladas de clases, fábricas y abstracciones. Es una paradoja, pero a algunas personas les gustan esas cosas. No tengo idea de por qué.
Codificador
55
¡¡¡Esto es Esparta!!!
Tom Squires
44
Las personas que no han estado haciendo esto durante veinte años simplemente no entienden cómo la complejidad puede matarlo. Piensan que son muy inteligentes. Son tontos, no inteligentes. Esa complejidad te matará a la muerte.
PeterAllenWebb
1
La robustez no se trata de preparar el futuro: se trata de continuar funcionando con entradas no válidas o entornos estresantes: Code Complete p464.
DJClayworth
55
A menos que el interlocutor esté usando 'robusto' en un sentido diferente del que entiendo, está respondiendo una pregunta diferente. No está preguntando "¿debería codificar para permitir requisitos futuros?", Está preguntando "qué casos de entrada inusuales debo manejar". Ninguno de YAGNI, KISS y SOLID son relevantes. ¿Necesita permitir que un millón de usuarios intenten iniciar sesión simultáneamente? ¿Qué sucederá si un nombre de inicio de sesión comienza con una barra diagonal inversa? YAGNI no responde a ninguna de estas preguntas.
DJClayworth
8

Intento mantener un equilibrio, centrándome en

  • manejar todas las rutas de ejecución posibles en los casos de uso existentes (esta es la parte de "robustez"),
  • habilitando características / requisitos Estoy bastante seguro de que vendrán en el futuro previsible, y
  • cosas que sé por experiencia que serán necesarias para el mantenimiento a largo plazo de la base del código (es decir, mantener el código limpio y comprobable).

Es un área fronteriza borrosa: a veces logro hacer un trabajo innecesario, a veces no puedo hacer algo que resulta necesario más tarde. Si las fallas no son grandes, estoy bien. En cualquier caso, me esfuerzo por aprender de mis errores.

Péter Török
fuente
Un ejemplo sería genial aquí manejando todas las rutas de ejecución posibles en los casos de uso existentes
CodeYogi
5

La diferencia entre robustez y sobre ingeniería es la diferencia entre manejar con gracia todos los casos de uso posibles, incluso los casos de uso extraños y marginales que NO DEBEN suceder. Cuando digo con gracia quiero decir, el usuario ingresa en un extraño caso de excepción o se encuentra con una situación que requiere una característica no admitida o no especificada que no se definió y el código sale sin fallar o informa al usuario de la funcionalidad no admitida.

La sobreingeniería, por otro lado, podría caer en el ámbito de la implementación completa de características que no fueron necesarias o solicitadas (¡algunas características que el cliente NECESITA pero nunca fueron solicitadas!) O puede definirse derivando un diseño demasiado complejo o demasiado complejo código para manejar un problema relativamente simple.

maple_shaft
fuente
4

1) Obtenga requisitos.

2) Escriba el código mínimo para cumplir con los requisitos. Si algo es ambiguo, haga una suposición educada. Si es super ambiguo, regrese a 1.

3) Enviar a prueba.

4) Si los evaluadores dicen que es bueno, documente la aprobación. Si algo está apagado, regrese a 1.

Concéntrese en aprobar las pruebas, no en predecir las pruebas. Si no tienes probadores ... ¡obtén probadores! Son esenciales no solo para verificar la corrección del código, sino para todo el proceso de desarrollo.

Morgan Herlocker
fuente
1
+1 para Enfocarse en pasar las pruebas, no en predecir las pruebas, sin embargo, se espera que muchos desarrolladores como yo hagan las dos cosas por la falta de analistas comerciales sólidos.
maple_shaft
@maple_shaft - Muy cierto. El punto es que estos problemas surgen debido a la incompetencia de otra persona. Estresarse por el trabajo de otra persona es el camino al agotamiento. Si mi empresa fuera lo suficientemente tonta como para que yo hiciera las cuentas por cobrar del mes, no estaría demasiado decepcionado conmigo mismo si no saliera bien. La definición de requisitos generalmente implica describir lo que hace todos los días, para que pueda automatizarse. Si ninguno de los empleados puede hacer eso, bueno ... la compañía puede estar en problemas.
Morgan Herlocker
3

En primer lugar, mantenga los datos normalizados (no redundantes) tanto como pueda. Si los datos están completamente normalizados, ninguna actualización única de los datos puede hacer que sean inconsistentes.

No siempre puede mantener los datos normalizados, en otras palabras, es posible que no pueda eliminar la redundancia, en cuyo caso puede tener estados inconsistentes. Lo que hay que hacer es tolerar la inconsistencia y repararla periódicamente con algún tipo de programa que lo abarque y lo repare.

Existe una fuerte tendencia a tratar de gestionar la redundancia de manera estricta mediante notificaciones. No solo es difícil asegurarse de que sean correctos, sino que pueden conducir a enormes ineficiencias. (Parte de la tentación de escribir notificaciones surge porque en OOP prácticamente se les alienta).

En general, cualquier cosa que dependa de la secuencia temporal de eventos, mensajes, etc., será vulnerable y requerirá toneladas de codificación defensiva. Los eventos y mensajes son característicos de los datos con redundancia, ya que están comunicando cambios de una parte a otra, tratando de evitar inconsistencias.

Como dije, si debe tener redundancia (y es muy probable que deba hacerlo), lo mejor es poder a) tolerar y b) repararlo. Si intenta evitar inconsistencias únicamente mediante mensajes, notificaciones, disparadores, etc., le resultará muy difícil hacerlo robusto.

Mike Dunlavey
fuente
3
  • Escribir para reutilizar.
  • escribir pruebas trivial, no trivial, algunos absurdamente complejos para ver cómo se maneja en tales condiciones. las pruebas también lo ayudarán a determinar la forma de la interfaz.
  • escribe el programa para que falle (p. ej. aserción). mi código tiene una tonelada de reutilización y pruebo una tonelada de casos: hay más verificación / manejo de errores que la implementación real (según el recuento de líneas).
  • reutilizar.
  • Inmediatamente arregla las cosas que salen mal.
  • Aprende y construye desde la experiencia.

aparecerán errores en el camino, pero (afortunadamente) serán localizados y (en la mayoría de los casos) aparecerán muy temprano en las pruebas. El otro beneficio de la reutilización es que el cliente / llamante puede guardar la mayor parte de la comprobación / andamiaje de errores utilizando lo que implica la implicación.

sus pruebas luego definirán las capacidades de su programa y qué tan robustas son: continúe agregando pruebas hasta que esté satisfecho con las tasas de éxito y las entradas; mejorando, extendiendo y fortificando según sea necesario.

justin
fuente
2

Hago esta distinción escribiendo código con un comportamiento bien definido, pero no necesariamente óptimo, para pases de ejecución muy improbables. Por ejemplo, cuando estoy bastante seguro (probado, pero no probado) de que una matriz será positiva definida, inserto una aserción o excepción en el programa para probar el estado, pero no escribo una ruta de código propia para ella. De este modo, el comportamiento se define, pero subóptimo.

thiton
fuente
2

Robustez: El grado en que un sistema continúa funcionando en presencia de entradas inválidas o condiciones ambientales estresantes. (Código completo 2, p464)

La pregunta importante aquí es preguntar qué tan importante es la robustez para usted. Si es Facebook, es realmente importante que su sitio web continúe funcionando cuando alguien ingrese caracteres especiales en la entrada, y que su servidor permanezca activo cuando 100 millones de usuarios inicien sesión simultáneamente. Si está escribiendo un script para realizar una operación común que solo usted hace, no le importa mucho. En el medio hay muchos niveles. Juzgar sobre cuánta robustez necesitas es una de las habilidades importantes que un desarrollador debe aprender.

El principio de YAGNI se aplica a la adición de características que un programa podría necesitar. Pero ese principio no se aplica a la robustez. Los programadores tienden a sobreestimar la probabilidad de que se necesite una extensión futura dada (especialmente si es buena), pero subestiman la probabilidad de que las cosas salgan mal. Además, si resulta que se necesita una función omitida después, el programador puede escribirla más tarde. Si resulta que después de todo se necesita una verificación de error omitida, el daño puede estar hecho.

Por lo tanto, en realidad es mejor errar al hacer las comprobaciones de condiciones de error inusuales. Pero hay un equilibrio. Algunas de las cosas a considerar en este balance:

  • ¿Con qué frecuencia puede ocurrir este error?
  • ¿Cuál es el costo de que ocurra este error?
  • ¿Es esto para uso interno o externo?

No olvide que las personas pueden, y lo harán, intentar usar su programa de maneras inesperadas. Es mejor si sucede algo predecible cuando lo hacen.

Como última línea de defensa, use la afirmación o el cierre. Si sucede algo que no puede resolver cómo lidiar con eso, cierre el programa. Eso suele ser mejor que permitir que el programa continúe y haga algo impredecible.

DJClayworth
fuente