Recientemente he presenciado más y más problemas similares a los explicados en este artículo sobre intersecciones de características. Otro término para ello sería líneas de productos, aunque tiendo a atribuirlos a productos realmente diferentes, mientras que generalmente encuentro estos problemas en forma de posibles configuraciones de productos.
La idea básica de este tipo de problema es simple: agrega una característica a un producto, pero de alguna manera las cosas se complican debido a una combinación de otras características existentes. Eventualmente, QA encuentra un problema con una rara combinación de características en las que nadie había pensado antes y lo que debería haber sido una simple corrección de errores puede incluso requerir grandes cambios de diseño.
Las dimensiones de este problema de intersección de características son de una complejidad alucinante. Digamos que la versión actual del software tiene N
características y agrega una nueva característica. Simplifiquemos también las cosas al decir que cada una de las funciones solo puede activarse o desactivarse, entonces ya tiene 2^(N+1)
posibles combinaciones de funciones para considerar. Debido a la falta de mejores términos de redacción / búsqueda, me refiero a la existencia de estas combinaciones como un problema de intersección de características . (Puntos de bonificación por una respuesta que incluye referencias para un término más establecido).
Ahora, la pregunta con la que lucho es cómo lidiar con este problema de complejidad en cada nivel del proceso de desarrollo. Por razones obvias de costos, no es práctico hasta el punto de ser utópico, querer abordar cada combinación individualmente. Después de todo, tratamos de mantenernos alejados de los algoritmos de complejidad exponencial por una buena razón, pero convertir el proceso de desarrollo mismo en un monstruo de tamaño exponencial seguramente conducirá a un fracaso total.
Entonces, ¿cómo se obtiene el mejor resultado de una manera sistemática que no explote ningún presupuesto y se complete de una manera decente, útil y profesionalmente aceptable?
Especificación: cuando especifica una nueva función, ¿cómo se asegura de que funcione bien con todos los demás niños?
Puedo ver que uno podría examinar sistemáticamente cada característica existente en combinación con la nueva característica, pero eso sería un aislamiento de las otras características. Dada la naturaleza compleja de algunas características, esta visión aislada a menudo ya está tan involucrada que necesita un enfoque estructurado en sí mismo, y mucho menos el
2^(N-1)
factor causado por las otras características que uno ignoraba voluntariamente.Implementación: cuando implementa una función, ¿cómo se asegura de que su código interactúa / intersecta correctamente en todos los casos?
Nuevamente, me pregunto acerca de la gran complejidad. Conozco varias técnicas para reducir el potencial de error de dos características que se cruzan, pero ninguna que se escalara de manera razonable. Sin embargo, supongo que una buena estrategia durante la especificación debería mantener el problema a raya durante la implementación.
Verificación: cuando prueba una característica, ¿cómo lidia con el hecho de que solo puede probar una fracción del espacio de intersección de esta característica?
Es lo suficientemente difícil saber que probar una sola característica de forma aislada no garantiza nada cerca del código libre de errores, pero cuando lo reduce a una fracción
2^-N
parece que cientos de pruebas ni siquiera cubren una sola gota de agua en todos los océanos combinados . Peor aún, los errores más problemáticos son los que se derivan de la intersección de las características, que uno no esperaría que condujeran a ningún problema, pero ¿cómo los prueba si no espera una intersección tan fuerte?
Si bien me gustaría escuchar cómo otros tratan este problema, estoy principalmente interesado en literatura o artículos que analicen el tema con mayor profundidad. Por lo tanto, si sigue personalmente una determinada estrategia, sería bueno incluir las fuentes correspondientes en su respuesta.
fuente
Respuestas:
Ya sabíamos matemáticamente que la verificación de un programa es imposible en un tiempo finito en el caso más general, debido al problema de detención. Entonces este tipo de problema no es nuevo.
En la práctica, un buen diseño puede proporcionar un desacoplamiento tal que el número de entidades que se cruzan es mucho menor que 2 ^ N, aunque ciertamente parece estar por encima de N incluso en sistemas bien diseñados.
En cuanto a las fuentes, me parece que casi todos los libros o blogs sobre diseño de software intentan reducir ese 2 ^ N tanto como sea posible, aunque no conozco ninguno que plantee el problema en los mismos términos que usted. hacer.
Para un ejemplo de cómo el diseño podría ayudar con esto, en el artículo se mencionó que parte de la intersección de características se produjo porque la replicación y la indexación se activaron en la etiqueta electrónica. Si tuvieran disponible otro canal de comunicación para indicar la necesidad de cada uno de ellos por separado, entonces posiblemente podrían haber controlado el orden de los eventos más fácilmente y tener menos problemas.
O tal vez no. No sé nada sobre RavenDB. La arquitectura no puede evitar problemas de intersección de características si las características realmente están inexplicablemente entrelazadas, y nunca podemos saber de antemano que no queremos una característica que realmente tenga el peor caso de intersección 2 ^ N. Pero la arquitectura puede al menos limitar las intersecciones debido a problemas de implementación.
Incluso si estoy equivocado acerca de RavenDB y eTags (y solo lo estoy usando como argumento, son personas inteligentes y probablemente lo hicieron bien), debería quedar claro cómo la arquitectura puede ayudar. La mayoría de los patrones de los que habla la gente están diseñados explícitamente con el objetivo de reducir la cantidad de cambios de código requeridos por características nuevas o cambiantes. Esto se remonta al pasado: por ejemplo, "Patrones de diseño, elementos de software orientado a objetos reutilizables", la introducción establece que "Cada patrón de diseño permite que algún aspecto de la arquitectura varíe independientemente de otros aspectos, lo que hace que un sistema sea más robusto para un tipo particular de cambio".
Mi punto es que uno puede tener una idea de la gran O de las intersecciones de características en la práctica al observar lo que sucede en la práctica. Al investigar esta respuesta, descubrí que la mayoría de los análisis de puntos de función / esfuerzo de desarrollo (es decir, productividad) encontraron menos crecimiento lineal del esfuerzo del proyecto por punto de función, o muy ligeramente por encima del crecimiento lineal. Lo cual me pareció un poco sorprendente. Este tenía un ejemplo bastante legible.
Esto (y estudios similares, algunos de los cuales usan puntos de función en lugar de líneas de código) no prueba que la intersección de características no ocurra y cause problemas, pero parece una evidencia razonable de que no es devastador en la práctica.
fuente
Esta no será la mejor respuesta de ninguna manera, pero he estado pensando en algunas cosas que se cruzan con puntos en su pregunta, así que pensé en mencionarlas:
Soporte estructural
Por lo poco que he visto, cuando las características tienen errores y / o no encajan bien con otras, se debe en gran medida al escaso soporte proporcionado por la estructura / marco central del programa para gestionarlas / coordinarlas. Creo que pasar más tiempo desarrollando y completando el núcleo debería facilitar la incorporación de nuevas funciones.
Una cosa que he encontrado para ser común en las aplicaciones en las que el trabajo es que la estructura de un programa fue creado para manejar uno de un tipo de objeto o proceso, pero muchas de las extensiones que hemos hecho o quieren hacer tener que ver con el manejo de muchos de los tipos. Si esto se tuviera en cuenta más al comienzo del diseño de la aplicación, habría ayudado a agregar estas características más adelante.
Esto se vuelve bastante crítico al agregar soporte para múltiples X que involucran código roscado / asíncrono / controlado por eventos porque esas cosas pueden ir mal bastante rápido: he tenido la alegría de depurar una serie de problemas relacionados con esto.
Sin embargo, es probable que sea difícil justificar este tipo de esfuerzo por adelantado, especialmente para prototipos o proyectos únicos, a pesar de que algunos de esos prototipos o únicos se usan nuevamente o como (la base del) sistema final, lo que significa que el gasto habría valido la pena a largo plazo.
Diseño
Al diseñar el núcleo de un programa, comenzar con un enfoque de arriba hacia abajo puede ayudar a convertir las cosas en fragmentos manejables y le permite comprender el dominio del problema; después de eso, creo que debería usarse un enfoque ascendente : esto ayudará a hacer las cosas más pequeñas, más flexibles y mejores para agregar más adelante. (Como se menciona en el enlace, hacer las cosas de esta manera hace que las implementaciones sean más pequeñas, lo que significa menos conflictos / errores).
Si se enfoca en los componentes básicos del sistema y se asegura de que todos interactúen bien, entonces todo lo que se construya con ellos también se comportará bien y debería integrarse mejor con el resto del sistema.
Cuando se agrega una nueva característica, creo que se podría tomar una ruta similar al diseñarla como se tomó al diseñar el resto del marco: descomponerla y luego ir de abajo hacia arriba. Si puede reutilizar cualquiera de los bloques originales del marco de trabajo al implementar la función, definitivamente sería útil; una vez que haya terminado, puede agregar cualquier bloque nuevo que obtenga de la función a los que ya están en el marco central, probándolos con el conjunto original de bloques, de esa manera serán compatibles con el resto del sistema y serán utilizables en el futuro características también.
¡Simplificar!
Últimamente he estado adoptando una postura minimalista sobre el diseño, comenzando simplificando el problema y luego simplificando la solución. Si se puede hacer tiempo por un segundo, simplificando la iteración de diseño en un proyecto, entonces podría ver que eso es muy útil al agregar cosas más adelante.
De todos modos, ese es mi 2c.
fuente