Abstracción: La guerra entre resolver el problema y una solución general [cerrado]

33

Como programador, me encuentro en el dilema donde quiero hacer que mi programa sea lo más abstracto y general posible.

Hacerlo generalmente me permitiría reutilizar mi código y tener una solución más general para un problema que podría (o no) aparecer nuevamente.

Entonces esta voz en mi cabeza dice, ¡solo resuelve el problema, tonto, así de fácil! ¿Por qué pasar más tiempo del necesario?

De hecho, todos nos hemos enfrentado a esta pregunta en la que Abstracción está en tu hombro derecho y Solve-it-stupid se sienta a la izquierda.

¿Cuál escuchar y con qué frecuencia? ¿Cuál es tu estrategia para esto? ¿Deberías abstraerlo todo?

Bryan Harrington
fuente
1
"Como resumen y como general" se conoce generalmente como la arrogancia del programador :) Sin embargo, debido a la ley de Murphy, el único grado de adaptación que necesitará cuando trabaje en la próxima tarea será uno que no haya proporcionado.
Matthieu M.
1
¡Una de las mejores preguntas!
explorest
1
Siempre adivina mal, así que hágalo simple. Cuando ha expandido los casos simples "suficientes veces" comienza a ver un patrón. Hasta entonces, no lo hagas.

Respuestas:

27

¿Cuál escuchar y con qué frecuencia?

Nunca haga un resumen hasta que deba hacerlo.

En Java, por ejemplo, debe usar interfaces. Son una abstracción.

En Python no tienes interfaces, tienes Duck Typing y no necesitas altos niveles de abstracción. Entonces simplemente no lo haces.

¿Cuál es tu estrategia para esto?

No resumas hasta que lo hayas escrito tres veces.

Una vez es, bueno, una vez. Solo resuélvelo y sigue adelante.

Dos veces es la indicación de que puede haber un patrón aquí. O tal vez no. Puede ser solo una coincidencia.

Tres veces es el comienzo de un patrón. Ahora trasciende la coincidencia. Ahora puede abstraer con éxito.

¿Deberías abstraerlo todo?

No.

De hecho, nunca debes abstraer nada hasta que tengas una prueba absoluta de que estás haciendo el tipo correcto de abstracción. Sin la "Regla de las tres repeticiones", escribirás cosas que son inútilmente abstraídas de una manera inútil.

Hacerlo generalmente me permitiría reutilizar mi código

Esta es una suposición que a menudo es falsa. La abstracción puede no ayudar en absoluto. Se puede hacer mal. Por lo tanto, no lo hagas hasta que debas hacerlo.

S.Lott
fuente
1
"Nunca abstraigas hasta que debas". - Supongo que evitas el uso de clases, métodos, bucles, o si las declaraciones? Estas cosas son todas abstracciones. Si lo piensa, el código escrito en lenguajes de alto nivel es muy abstracto, le guste o no. La pregunta no es si hacer un resumen. Es qué abstracciones usar.
Jason Baker
9
@ Jason Baker: "Supongo que evitas el uso de clases, métodos, bucles o declaraciones if". Esa no parece ser la cuestión. ¿Por qué hacer la absurda afirmación de que toda la programación es imposible si evitamos abstraer demasiado nuestros diseños?
S.Lott
1
Me recuerda el viejo chiste donde un hombre le pregunta a una mujer si ella se acostará con él por un millón de dólares. Cuando ella dice que sí, él le pregunta si ella se acostará con él por cinco dólares. Cuando ella dice "¿Por qué tipo de mujer me tomas?" la respuesta es "Bueno, ya hemos establecido que te acostarás con alguien para tener relaciones sexuales. Ahora solo estamos regateando sobre el precio". Mi punto es que nunca se trata de una decisión de abstraer o no. Se trata de cuánto abstraer y qué abstracciones elegir. No puede optar por retrasar la abstracción a menos que esté escribiendo un ensamblaje puro.
Jason Baker, el
9
@ Jason Baker: "No puede optar por retrasar la abstracción a menos que esté escribiendo un ensamblaje puro" Eso es trivialmente falso. El ensamblaje es una abstracción sobre el lenguaje de máquina. Lo cual es una abstracción sobre los circuitos de hardware. Por favor, deje de leer demasiado la respuesta que no estaba presente en cuestión en primer lugar. La pregunta no era sobre "Abstracción" como concepto o herramienta intelectual. Se trataba de la abstracción como una técnica de diseño OO, mal aplicada, con demasiada frecuencia. Mi respuesta no fue que la abstracción es imposible. Pero ese "exceso de abstracción" es malo.
S.Lott
1
@JasonBaker no es una razón desconocida para acostarse con alguien. Tal vez estabas pensando en "dinero"?
19

Ah, YAGNI. El concepto de programación más abusado.

Hay una diferencia entre hacer que su código sea genérico y hacer un trabajo extra. ¿Debería dedicar más tiempo a acoplar su código sin apretarlo y adaptarlo fácilmente para hacer otras cosas? Absolutamente. ¿Debería pasar tiempo implementando funcionalidades innecesarias? No. ¿Debería pasar tiempo haciendo que su código funcione con otro código que aún no se ha implementado? No.

Como dice el refrán "Haz lo más simple que pueda funcionar". El caso es que la gente siempre confunde lo simple con lo fácil . La simplicidad requiere trabajo. Pero vale la pena el esfuerzo.

¿Puedes ir por la borda? Por supuesto. Pero mi experiencia es que pocas compañías necesitan más de una actitud de "hazlo ahora" de lo que ya tienen. La mayoría de las empresas necesitan más personas que piensen las cosas con anticipación. De lo contrario, siempre terminan teniendo un problema autosuficiente en el que nadie tiene tiempo para nada, todos siempre tienen prisa y nadie hace nada.

Piénselo de esta manera: es muy probable que su código se vuelva a usar de alguna manera. Pero no vas a predecir correctamente de esa manera. Así que haga su código limpio, abstracto y poco acoplado. Pero no intente hacer que su código funcione con ninguna pieza específica de funcionalidad que aún no exista. Incluso si sabes que existirá en el futuro.

Jason Baker
fuente
Bonita distinción entre simpley easy!
Matthieu M.
"Pero mi experiencia es que pocas compañías" - curiosamente, lo contrario puede ser cierto en el mundo académico.
Steve Bennett
12

Un silogismo:

  1. La generalidad es cara.

  2. Estás gastando el dinero de otras personas.

  3. Por lo tanto, el gasto de generalidad debe justificarse ante los interesados.

Pregúntese si realmente está resolviendo el problema más general para ahorrar dinero a las partes interesadas más adelante, o si le parece un desafío intelectual hacer una solución innecesariamente general.

Si se determina que la generalidad es deseable, entonces debe diseñarse y probarse como cualquier otra característica . Si no puede escribir pruebas que demuestren cómo la generalidad que ha implementado resuelve un problema requerido por la especificación, ¡no se moleste en hacerlo! Una característica que no cumple con los criterios de diseño y que no se puede probar es una característica en la que nadie puede confiar.

Y finalmente, no existe tal cosa como "lo más general posible". Supongamos que escribe software en C #, solo por el argumento. ¿Vas a hacer que cada clase implemente una interfaz? ¿Cada clase es una clase base abstracta con cada método un método abstracto? Eso es bastante general, pero no es "tan general como sea posible". Eso solo permite a las personas cambiar la implementación de cualquier método a través de subclases. ¿Qué pasa si quieren cambiar la implementación de un método sin subclasificar? Puede hacer que cada método sea en realidad una propiedad de tipo delegado, con un setter para que las personas puedan cambiar cada método por otro. ¿Pero qué pasa si alguien quiere agregar más métodos? Ahora cada objeto tiene que ser expandible.

En este punto, debe abandonar C # y adoptar JavaScript. Pero aún no has llegado a un lugar lo suficientemente general; ¿Qué pasa si alguien quiere cambiar cómo funciona la búsqueda de miembros para este objeto en particular? Tal vez deberías escribir todo en Python en su lugar.

El aumento de la generalidad a menudo significa abandonar la previsibilidad y aumentar enormemente los costos de prueba para garantizar que la generalidad implementada realmente tenga éxito en satisfacer una necesidad real del usuario . ¿Están esos costos justificados por sus beneficios para las partes interesadas que pagan por ellos? Quizás lo son; eso depende de usted para discutir con las partes interesadas. Mis partes interesadas no están en absoluto dispuestas a que abandone la escritura estática en busca de un nivel de generalidad totalmente innecesario y extremadamente costoso, pero tal vez el suyo sí lo esté.

Eric Lippert
fuente
2
+1 para principios útiles, -1 para hacerlo todo sobre dinero.
Mason Wheeler
44
@Mason: No se trata de dinero , se trata de esfuerzo . El dinero es una medida práctica de esfuerzo . La eficiencia son los beneficios acumulados por el esfuerzo producido; nuevamente, la ganancia en dinero es una medida práctica de los beneficios acumulados . Simplemente es más fácil discutir la abstracción del dinero que encontrar alguna forma de medir el esfuerzo, el costo y el beneficio. ¿Preferiría medir el esfuerzo, el costo y el beneficio de alguna otra manera? Siéntase libre de hacer una propuesta de una mejor manera.
Eric Lippert el
2
Estoy de acuerdo con el punto de @Mason Wheeler. Creo que la solución aquí es no involucrar a las partes interesadas en ese nivel de un proyecto. Obviamente, depende en gran medida de la industria, pero los clientes no suelen ver el código. Además, todas estas respuestas parecen ser anti-esfuerzo, parece vago.
Orbling
2
@Orbling: Me opongo firmemente al esfuerzo innecesario, costoso y derrochador que quita recursos de proyectos importantes e importantes. Si está sugiriendo que esto me hace "flojo", entonces le sugiero que está usando la palabra "flojo" de una manera inusual.
Eric Lippert
1
En mi experiencia, otros proyectos no tienden a desaparecer, por lo que generalmente vale la pena invertir tanto tiempo como sea necesario en el proyecto actual antes de continuar.
Orbling
5

Para ser claros, hacer algo generalizado e implementar una abstracción son dos cosas completamente diferentes.

Por ejemplo, considere una función que copia la memoria.

La función es una abstracción que oculta cómo se copian los 4 bytes.

int copy4Bytes (char * pSrc, char * pDest)

Una generalización sería hacer que esta función copie cualquier número de bytes.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

La abstracción se presta para su reutilización, mientras que la generalización solo hace que la abstracción sea útil en más casos.

Más específicamente relacionado con su pregunta, la abstracción no solo es útil desde una perspectiva de reutilización de código. Muchas veces, si se hace correctamente, hará que su código sea más legible y fácil de mantener. Usando el ejemplo anterior, ¿qué es más fácil de leer y entender si está hojeando el código copyBytes () o un bucle for iterando a través de un conjunto de datos en movimiento índice por índice? La abstracción puede proporcionar una especie de auto documentación que, en mi opinión, hace que el código sea más fácil de trabajar.

Como regla general, si puedo encontrar un buen nombre de función que describa exactamente lo que pretendo que haga un fragmento de código, entonces escribo una función para él, independientemente de si creo que lo volveré a usar o no.

Pemdas
fuente
+1 por marcar la diferencia entre abstracción y generalización.
Joris Meys
4

Una buena regla general para este tipo de cosas es Cero, Uno, Infinito. Es decir, si necesita lo que está escribiendo más de una vez, puede asumir que lo necesitará aún más veces y generalizar. Esta regla implica que no te molestes con la abstracción la primera vez que escribes algo.

Otra buena razón para esta regla es que la primera vez que escribe el código, no necesariamente sabrá qué abstraer porque solo tiene un ejemplo. La Ley de Murphy implica que si escribe código abstracto la primera vez, el segundo ejemplo tendrá diferencias que no anticipó.

Larry Coleman
fuente
1
Eso se parece mucho a mi versión de la Regla de refactorización: ¡dale dos! refactor!
Frank Shearar
@Frank: Probablemente sea tu regla de refactorización, pero con el segundo párrafo agregado por experiencia personal.
Larry Coleman
+1: Creo que esto también se nota bastante temprano en The Pragmatic Programmer casi literalmente IIRC.
Steven Evers
oh definitivamente No hay nada que abstraer con un solo ejemplo: tan pronto como tenga un segundo ejemplo, puede ver lo que es común (compartido) y lo que no, ¡y puede abstraerse!
Frank Shearar
Una muestra de dos es, en mi opinión, demasiado pequeña para generalizar hasta el infinito. En otras palabras, demasiado, demasiado temprano.
2

Eric Lippert señala tres cosas que creo que se aplican en su artículo a prueba de futuro de un diseño . Creo que si lo sigues estarás en buena forma.

Primero: la generalidad prematura es costosa.

Segundo: Representa en tu modelo solo aquellas cosas que siempre están en el dominio del problema y cuyas relaciones de clase no cambian.

Tercero: mantenga sus políticas alejadas de sus mecanismos.

Conrad Frix
fuente
1

Depende de por qué está codificando, el propósito de su proyecto. Si el valor de su código es que resuelve un problema concreto, entonces desea hacerlo y pasar al siguiente problema. Si hay cosas rápidas y fáciles que puede hacer para facilitar las cosas para los futuros usuarios del código (incluido usted mismo), entonces, por todos los medios, haga ajustes razonables.

Por otro lado, hay casos en los que el código que está escribiendo tiene un propósito más genérico. Por ejemplo, cuando está escribiendo una biblioteca para otros programadores, que la usarán en una amplia variedad de aplicaciones. Los usuarios potenciales son desconocidos y no puede preguntarles exactamente qué quieren. Pero desea que su biblioteca sea ampliamente útil. En tales casos, pasaría más tiempo tratando de apoyar la solución general.

Rob Weir
fuente
0

Soy un gran admirador del principio KISS.

Me concentro en proporcionar lo que me piden que haga, y no en cuál es la mejor solución. Tuve que abandonar el perfeccionismo (y el TOC), porque me hizo sentir miserable.

Pablo
fuente
¿Por qué la aversión al perfeccionismo? (Por cierto
rechacé
No apuntar a la perfección funciona para mí. ¿Por qué? Porque sé que mis programas nunca serán perfectos. No existe una definición estándar de perfección, así que solo elijo proporcionar algo que funcione bien.
Pablo
-1

donde Abstracción está en tu hombro derecho y Solve-it-stupid se sienta a la izquierda.

No estoy de acuerdo con "resolverlo estúpido" , creo que puede ser más "resolverlo de manera inteligente" .

¿Qué es más inteligente?

  • Escribir una solución generalizada compleja que pueda soportar múltiples casos
  • Escribir código corto y eficiente que resuelve el problema a la mano y es fácil de mantener, y que puede ser ampliada en el futuro SI se requiere.

La opción predeterminada debería ser la segunda. A menos que pueda demostrar una necesidad generacional.

La solución generalizada solo debe ser cuando se sabe que es algo que se utilizará en múltiples proyectos / casos diferentes.

Por lo general, encuentro que las soluciones generalizadas se sirven mejor como "Código de biblioteca principal"

Noche oscura
fuente
Si pudiera hacer todas las suposiciones aquí, no tendría ningún problema. Cortos y fáciles de mantener son mutuamente excluyentes. Si sabe que algo se reutilizará, la solución general abordaría eso.
JeffO
@Jeff No estoy muy seguro de entender tu comentario ...
Darknight
Sacaste el "Solucionarlo estúpido" fuera de contexto.
Bryan Harrington