¿Cuántos patrones de diseño y niveles de abstracción son necesarios? [cerrado]

29

¿Cómo puedo saber si mi software tiene demasiada abstracción y demasiados patrones de diseño, o al revés, cómo sé si debería tener más?

Los desarrolladores con los que trabajo están programando de manera diferente con respecto a estos puntos.

Algunos resumen cada pequeña función, usan patrones de diseño siempre que sea posible y evitan la redundancia a cualquier costo.

Los demás, incluyéndome a mí, intentamos ser más pragmáticos y escribimos código que no se ajusta perfectamente a todos los patrones de diseño, pero que es mucho más rápido de entender porque se aplica menos abstracción.

Sé que esto es una compensación. ¿Cómo puedo saber cuándo hay suficiente abstracción en el proyecto y cómo sé que necesita más?

Ejemplo, cuando se escribe una capa de caché genérica usando Memcache. ¿Realmente necesitamos Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... o se trata más fáciles de mantener y aún así un buen código cuando se utiliza sólo la mitad de esas clases?

Encontré esto en Twitter:

ingrese la descripción de la imagen aquí

( https://twitter.com/rawkode/status/875318003306565633 )

Daniel W.
fuente
58
Si está tratando los patrones de diseño como cosas que saca de un cubo y usa para ensamblar programas, está usando demasiados.
Blrfl
55
Podrías pensar en patrones de diseño como los patrones de habla que usa la gente. Porque, en cierto sentido, los modismos, las metáforas, etc. son todos patrones de diseño. Si estás usando un modismo en cada oración ... probablemente sea con demasiada frecuencia. Pero pueden ayudar a aclarar los pensamientos y ayudar a comprender lo que de otro modo sería un largo muro de prosa. Realmente no hay una manera correcta de responder "¿con qué frecuencia debo usar metáforas?", Depende del juicio del autor.
Prime
8
No hay forma de que una sola respuesta SE pueda cubrir adecuadamente este tema. Esto lleva literalmente años de experiencia y tutoría para manejarlo. Esto es claramente demasiado amplio.
jpmc26
55
Siga el principio de diseño básico en la industria de la aviación: "Simplifique y agregue más ligereza". Cuando descubre que la mejor manera de corregir un error es simplemente eliminar el código que contiene el error, ya que todavía no hace nada útil, incluso si estaba libre de errores, ¡está comenzando a hacer el diseño correcto!
alephzero

Respuestas:

52

¿Cuántos ingredientes son necesarios para una comida? ¿Cuántas partes necesitas para construir un vehículo?

Sabes que tienes muy poca abstracción cuando un pequeño cambio de implementación conduce a una cascada de cambios en todo tu código. Las abstracciones adecuadas ayudarían a aislar la parte del código que debe cambiarse.

Sabes que tienes demasiada abstracción cuando un pequeño cambio de interfaz conduce a una cascada de cambios en todo tu código, en diferentes niveles. En lugar de cambiar la interfaz entre dos clases, se encuentra modificando docenas de clases e interfaces solo para agregar una propiedad o cambiar un tipo de argumento de método.

Aparte de eso, realmente no hay forma de responder la pregunta dando un número. El número de abstracciones no será el mismo de un proyecto a otro, de un idioma a otro e incluso de un desarrollador a otro.

Arseni Mourzenko
fuente
28
Si solo tiene dos interfaces y cientos de clases que las implementan, cambiar las interfaces conduciría a una cascada de cambios, pero eso no significa que haya demasiada abstracción, ya que solo tiene dos interfaces.
Tulains Córdova
¡El número de abstracciones ni siquiera será el mismo para diferentes partes del mismo proyecto!
T. Sar - Restablece a Monica el
Sugerencia: cambiar de Memcache a otro mecanismo de almacenamiento en caché (¿Redis?) Es un cambio de implementación .
Ogre Psalm33
Sus dos reglas (pautas, como quiera llamarlas) no funcionan, como lo demuestran Tulains. También están lamentablemente incompletos, incluso si lo hicieran. El resto de la publicación no responde, dice poco más de lo que no podemos proporcionar una respuesta razonable. -1
jpmc26
Yo diría que en el caso de dos interfaces y cientos de clases que las implementan, es muy probable que haya excedido sus abstracciones. Ciertamente, he visto esto en proyectos que reutilizan una abstracción muy vaga en muchos lugares ( interface Doer {void prepare(); void doIt();}), y se vuelve doloroso refactorizar cuando esta abstracción ya no se ajusta. La parte clave de la respuesta es que la prueba se aplica cuando una abstracción tiene que cambiar; si nunca lo hace, nunca causa dolor.
James_pic
24

El problema con los patrones de diseño se puede resumir con el proverbio "cuando sostienes un martillo, todo parece un clavo". El acto de aplicar un patrón de diseño no mejora su programa en absoluto. De hecho, diría que está haciendo un programa más complicado si está agregando un patrón de diseño. La pregunta sigue siendo si estás haciendo un buen uso del patrón de diseño o no, y este es el corazón de la pregunta: "¿Cuándo tenemos demasiada abstracción?"

Si está creando una interfaz y una superclase abstracta para una sola implementación, ha agregado dos componentes adicionales a su proyecto que son superfluos e innecesarios. El punto de proporcionar una interfaz es poder manejarla por igual en todo el programa sin saber cómo funciona. El objetivo de una superclase abstracta es proporcionar un comportamiento subyacente para las implementaciones. Si solo tiene una implementación, obtiene todas las interfaces de complicación y las clases de abstact proporcionadas y ninguna de las ventajas.

Del mismo modo, si está utilizando un patrón Factory y se encuentra lanzando una clase para usar la funcionalidad solo disponible en la superclase, el patrón Factory no agrega ninguna ventaja a su código. Solo ha agregado una clase adicional a su proyecto que podría haberse evitado.

TL; DR Mi punto es que el objetivo de la abstracción no es abstracto en sí mismo. Tiene un propósito muy práctico en su programa, y ​​antes de decidir usar un patrón de diseño o crear una interfaz, debe preguntarse si al hacerlo, el programa es más fácil de entender a pesar de la complejidad adicional o el programa es más robusto a pesar de la complejidad adicional (preferiblemente ambas). Si la respuesta es no o tal vez, tómese un par de minutos para considerar por qué quería hacer eso y si tal vez se puede hacer de una mejor manera en su lugar sin necesariamente el requisito de agregar abstracción a su código.

Neil
fuente
La analogía del martillo sería el problema de conocer solo un patrón de diseño. Los patrones de diseño deben crear un conjunto completo de herramientas para seleccionar y aplicar cuando sea apropiado. No seleccionas una almádena para romper una nuez.
Pete Kirkham
@PeteKirkham True, pero incluso una gama completa de patrones de diseño a su disposición puede no ser adecuada para un problema en particular. Si un martillo no es el más adecuado para romper una tuerca, y tampoco lo es un destornillador, y tampoco es una cinta métrica porque le falta el martillo, eso no hace que un martillo sea la elección correcta para el trabajo, simplemente hace Es la herramienta más adecuada a su disposición. Sin embargo, eso no significa que deba usar un mazo para romper una nuez. Diablos, si estamos siendo francos, lo que realmente necesitarías es un cascanueces, no un martillo ..
Neil
Quiero un ejército de ardillas entrenadas para romper mi nuez.
icc97
6

TL: DR;

No creo que haya un número "necesario" de niveles de abstracciones por debajo del cual haya demasiado poco o por encima del cual haya demasiado. Al igual que en el diseño gráfico, un buen diseño de OOP debe ser invisible y debe darse por sentado. El mal diseño siempre sobresale como un pulgar dolorido.

Respuesta larga

Lo más probable es que nunca sepas cuántos niveles de abstracciones estás construyendo.

La mayoría de los niveles de abstracción son invisibles para nosotros y los damos por sentado.

Ese razonamiento me lleva a esta conclusión:

Uno de los propósitos principales de la abstracción es salvar al programador de la necesidad de tener todo el funcionamiento del sistema en mente todo el tiempo. Si el diseño te obliga a saber demasiado sobre el sistema para agregar algo, entonces probablemente haya muy poca abstracción. Creo que una mala abstracción (diseño deficiente, diseño anémico o ingeniería excesiva) también puede obligarlo a saber demasiado para agregar algo. En un extremo tenemos un diseño basado en una clase de dios o un montón de DTO, en el otro extremo tenemos algunos marcos de OR / persistencia que te hacen saltar a través de incontables aros para lograr un mundo hola. Ambos casos te obligan a que tú también sepas demasiado.

La mala abstracción se adhiere a una campana de Gauss en el hecho de que una vez que pasas un punto dulce comienza a interponerse en el camino. La buena abstracción, por otro lado, es invisible y no puede haber demasiado porque no te das cuenta de que está allí. Piense en cuántas capas sobre capas de API, protocolos de red, bibliotecas, bibliotecas del sistema operativo, sistemas de archivos, capas de hardware, etc., su aplicación se basa y da por sentado.

Otro propósito principal de la abstracción es la compartimentación, por lo que los errores no penetran más allá de un área determinada, a diferencia del doble casco y los tanques separados evitan que un barco se inunde completamente cuando una parte del casco tiene un agujero. Si las modificaciones al código terminan creando errores en áreas aparentemente no relacionadas, entonces es probable que haya muy poca abstracción.

Tulains Córdova
fuente
2
"Lo más probable es que nunca sepas cuántos niveles de abstracciones estás construyendo". - De hecho, el objetivo de una abstracción es que no sabes cómo se implementa, por supuesto, no sabes (y no puedes ) cuántos niveles de abstracciones esconde.
Jörg W Mittag
4

Los patrones de diseño son simplemente soluciones comunes a los problemas. Es importante conocer los patrones de diseño, pero son solo síntomas de un código bien diseñado (un buen código aún puede ser anulado por la pandilla de cuatro patrones de diseño), no la causa.

Las abstracciones son como vallas. Ayudan a separar las regiones de su programa en fragmentos comprobables e intercambiables (requisitos para crear código no frágil y no frágil). Y al igual que las cercas:

  • Desea abstracciones en puntos de interfaz naturales para minimizar su tamaño.

  • No quieres cambiarlos.

  • Desea que separen cosas que pueden ser independientes.

  • Tener uno en el lugar equivocado es peor que no tenerlo.

  • No deberían tener grandes fugas .

dlasalle
fuente
4

Refactorización

No vi la palabra "refactorización" mencionada ni una vez hasta ahora. Así que, aquí vamos:

Siéntase libre de implementar una nueva característica lo más directamente posible. Si solo tiene una clase simple y simple, es probable que no necesite una interfaz, una superclase, una fábrica, etc.

Si y cuando notas que expandes la clase de una manera que engorda demasiado, entonces es el momento de destrozarla. En ese momento tiene mucho sentido pensar en cómo debería hacerlo.

Los patrones son una herramienta mental

Los patrones, o más específicamente el libro "Patrones de diseño" de la pandilla de cuatro, son geniales, entre otras razones, porque crean un lenguaje para que los desarrolladores piensen y hablen. Es fácil decir "observador", "fábrica" ​​o "fachada" y todos saben exactamente lo que significa, de inmediato.

Entonces, mi opinión sería que cada desarrollador debería tener un conocimiento pasajero sobre al menos los patrones en el libro original, simplemente para poder hablar sobre los conceptos de OO sin tener que explicar siempre los conceptos básicos. ¿Deberías usar los patrones cada vez que aparece una posibilidad? Probablemente no.

Bibliotecas

Las bibliotecas son probablemente el área donde puede estar para errar del lado de demasiadas opciones basadas en patrones en lugar de muy pocas. Cambiar algo de una clase "gorda" a algo con más patrones derivados (generalmente eso significa más y más pequeñas clases) cambiará radicalmente la interfaz; y eso es lo único que normalmente no desea cambiar en una biblioteca, porque es lo único que es de verdadero interés para el usuario de su biblioteca. No les importaría menos cómo manejas tu funcionalidad internamente, pero les importa mucho si constantemente tienen que cambiar su programa cuando haces una nueva versión con una nueva API.

AnoE
fuente
2

El punto de la abstracción debe ser ante todo el valor que se le brinda al consumidor de la abstracción, es decir, el cliente de la abstracción, los otros programadores y, a menudo, usted mismo.

Si, como cliente que consume las abstracciones, encuentra que necesita mezclar y combinar muchas abstracciones diferentes para realizar su trabajo de programación, entonces hay demasiadas abstracciones.

Idealmente, la estratificación debería reunir varias abstracciones inferiores y reemplazarlas con una abstracción simple y de nivel superior que sus consumidores puedan usar sin tener que lidiar con ninguna de esas abstracciones subyacentes. Si tienen que lidiar con las abstracciones subyacentes, entonces la capa tiene fugas (por estar incompleta). Si el consumidor tiene que lidiar con demasiadas abstracciones diferentes, tal vez faltan las capas.

Después de considerar el valor de las abstracciones para los programadores consumidores, podemos pasar a evaluar y considerar la implementación, como la de DRY-ness.

Sí, se trata de facilitar el mantenimiento, pero primero debemos considerar la difícil situación del mantenimiento de nuestros consumidores, proporcionando abstracciones y capas de calidad, y luego considerar facilitar nuestro propio mantenimiento en términos de aspectos de implementación como evitar la redundancia.


Ejemplo, cuando se escribe una capa de caché genérica usando Memcache. ¿Realmente necesitamos Memcache, MemcacheAdapter, MemcacheInterface, AbstractCache, CacheFactory, CacheConnector, ... o es más fácil de mantener y sigue siendo un buen código cuando se usa solo la mitad de esas clases?

Tenemos que mirar la perspectiva del cliente, y si sus vidas se hacen más fáciles, entonces es bueno. Si sus vidas son más complejas, entonces es malo. Sin embargo, podría ser que falta una capa que envuelva estas cosas en algo simple de usar. Internamente, estos pueden mejorar el mantenimiento de la implementación. Sin embargo, como sospecha, también es posible que simplemente esté sobredimensionado.

Erik Eidt
fuente
2

La abstracción está diseñada para hacer que el código sea más fácil de entender. Si una capa de abstracción va a hacer las cosas más confusas, no lo hagas.

El objetivo es utilizar el número correcto de abstracciones e interfaces para:

  • minimizar el tiempo de desarrollo
  • maximizar el mantenimiento del código

Resumen solo cuando sea necesario

  1. Cuando descubres que estás escribiendo una super clase
  2. Cuándo permitirá una reutilización significativa del código
  3. Si la abstracción hará que el código sea mucho más claro y fácil de leer

No abstraiga cuando

  1. Hacerlo no tendrá una ventaja en la reutilización de código o claridad
  2. Hacerlo hará que el código sea significativamente más largo / más complejo sin beneficio

Algunos ejemplos

  • Si solo va a tener un caché en todo su programa, no haga un resumen a menos que piense que es probable que termine con una superclase
  • Si tiene tres tipos diferentes de búferes, use una abstracción de interfaz común para todos ellos
sdfgeoff
fuente
2

Creo que esta podría ser una meta-respuesta controvertida, y llego un poco tarde a la fiesta, pero creo que es muy importante mencionar esto aquí, porque creo que sé de dónde vienes.

El problema con la forma en que se usan los patrones de diseño es que, cuando se les enseña, presentan un caso como este:

Tienes este escenario específico. Organiza tu código de esta manera. Aquí hay un ejemplo inteligente, pero un tanto artificial.

El problema es que cuando comienzas a hacer ingeniería real, las cosas no son tan simples. El patrón de diseño sobre el que leyó no se ajustará exactamente al problema que está tratando de resolver. Sin mencionar que las bibliotecas que está utilizando violan totalmente todo lo que se indica en el texto que explica esos patrones, cada uno de manera especial. Y como resultado, el código que escribe "se siente mal" y hace preguntas como esta.

Además de esto, me gustaría citar a Andrei Alexandrescu, cuando hablamos de ingeniería de software, quien afirma:

La ingeniería de software, tal vez más que cualquier otra disciplina de ingeniería, exhibe una rica multiplicidad: puede hacer lo mismo de muchas maneras correctas, y hay infinitos matices entre lo correcto y lo incorrecto.

Tal vez esto sea un poco exagerado, pero creo que esto explica perfectamente una razón adicional por la que podría sentirse menos seguro de su código.

En momentos como este, la voz profética de Mike Acton, líder del motor del juego en Insomniac, grita en mi cabeza:

CONOCE TUS DATOS

Él está hablando de las entradas a su programa y los resultados deseados. Y luego está esta joya de Fred Brooks del Mes del Hombre Mítico:

Muéstrame tus diagramas de flujo y oculta tus tablas, y seguiré desconcertado. Muéstrame tus tablas, y generalmente no necesitaré tus diagramas de flujo; Serán obvios.

Entonces, si fuera usted, razonaría sobre mi problema en función de mi caso típico de entrada y si logra la salida correcta deseada. Y haz preguntas como esta:

  • ¿Son correctos los datos de salida de mi programa?
  • ¿Se produce de manera eficiente / rápida para mi caso de entrada más común?
  • ¿Es mi código lo suficientemente fácil como para razonar localmente, tanto para mí como para mis compañeros de equipo? Si no es así, ¿puedo simplificarlo?

Cuando haces eso, la pregunta de "cuántas capas de abstracción o patrones de diseño se necesitan" se vuelve mucho más fácil de responder. ¿Cuántas capas de abstracción necesitas? Tantas como sea necesario para lograr estos objetivos, y no más. "¿Qué pasa con los patrones de diseño? ¡No he usado ninguno!" Bueno, si los objetivos anteriores se lograron sin la aplicación directa de un patrón, entonces está bien. Haz que funcione y pasa al siguiente problema. Comience desde sus datos, no desde el código.

nasser-sh
fuente
2

La arquitectura de software está inventando idiomas

Con cada capa de software, crea el lenguaje en el que usted (o sus compañeros de trabajo) desea expresar su próxima solución de capa superior (por lo tanto, incluiré algunos análogos de lenguaje natural en mi publicación). Sus usuarios no quieren pasar años aprendiendo a leer o escribir ese idioma.

Esta vista me ayuda a la hora de decidir sobre cuestiones arquitectónicas.

Legibilidad

Ese lenguaje debe entenderse fácilmente (haciendo que el código de la siguiente capa sea legible). El código se lee con mucha más frecuencia que lo escrito.

Un concepto debe expresarse con una palabra: una clase o interfaz debe exponer el concepto. (Los idiomas eslavos suelen tener dos palabras diferentes para un verbo en inglés, por lo que debe aprender el doble del vocabulario. Todos los idiomas naturales usan palabras simples para conceptos múltiples).

Los conceptos que expongas no deben contener sorpresas. Eso es principalmente nombrar convenciones como métodos get y set, etc. Y los patrones de diseño pueden ayudar porque proporcionan un patrón de solución estándar, y el lector ve "OK, obtengo los objetos de una Fábrica" ​​y sabe lo que eso significa. Pero si simplemente hacer una instancia de una clase concreta hace el trabajo, preferiría eso.

Usabilidad

El lenguaje debe ser fácil de usar (facilitando la formulación de "oraciones correctas").

Si todas estas clases / interfaces MemCache se vuelven visibles para la siguiente capa, eso crea una curva de aprendizaje abrupta para el usuario hasta que comprenda cuándo y dónde usar cuál de estas palabras para el concepto único de caché.

Exponer solo las clases / métodos necesarios hace que sea más fácil para su usuario encontrar lo que necesita (consulte la cita de DocBrowns de Antoine de Saint-Exupery). Exponer una interfaz en lugar de la clase implementadora puede facilitarlo.

Si expone una funcionalidad donde se puede aplicar un patrón de diseño establecido, es mejor seguir ese patrón de diseño que inventar algo diferente. Su usuario comprenderá las API siguiendo un patrón de diseño más fácilmente que un concepto completamente diferente (si sabe italiano, el español será más fácil para usted que el chino).

Resumen

Introduzca abstracciones si eso facilita el uso (y vale la pena la sobrecarga de mantener tanto la abstracción como la implementación).

Si su código tiene una subtarea (no trivial), resuélvala "de la manera esperada", es decir, siga el patrón de diseño apropiado en lugar de reinventar un tipo diferente de rueda.

Ralf Kleberhoff
fuente
1

Lo importante a tener en cuenta es cuánto necesita saber el código consumidor que realmente maneja su lógica de negocios acerca de estas clases relacionadas con el almacenamiento en caché. Idealmente, su código solo debería preocuparse por el objeto de caché que desea crear y tal vez una fábrica para crear ese objeto si un método de construcción no es suficiente.

El número de patrones utilizados o el nivel de herencia no son demasiado importantes siempre que cada nivel pueda justificarse para otros desarrolladores. Esto crea un límite informal ya que cada nivel adicional es más difícil de justificar. La parte más importante es cuántos niveles de abstracción se ven afectados por los cambios en los requisitos funcionales o comerciales. Si puede hacer un cambio a un solo nivel para un solo requisito, entonces es probable que no esté demasiado abstraído o mal abstraído, si cambia el mismo nivel para múltiples cambios no relacionados, es probable que no esté resumido y necesite separar más preocupaciones.

Ryathal
fuente
-1

Primero, la cita de Twitter es falsa. Los nuevos desarrolladores necesitan hacer un modelo, las abstracciones generalmente los ayudarían a "hacerse una idea". Siempre que las abstracciones tengan sentido, por supuesto.

En segundo lugar, su problema no es demasiadas o muy pocas abstracciones, es que aparentemente nadie puede decidir sobre estas cosas. Nadie posee el código, no se implementa un solo plan / diseño / filosofía, cualquier otro tipo puede hacer lo que parezca adecuado para ese momento. Sea cual sea el estilo que elija, debería ser uno.

Martin Maat
fuente
2
Evitemos descartar la experiencia como "falsa". Demasiadas abstracciones son un problema real. Lamentablemente, la gente agrega abstracción por adelantado, porque es "la mejor práctica", en lugar de resolver un problema real. Además, nadie puede decidir "sobre estas cosas" ... la gente deja las empresas, las personas se unen, nadie se apropia de su lodo.
Rawkode