Discusiones de simplicidad.

8

Recientemente en mi empresa hemos tenido un pequeño debate sobre abstracción versus simplicidad. Una escuela de pensamiento que caracterizaría como "SECO y la abstracción no puede hacer daño", y lleva a un código como este:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

y esto:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

Mi punto de vista es que crear una abstracción como esta, que solo se usa en un lugar, hace que el código sea menos legible, ya que está reemplazando una llamada de función con la que el lector está familiarizado (gsub) por otra que nunca ha visto antes (escape_space), que deberán leer si quieren entender cómo funciona realmente el código. El reemplazo se describe esencialmente en inglés ("espacio de escape") y el inglés es notoriamente vago. Por ejemplo, sin mirar la definición, no sabes si se escapa todo el espacio en blanco, o solo el carácter de espacio.

Hay muchos escritos que cantan alabanzas a DRY y abstracción. ¿Alguien está al tanto de las fuentes que describen los límites de la abstracción? ¿Que cantan alabanzas y discuten la pragmática de mantener el código simple?

Editar: Puedo encontrar textos que fomenten la simplicidad en la vida o en la escritura (en inglés), por ejemplo, "Simplifica, simplifica" de Thoreau. o "La escritura vigorosa de Strunk y White es concisa". ¿Dónde está el equivalente para programar?

Martin C. Martin
fuente
1
Recomiendo leer "Clean Code" de Robert C. Martin, si no lo ha hecho. Si es así, lea los primeros capítulos con más cuidado.
Bruno Schäpper
Recomiendo ver Simple Made Easy para una discusión en profundidad sobre cómo escribir código simple.
dan_waterworth
3
No soy fanático del "Código limpio". Demasiado dogmático.
Plataforma

Respuestas:

9

Claro: Joel Spolsky (puede que hayas oído hablar de él) dice :

Todas las abstracciones no triviales, hasta cierto punto, tienen fugas.

Las abstracciones fallan. A veces un poco, a veces mucho. Hay fugas Las cosas van mal. Ocurre por todas partes cuando tienes abstracciones.

También, vea KISS y YAGNI que Jeff Atwood discute :

Como desarrolladores, creo que también tendemos a ser demasiado optimistas al evaluar la generalidad de nuestras propias soluciones, y así terminamos construyendo [soluciones] elaboradas en torno a cosas que pueden no justificar ese nivel de complejidad. Para combatir este impulso, sugiero seguir la doctrina YAGNI (No lo vas a necesitar). Cree lo que necesita según lo necesite, refactorizando agresivamente a medida que avanza; No pase mucho tiempo planeando escenarios futuros grandiosos y desconocidos. Un buen software puede evolucionar hacia lo que finalmente se convertirá.

(ver el enlace para la cotización exacta)

Mi interpretación de estas citas:

  1. Es realmente difícil crear buenas abstracciones: es mucho más fácil crear cosas malas.

  2. Si agrega una abstracción para la que no hay necesidad, o que es incorrecta, ahora tiene un código adicional que necesita ser probado, depurado y mantenido. Y si tiene que regresar y refactorizar, ahora tiene más peso muerto que lo arrastra hacia abajo.


fuente
1
Buenas fuentes +1
KChaloux
7

No estoy familiarizado con el lenguaje o el tiempo de ejecución de sus ejemplos, pero gsubno significa nada para mí. Sin embargo, escape_spacees mucho más significativo. Estoy completamente en desacuerdo con su argumento de que las llamadas a funciones familiares no deberían reemplazarse por llamadas a funciones que nunca antes habían visto . Si tuviera que llevar este argumento a los extremos, nunca se debería agregar una nueva función a un programa: siempre abstrae las llamadas de funciones familiares y siempre las reemplaza con esta nueva llamada desconocida definida por el usuario.

Un desarrollador nunca debería tener que leer el código para comprender cómo funciona. En primer lugar, él o ella no debería tener que preocuparse por cómo funciona, sino que debería ser capaz de comprender cómo usarlo y qué efectos tiene. Si este no es el caso, entonces hay un problema con el nombre y la firma de la función, o su documentación.

Para mí, el objetivo del resumen es eliminar los detalles internos y proporcionar una interfaz más limpia para algunas funciones. En el futuro, el funcionamiento interno de la escape_spacefunción podría modificarse o anularse. No me importa que alguna función gsubse llame con dos argumentos. Me importa que mis espacios se escapen. Eso hace que la abstracción sea útil.

En mi lenguaje favorito, C #, siempre agrego documentación a todas mis funciones (incluso las privadas) que describen cosas como la función, el uso, las unidades usadas, las excepciones lanzadas, el contrato de entrada y los tipos. Luego, con IntelliSense de Visual Studio, cualquier desarrollador puede ver más claramente lo que hace la función sin leer el código . Sin una herramienta como IntelliSense, los desarrolladores tendrán que ser más específicos en sus nombres de funciones, o mantener documentación adicional en algún lugar fácilmente accesible.

No creo que las abstracciones deban ser limitadas, y no conozco esa fuente.

Daniel AA Pelsmaeker
fuente
2
El lenguaje que está usando es Ruby, y gsub es una función muy utilizada de la clase String. Sería difícil encontrar a alguien que haya escrito más que un pequeño Ruby que no sepa de inmediato que gsub se usa como "buscar y reemplazar todo".
KChaloux
@KChaloux También se puede adivinar para usuarios que no son Ruby: además del contexto (los argumentos pasados), se ve como 'g' + "sustituto" - y en comandos sustitutos (por ejemplo, en sedy vim :s), 'g' es utilizado como "todos"
Izkata
Sí, supongo que reemplazó algo. Pero no me gusta adivinar qué hace una función. Debe ser un artefacto de épocas anteriores, pero los nombres de función gustaría gsub, atoiy wcstombsson mucho peores de lo nombrado escape_space.
Daniel AA Pelsmaeker
2
@Virtlink Por otro lado, gsub es un comando común. Cuando vea "something".gsub(' ', '\ ')que sabe exactamente qué salida está obteniendo. Escapar de espacios no es algo que veo muy a menudo. ¿Está usando el carácter `\` o algún otro carácter de escape? ¿Se escapa de las nuevas líneas? Sin mencionar que es un parche de la clase String predeterminada, que algunos desarrolladores consideran una mala práctica.
KChaloux
5

Cuando hablamos de abstracciones, creo que es importante definir dos términos: complejidad inherente e incidental. La complejidad inherente es la complejidad del problema que la abstracción está resolviendo, la complejidad incidental es la complejidad que la abstracción está ocultando.

Las buenas abstracciones son aquellas que ocultan mucha complejidad incidental y tratan de resolver los problemas correctos, disminuyendo su complejidad inherente. Creo que con lo que te estás frustrando aquí son abstracciones demasiado superficiales; no ocultan mucha complejidad incidental y es tan difícil (si no más) comprender las abstracciones como comprender sus implementaciones.

dan_waterworth
fuente
2

Mi respuesta va a ir más allá de la experiencia personal, pero una búsqueda rápida encontró este blog , que parece que entra en bastante detalles sobre cuándo la abstracción va demasiado lejos (no solo la entrada vinculada, sino que hay varias relacionadas).

Básicamente, la abstracción puede ser algo bueno. Sin embargo, como cualquier otra cosa, uno puede dejarse llevar. Sus ejemplos son buenos donde la abstracción ha ido demasiado lejos. Estás creando "pasadas" que básicamente no hacen más que cambiar el nombre de la función en cuestión.

Ahora, en el caso de las funciones personalizadas, esto podría ser necesario, si está buscando desaprobar un antiguo esquema de nomenclatura en favor de uno nuevo (como cambiar escape_space a escapeSpace o escWhitespace, o lo que sea). ¿Pero para las funciones de biblioteca? Exceso Sin mencionar contraproducente. ¿Qué has ganado haciendo esa abstracción? Si eso es todo lo que está haciendo, entonces todo lo que ha ganado es el dolor de cabeza de tener que recordar esa función y transmitir ese conocimiento a otra persona (mientras que, con las funciones de biblioteca, los desarrolladores ya las conocen o deberían poder escribir ruby gsuben Google y descubre lo que significa).

Recomiendo ser más inteligente para determinar cuándo abstraer algo. Por ejemplo, las cosas que son más de 2-3 líneas, y se usan al menos dos veces (incluso si existen pequeñas diferencias, muchas veces, esas diferencias pueden ser parametrizadas), generalmente son buenos candidatos para la abstracción. A veces, consideraré grandes bloques de código que hacen una cosa en particular dentro de una función más grande, en aras de la legibilidad y el principio de "un solo trabajo", y los resumiré en funciones protegidas dentro de la misma clase (a menudo en la inmediata proximidad de la función que lo utiliza).

Además, no olvides YAGNI (no lo vas a necesitar) y la optimización prematura. ¿Se está usando actualmente más de una vez? ¿No? Entonces no se preocupe por resumirlo ahora (a menos que hacerlo cree una ganancia de legibilidad sustancial). "¡Pero podríamos necesitarlo más tarde!" Luego, resúmalo cuando realmente lo necesites en otro lugar. Hasta entonces, terminas con punteros a punteros a punteros sin ningún beneficio real.

En el otro lado de esta moneda, cuando abstraes algo, hazlo tan pronto como la necesidad se haga evidente. Es más fácil mover y ajustar dos o tres bloques de código que 15. En términos generales, tan pronto como me encuentro copiando bloques de código de otro lugar, o escribiendo algo que me resulta muy familiar, empiezo a pensar en cómo abstraerlo.

Shauna
fuente
2
Hace algunos años, estuve totalmente de acuerdo con usted y trabajé tal como lo describe. En el medio, leí algunas cosas de Robert Martin y comencé a repensar mi forma de programar. Construir funciones muy pequeñas con buenos nombres, incluso cuando la función se usa una vez, tiene una ventaja real: necesita muchos menos comentarios. Y le ayuda a mantener las llamadas de esa función más simples y legibles. Eso definitivamente no es "optimización prematura" (Knuth estaba hablando del rendimiento, no de la calidad del código). YAGNI debería considerarse, por supuesto, pero las buenas herramientas de refactorización hicieron que tales cambios sean muy fáciles hoy en día.
Doc Brown
1
@DocBrown - Creo que podemos estar más de acuerdo de lo que piensas. Probablemente podríamos hablar durante horas o días sobre los pros y los contras de la abstracción. Intenté ser más conciso que eso, pero mencioné su preocupación cuando mencioné la legibilidad y el principio de "un solo trabajo" / responsabilidad única. Cuando generalmente trazo la línea es cuando una función simplemente está actuando como un paso para otra función (que, para mí, en la mayoría de los casos, es abstracción por el bien de la abstracción).
Shauna
2

Primero, eche un vistazo a este ejemplo de Robert C. Martin:

http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop

La razón de este tipo de refactorización es, en consecuencia, construir abstracciones para crear funciones muy pequeñas, de modo que cada función haga una sola cosa . Eso lleva a funciones como las que nos mostró anteriormente. Creo que esto es bueno: le ayuda a crear código donde cada función llamada dentro de otra función está en el mismo nivel de abstracción.

Si cree que usar nombres como make_foo_binaryo escape_spacehace que el código sea menos legible que como consecuencia, debería intentar elegir mejores nombres (por ejemplo:) en escape_space_chars_with_backslashlugar de abandonar la abstracción.

Doc Brown
fuente