4 puntos por GN⁺ 2023-12-09 | 1 comentarios | Compartir por WhatsApp
  • A través de la experiencia de reescribir el código de redimensionamiento de un editor gráfico enfocándose en eliminar duplicación, y luego tener que revertirlo, muestra que la obsesión por el código limpio puede perjudicar la facilidad real para hacer cambios
  • La implementación existente repetía fórmulas por manejador para Rectangle, Oval, Header y TextBlock, y la nueva implementación intentaba eliminar la duplicación separando dirección y figura para combinarlas
  • Justo después de la refactorización, el tamaño del código se redujo a la mitad y los puntos de cambio quedaron concentrados en un solo lugar, pero si después se necesitaban comportamientos especiales por figura o por manejador, la abstracción podía terminar siendo un obstáculo
  • Un problema mayor que el juicio técnico fue haber hecho check-in directamente en master sin consultar al colega que había escrito el código original, lo que puede dañar la construcción de confianza del equipo
  • La “limpieza” no es un objetivo, sino solo una herramienta para lidiar con sistemas complejos; más importante que la forma del código es si el equipo puede responder al cambio en conjunto

Repetición en el código de redimensionamiento de un editor gráfico

  • En el lienzo de un editor gráfico había código para redimensionar figuras como rectángulos y óvalos mediante pequeños manejadores en los bordes
  • La implementación funcionaba, pero se repetían las fórmulas para calcular posición y tamaño según la figura y la dirección del manejador
    • Rectangle tenía métodos como resizeTopLeft, resizeTopRight, resizeBottomLeft, resizeBottomRight
    • Oval tenía métodos como resizeLeft, resizeRight, resizeTop, resizeBottom
    • Header y TextBlock también tenían sus propios métodos de redimensionamiento
  • Si el usuario presionaba Shift, también había que manejar la conservación de la proporción durante el redimensionamiento, lo que agregaba todavía más fórmulas

Abstracción para eliminar duplicación

  • La repetición parecía darse en dos ejes
    • Se repetía entre manejadores de la misma dirección: Oval.resizeLeft() y Header.resizeLeft() son ambos acciones de arrastrar el manejador izquierdo
    • Se repetía entre métodos dentro de la misma figura: varios métodos de Oval tratan todos con óvalos
  • La nueva estructura intentaba dividir el código en Directions y Shapes para combinar comportamientos por dirección y comportamientos por figura
    • En Directions estaban top, left, bottom, right
    • En Shapes estaban Oval, Rectangle
    • Se combinaban manejadores y figuras con createHandle, createBox
  • Con este enfoque se creaban configuraciones de manejadores como fourCorners, fourSides, twoSides, y se usaban para generar Rectangle, Oval, Header, TextBlock

La ilusión justo después de refactorizar

  • Después de la refactorización, el tamaño total del código se redujo a la mitad y la duplicación parecía haber desaparecido
  • Se asumía que, al cambiar el comportamiento de una dirección o figura específica, bastaría con modificar un solo lugar en vez de varios métodos
  • Hizo check-in del cambio en master tarde por la noche, pensando que había desenredado limpiamente el código desordenado de un colega

El problema que apareció al día siguiente

  • Al día siguiente, su jefe le pidió revertir el cambio, y en ese momento le resultó difícil aceptarlo porque creía que el nuevo código era más limpio que el anterior
  • Con el tiempo, vio que ese juicio era correcto por dos razones
    • Había reescrito el código y hecho check-in sin hablar con la persona que lo había escrito originalmente
    • A cambio de reducir la duplicación, había sacrificado la facilidad para cambiar requisitos
  • Un equipo de ingeniería sano debe seguir construyendo confianza, y una reescritura grande del código de un colega puede golpear con fuerza la colaboración

El costo de una abstracción equivocada

  • Más adelante hicieron falta muchos casos especiales y comportamientos para varios manejadores de varias figuras
  • En la nueva estructura de abstracción, acomodar esos requisitos podría haber vuelto el código varias veces más complejo
  • En cambio, la estructura original “desordenada” tenía cada método separado, por lo que era relativamente fácil aplicar esos cambios
  • Eliminar duplicación en el código no es gratis: al ganar una capacidad, se puede perder otra

Repensar la “limpieza”

  • Muchos desarrolladores pueden pasar por una etapa de obsesión con el “código limpio” y la eliminación de duplicación
  • Cuando uno no tiene confianza en el código que escribe, es fácil sentirse orgulloso de cosas medibles como reglas estrictas de lint, convenciones de nombres, estructura de archivos o ausencia de duplicación
  • Al aprender a crear abstracciones, cada vez que se ve código repetido puede surgir la tentación de creer que la abstracción es una virtud y aplicarla
  • Hay que pensar en profundidad con qué resultados reales de ingeniería se conectan esas sensaciones de “limpio” o “sucio”
  • No solo importa cómo se ve el código, sino cómo el código cambia y se modifica junto con las personas del equipo

Cuándo soltar el código limpio

  • El código limpio no es un objetivo, sino un intento de entender sistemas complejos
  • Puede servir como mecanismo de defensa que orienta cuando no hay certeza sobre el impacto que tendrá un cambio en la base de código
  • Al principio, puede ser placentera la experiencia de simplificar código complejo extrayendo funciones o refactorizando clases
  • Pero no hay que quedarse ahí y convertirse en un “fanático del código limpio”
  • Que el código limpio sirva de guía, pero en algún momento hay que soltarlo

1 comentarios

 
GN⁺ 2023-12-09
Opiniones de Hacker News
  • La duplicación de código a veces está bien, pero eso no prueba que el código limpio sea malo.
    Parece que se fueron un poco de más con el refactoring; si solo hubieran extraído a una función esas “10 líneas de matemáticas repetidas”, habría quedado más limpio.
    El compañero que no convirtió esas 10 líneas en una función tampoco hizo lo correcto, así que yo habría rechazado el PR, pero no lo habría reescrito personalmente.
    Reescribir el PR de otra persona es un insulto rápido que cierra la posibilidad de discutir, y muchas veces uno no conoce el contexto en el que se escribió ese código.
    Si no hay estándares de codificación, el PR se debe revisar y rechazar; si se deja que se haga commit con la idea de “el código limpio no importa”, en pocos meses se vuelve una base de código en la que nadie quiere trabajar.
    Lo que hay que aprender aquí no es sobre código limpio, sino sobre comunicación y consideración.

    • Reescribir directamente código funcional de otra persona no solo insulta al autor, sino que además es una conducta poco profesional: se pierde tiempo cambiando código que tiene otro mantenedor.
      También se elimina la oportunidad de que el autor original aprenda cómo el código de baja calidad realmente hace perder tiempo.
      Aunque el enfoque de código limpio fuera mejor, el punto central aquí no era el código, sino que desperdició su propio tiempo sin dar feedback útil.
      Lo mejor habría sido que el autor original elevara el estándar y que la otra persona trabajara en algo que sí necesitara tocar. Es decir, la respuesta correcta era una revisión de código.
    • La lección se parece más a “no apliquemos reglas en exceso sin considerar los compromisos según el contexto”, y esa es una lección que toma tiempo aprender.
    • Después de más de 20 años, veo que la idea de “es pésimo porque no extrajo a una función 10 líneas repetidas” suele venir de gente que se enfoca en lo equivocado dentro de una base de código.
      Estas personas muchas veces, en vez de resolver problemas reales, resuelven cosas que no son problemas y hasta crean problemas futuros.
      En una primera implementación, lo correcto suele ser no abstraer un fragmento tan pequeño como esas 10 líneas. El código que hace exactamente esa tarea es fácil de modificar después, y abstraer 10 líneas por un beneficio no comprobado tiene poco sentido.
      Clean Code no importa en absoluto, y la mayoría de reglas o “principios” parecidos tampoco importan. Más bien son cosas a las que se aferran quienes quieren justificar estructuras tipo panal que van acumulando con el tiempo en la base de código.
      El resultado suele ser código demasiado abstraído, difícil de cambiar, lento y difícil de entender.
      Si se necesita un principio, basta con fijarse en expresar con claridad y facilidad de cambio lo que el programa realmente hace: transformación de datos.
      La mayoría de los principios SOLID no ayudan a ver qué es la transformación de datos ni cómo ocurre, o incluso lo dificultan, así que por lo general sirven de poco para entender programas reales.
    • Creo que la formulación del artículo original no es buena.
      Si se mira el refactoring propuesto, esas 10 líneas en realidad no eran idénticas, solo parecidas.
      La fórmula para redimensionar la esquina superior izquierda de un rectángulo y la fórmula para cambiar la esquina inferior derecha de una elipse son distintas. Por fuera se ven parecidas, pero en algunos lugares aparece - en vez de +.
      En este caso la complejidad está en la geometría misma, y “abstraerla” solo mueve el desorden a otro lugar; no lo elimina de raíz.
      Podría existir alguna abstracción matemática ingeniosa, como un sistema numérico especial, pero si no está incorporada en el lenguaje, es muy probable que el costo de implementar operaciones especiales sobre enteros básicos supere el beneficio.
  • El “código limpio” necesita un rebranding.
    En más de 25 años de carrera he visto una resistencia interminable a los patrones de diseño, la abstracción, la eliminación de duplicación, etc.
    Las razones que siempre aparecen son que la abstracción hace el código más complejo, que no hay tiempo para escribir código limpio, o que todo es cuestión de gustos y opiniones.
    Pero el objetivo del código limpio es hacer que el código sea más simple y más fácil de mantener cuando cambian los requisitos.
    El valor del software está en que puede cambiar con el tiempo. Si no fuera así, usaríamos circuitos fijos, que son mucho más fáciles y baratos de implementar y mantener.
    Si el refactoring no logró ese objetivo, y el jefe puede argumentar de forma convincente que el código quedó más difícil de mantener ante futuros cambios de requisitos, entonces ese código no era “limpio”.
    No haberlo discutido con el autor original es un problema de procedimiento y etiqueta, fuera del alcance del código limpio. Que alguien haya actuado de forma grosera y unilateral no elimina el valor del código limpio.

    • Creo que la resistencia apunta a prescripciones demasiado absolutas.
      El famoso libro Clean Code presenta “sin duplicación” como condición de un sistema bien diseñado, y trata la duplicación como el principal enemigo del trabajo extra, el riesgo adicional y la complejidad innecesaria.
      Según esa definición, eliminar duplicación se vuelve sinónimo de simplificar, y eso es incorrecto.
      Eliminar duplicación es introducir una dependencia. Si esa dependencia modela bien el problema, puede ser una buena abstracción y una simplificación; si no, solo es compresión disfrazada de abstracción.
      Como referencia, esa cita corresponde a la mención de Simple Design de Kent Beck que aparece en Clean Code.
    • “Código limpio” se usa con dos sentidos.
      Uno es la sensación subjetiva de que cierto código es bueno; el otro es seguir el libro Clean Code y sus lineamientos relacionados.
      Cada vez más personas sienten que esas dos cosas difieren bastante. Seguir algunas pautas del libro puede producir código que en realidad no es limpio en absoluto.
      En un mundo ideal, todos querríamos escribir código limpio, pero cada vez menos personas sienten que Clean Code sea una buena forma de alcanzar ese objetivo.
      En especial, la abstracción excesiva y la eliminación de duplicación pasaron a verse, para mucha gente, como fuentes de código desordenado.
    • Lo que aprendí en la industria es que incluso el código escrito de la forma más prolija no aguanta mucho cuando se encuentra con los requisitos de producto.
      A veces la deuda técnica está codificada en los propios requisitos de producto, así que no se puede arreglar con refactoring ni con buenas prácticas; solo se puede reducir el dolor.
    • Suena como un argumento del tipo “ningún verdadero escocés”.
      El objetivo del código limpio y los resultados reales de practicarlo son muy distintos. La gente, aun teniendo los requisitos enfrente, por lo general no puede predecir el estado futuro del código.
      El código limpio invita a la terrible pregunta “¿y si…?”, y eso puede convertirse fácilmente en una caja de Pandora que posterga el lanzamiento indefinidamente.
      Casi siempre es mejor programar en función de lo que se sabe, y que alguien con experiencia en el dominio lidere la arquitectura.
    • Si tuviera que listar alternativas: la mayoría de las abstracciones que creamos son malas, e incluso las que creemos que son buenas suelen serlo.
      Muchas de las buenas abstracciones ya están en bibliotecas. Por supuesto, lamentablemente no siempre es así.
      Una mala abstracción devora tiempo, y el código también es una forma de comunicarse con otras personas del equipo, así que importa mucho más cómo reciben esa comunicación los compañeros que mi propia forma de pensar.
  • Un colega escribió mucho código copiando y pegando, y tú lo refactorizaste después del commit
    El colega se quejó con el jefe, el jefe te regañó, y terminaste concluyendo que la próxima vez dejarías la suciedad en la base de código
    Hay una lección que aprender aquí, pero eso no significa que copiar y pegar sea mejor que escribir una función
    La refactorización debió haberse subido como un cambio separado, etiquetando al autor original, para que fuera revisada
    Dicho eso, que el colega lo escalara al jefe en vez de hablarlo directamente, y que el jefe pidiera revertirlo, es una señal bastante mala

    • Yo le habría mandado un mensaje privado por Slack al colega preguntando: “Estas funciones repiten 10 líneas de matemáticas cada una; ¿no sería mejor sacarlas a una función aparte?”
      Las respuestas posibles serían “No, porque…” o, rara vez, “Tienes razón, lo hago mañana”
      Sin escándalo, todos evitan el problema y quizá alguien aprende algo
    • Si era un copiar y pegar por flojera, entonces usar una función para la repetición, es decir, abstraer, es mejor desde el punto de vista de DRY
      Pero el autor añadió un contexto importante: intercambió la posibilidad de cambios en los requisitos por una reducción de duplicación, y más adelante cada manejador de figura terminó necesitando muchos casos especiales y comportamientos propios
      Esto muestra la diferencia entre DRY accidental y DRY esencial
      Muchas veces las bibliotecas A y B parecen coherentes entre sí y por eso el código queda igual, pero en realidad puede ser una coincidencia
      Aunque un wrapper alrededor de lib A y otro alrededor de lib B contengan el mismo código, si envuelven cosas completamente distintas, abstraerlos produce malos resultados
      Lo mismo pasa con “15”. Si “todas las listas siempre deben mostrar 15 elementos”, tiene sentido abstraer el 15; pero si son “las 15 canciones principales de la página de inicio” y “los 15 comentarios predeterminados debajo de una canción”, agrupar ese 15 es una peor decisión
    • La mayor señal de alerta es haber cambiado el envío del colega sin ninguna comunicación
      La segunda señal de alerta es que la refactorización en sí tampoco era una buena refactorización. Le falta modularidad, sigue acoplada y solo redujo un poco la duplicación
    • Etiquetar al autor original también puede traer problemas
      Puede sonar como “El código que escribiste era malo, así que lo reescribí. ¿Algún problema?”
      La mejor solución probablemente sería pedir una explicación en forma de “revisión tardía”, proponer una solución y esperar que el autor original no simplemente la ignore
    • Al final del texto había una parte importante: en cambios posteriores de la base de código fue necesario tratar de forma especial los manejadores por figura, y en la estructura refactorizada era difícil, mientras que en el código repetido era fácil
      El punto central es reconocer que el código cambia en el futuro. Los requisitos cambian y el código relacionado también
      Si creas una abstracción que limita esa capacidad de cambio, perjudicas la capacidad de tu yo futuro para adaptar la base de código
      En especial, cuando invertiste mucho tiempo y esfuerzo en una abstracción, te apegas emocionalmente a ella y puedes terminar perdiendo más tiempo intentando encajar los cambios en una solución elegante
      KISS es lo correcto
  • Este caso parece más bien el problema de “a nadie le gustan los presumidos”
    Ocurre a menudo, sobre todo cuando un desarrollador junior todavía no aprendió que el trabajo no es solo entre él y la computadora, sino entre él, la computadora y otras personas
    Una versión más limpia probablemente habría sido aceptada si hubiera tenido una conversación real con el autor original y hubieran acordado una mejor forma
    El título del texto parece más bien aprendí a trabajar en equipo que “adiós, código limpio”

    • La expresión “a nadie le gustan los presumidos” capta bien el entorno de desarrollo
      El código más complejo pudo haber sido percibido como una amenaza, y la persona como un competidor al que había que contener mediante la cadena de mando
      Si eres junior, debes saber que esto depende del contexto. En muchos lugares de trabajo, la simplicidad importa más que la flexibilidad; se evitan las características confusas del lenguaje, y se prefieren las formas de trabajar que implican pasar mucho tiempo frente al teclado y lenguajes así
      En cambio, las filosofías que valoran el aprendizaje continuo, la artesanía del software y la reflexión sobre el proceso chocan por completo con esos entornos
      Los intentos de mejorar no son populares entre quienes no crecen
      Ambas son decisiones de vida válidas. El software puede ser un lugar cómodo para trabajar 7 horas e irse a casa, y también puede ser un mundo de exploración donde los únicos límites son la imaginación y el intelecto
      Si quieres descubrir cosas nuevas, ten cuidado con pasar décadas junto a personas que temen lo desconocido
      Dicho más brevemente: si tus incentivos no están alineados con los de tus colegas, considera irte
    • Creo que el título está bien
      Este texto tiene razones para ser querido durante mucho tiempo, y les abrió los ojos a muchos desarrolladores que caen en la misma trampa
      Incluso si trabajas solo, puedes sobrediseñar una abstracción limpia y luego quedar atrapado por esa abstracción en el futuro
    • Este tipo de problema suele cometerlo más a menudo un senior embriagado de poder que un junior
  • El problema es que los cambios se commitean directamente a la rama main sin revisión
    Todo cambio de código debería tener revisión de pares, ya sea con programación en pareja o con un PR tradicional, para evitar escaladas como esta
    El jefe fue un mal jefe. En lugar de regañar, debió haber notado que faltaba un buen proceso
    En un equipo con cultura de revisión de código, si un novato demasiado entusiasta intentara algo así, un colega le habría dicho “gracias, pero esto no va” o “buena idea, probemos hacerlo así”
    Por separado, creo que no se debería refactorizar código sin una buena razón. Incluso el código malo puede ser código que casi nunca haya que modificar
    El mejor momento para refactorizar es cuando te das cuenta de que necesitas cambiar el comportamiento, pero la estructura existente lo vuelve demasiado difícil
    Lamentablemente, parece que aquí se aprendió la lección equivocada

    • La revisión de código después del merge también está perfectamente bien y, en algunos aspectos, es mejor
  • No entiendo por qué todos solo hablan de qué debería haber hecho él en su lugar. Esta es una lección suficientemente válida
    En finanzas se trabaja todo el tiempo con productos que son algo parecidos, pero no exactamente iguales. Las opciones sobre acciones y las opciones de divisas son ambas opciones, tienen precio de ejercicio y se valoran con alguna versión de Black-Scholes, pero en la práctica son enormemente distintas
    La tentación de alguien recién incorporado de querer unificar muchos comportamientos comunes es grande, y a menudo terminamos en la posición del jefe del texto original, diciéndole a un desarrollador joven que se calme y mantenga las cosas separadas
    No se trata de despedirse del código limpio, sino de que hay que evitar la sobreabstracción para poder mantener código realmente limpio

    • El ejemplo financiero es correcto, pero aquí se está tratando con geometría
      Lo que hacen los manejadores genéricos son transformaciones afines, es decir, traslación, rotación, escalado y reflexión; si el escalado es uniforme, es una transformación de similitud. Está garantizado que eso funciona con cualquier figura
      El verdadero problema es que no debes abstraer algo que no conoces lo suficiente. Especialmente si no sabes qué posibilidades pueden aparecer en el futuro
      Por ejemplo, si crees que las elipses son solo círculos y elipses, te vas a sorprender bastante cuando aparezca el primer oval “real”
    • Entiendo el punto del artículo, pero el título puede leerse fácilmente como una excusa para justificar código sucio
      Como el autor no distingue activamente entre código sucio y código “no limpio”, los demás intentan hacer esa distinción
    • Se nota bastante que todos aquí, sin más contexto que unas líneas de pseudocódigo, sienten que saben más que los miembros del equipo de entonces y que el autor
      DRY tiene valor, pero la abstracción tiene costos. Siempre es una concesión
  • Una de las razones por las que me gustan lenguajes como Haskell es que el maximalismo de abstracción está instalado a nivel del lenguaje, no a nivel de codificación
    Todos están de acuerdo sobre qué debe hacer Applicative. El desafío se mueve más a la derecha en la tubería que va de la idea al código: encontrar el Applicative adecuado para el problema
    La mayoría de los patrones de diseño orientados a objetos tienen equivalentes naturales y bien definidos en este entorno, y ayudan a reducir las abstracciones específicas de cada proyecto
    Para principiantes, el costo inicial de aprender el lenguaje es alto, y a menudo pasa que abres otro código Haskell y hay unas 12 extensiones del compilador que nunca habías visto y que tienes que entender
    Aun así, como esas extensiones del compilador luego te las puedes llevar a todos los proyectos, lo veo como una ganancia neta
    Para expertos, la desventaja es que hace falta un doctorado para tener una intuición de cuánta RAM va a consumir cats

    • La razón por la que sabemos qué hacen Applicative y Monad es que existen leyes matemáticas
      Si un código usa Applicative o Monad, esas leyes te permiten saber cómo se comporta
      Los patrones de diseño orientados a objetos no tienen esas leyes. Son conceptos o patrones definidos de forma laxa, abiertos a interpretaciones y personalizaciones individuales, pero no tienen leyes concretas que digan exactamente qué son ni qué comportamiento o salida deberías esperar
    • Como usuario de Haskell, coincido en que la capacidad de abstracción del lenguaje es muy buena
      Pero no puedo evitar decir que, en ejemplos como el del blog, seguir el camino del código limpio muchísimas veces terminó haciendo que mi yo del futuro se disparara en el pie
      Si más tarde tienes que estudiar la abstracción para volver a entenderla, esa abstracción no es beneficiosa
    • Al autor del texto original le habría ido mejor usando más principios de orientación a objetos, salvo que el equipo en general los rechazara. Si fuera ese tipo de equipo, creo que yo trabajaría en otro lado
      Donde la orientación a objetos encaja de forma más natural es en widgets de GUI e implementaciones de editores
      La regla que integra las demás reglas de la orientación a objetos es no preguntes, ordena
      En este ejemplo, los manejadores se compartirían de una forma conocida, y habría puntos de control específicos por figura. Si cambia un manejador de la caja, se podría llamar a updateControlPoints(changedHandle), y si cambia un punto de control, se podría llamar a updateBoxHandles(changedControlPoint)
      También serían posibles otras separaciones limpias, pero yo lo haría así
    • Podemos estar de acuerdo sobre qué debe hacer Applicative
      Pero no se suele llegar a un acuerdo sobre qué biblioteca de parsing usar, si usar Data.Text.Strict o Data.Text.Lazy, cuánto estilo point-free usar, si usar . y >>> u otros combinadores, o qué extensiones del lenguaje usar
      Me gusta Haskell, pero sufre problemas parecidos a otros lenguajes. La gente realmente no logra ponerse de acuerdo sobre la mejor forma de hacer las cosas
    • Si quieres ir un paso más allá, puedes usar Rust con tipos afines
      Obtienes el sistema de tipos de Haskell y rendimiento predecible
  • Bastaba con mover solo la parte matemática repetida a una función aparte y llamarla desde las funciones de redimensionamiento
    Las propias funciones de redimensionamiento parecían repetidas porque constituían la interfaz, así que el refactor que las eliminaba estaba mal
    Pero las matemáticas que realizaban esas funciones eran un objetivo totalmente válido para ordenar el código

    • Sí. Parece la dirección obvia, y normalmente yo también lo hago así
      El resultado es un montón de funciones pequeñas y puras, pero no es un problema porque son fáciles de probar y refactorizar
    • En especial si se trata de refactorizar, al trabajar con un equipo normalmente conviene hacerlo por etapas
      Hay muchas formas y patrones para resolver el mismo problema, pero desarmar una interfaz existente que otras personas usan no es un buen punto de partida
      La ventaja de la abstracción es que puedes hacer el interior más flexible sin perjudicar el resto
      Un buen primer paso es, como dijiste, crear por debajo funciones matemáticas más flexibles y dejar lo demás como está
      Luego puedes hablar más a fondo con el equipo sobre cómo comunicar que la interfaz ofrece una fachada clara mientras la implementación queda abstraída
  • Lo que se describe aquí se siente como una mala abstracción
    El problema real es que la gente diseña basándose en apenas dos o tres ejemplos, sin comprobar si es lo suficientemente general como para admitir usos futuros.

    • Un enfoque especialmente bueno es abstraer las partes comunes solo en funciones muy simples.
      Hay que abstraer de abajo hacia arriba, no de arriba hacia abajo.
      No hace falta crear un único punto donde todo ocurra y que maneje combinaciones 2^n.
      Aun así se puede reducir la repetición, cambiar en un solo punto y conservar la capacidad de hacer modificaciones a medida cuando sea necesario.
    • El problema en la realidad es que nadie sabe, al momento de escribirla, que está usando una mala abstracción. Si lo supiera, no la usaría.
      Por eso no basta con decir “evita las malas abstracciones” o “haz que admita usos futuros”.
      En general, nadie puede predecir el futuro.
      Las abstracciones deben usarse con extremo cuidado incluso cuando parecen correctas. Es mejor ser extremadamente escéptico.
    • El problema de la versión “limpia” es que todo queda completamente acoplado entre sí.
      Si tocas algo en el código matemático, afecta a todo.
      En algún punto se vuelve más fácil dejar algo de código duplicado, señalar las similitudes con pistas en la estructura del código o con comentarios y, con el tiempo, destacar en comentarios las diferencias clave.
      El enfoque de código limpio para este tipo de código no maneja bien los requisitos cambiantes.
      Al final es fácil terminar con un montón de funciones auxiliares que contienen varias rutas de código según desde dónde se llamen.
      A veces esto se puede abstraer y, como algunos ya habrán notado, la oración anterior describe el polimorfismo orientado a objetos.
      Pero ¿cómo podrías conocer de antemano los requisitos exactos? Intentar hacer “código limpio” en una etapa tan temprana solo termina en malas abstracciones o sobrediseño.
      El enfoque correcto es no hacer nada, anotar los posibles problemas y esperar hasta saber más sobre el dominio del problema.
    • Cada vez me gusta más la forma de descubrir patrones a través de los cambios: buscar patrones en nuevas funcionalidades y correcciones de bugs.
      No me preocupa demasiado el código un poco duplicado pero fácil de entender, como en el ejemplo original.
      Pero sí me preocupa si tengo que corregir el mismo issue en tres lugares o agregar código de una nueva funcionalidad en tres lugares.
      En ese momento empiezo a pensar si puedo empujar de alguna manera el código que hay que cambiar o agregar hacia una ubicación común.
    • Es cierto.
      La gente lee sobre patrones de diseño o DRY y, como está aprendiendo, por lo general los aplica mal y luego suele culpar a las fuentes originales.
  • Rob Pike dijo alguna vez que una de las razones por las que Go terminó siendo así es que “una copia pequeña es mejor que una dependencia pequeña”.
    Cuanto más programo, más entiendo lo que quiso decir.
    La repetición en sí no es el problema. Lo importante es entender la unidad que estás leyendo ahora y que quizá vas a modificar.
    DRY a menudo significa agregar otra abstracción, y si esa abstracción no es lo suficientemente ortogonal, ahora tienes que considerar dos unidades en lugar de una, así que empeoraste la situación.