4 puntos por GN⁺ 2024-10-30 | 1 comentarios | Compartir por WhatsApp
  • En cuanto se escribe, el código genera costos de mantenimiento, por lo que muchas veces es más importante que tenga una estructura fácil de eliminar o reemplazar después que priorizar su reutilización
  • Cuantos más usuarios tenga una API, mayor será el costo de cambiarla, y cuanto más profunda sea la dependencia de una API de terceros, más se sacudirá la base de código ante cambios externos
  • La duplicación, el boilerplate, el layering, los grandes bloques de código, la separación en módulos y las feature flags pueden convertirse, según el contexto, en herramientas de gestión de dependencias
  • Una buena separación no consiste tanto en agrupar funciones comunes, sino en ocultar entre sí las decisiones de diseño que son difíciles de cambiar o que probablemente cambien
  • El buen código no es el código perfecto desde el principio, sino el código legacy que estorba menos con el paso del tiempo y, al final, el código fácil de eliminar

El código es costo y eliminarlo reduce costos

  • Todo código, en el momento en que se escribe, crea costos de mantenimiento; la reutilización puede reducir la cantidad de código, pero también puede hacer más difícil cambiar de idea después
  • Cuanto más código use una API, más costo de reescritura implicará un cambio en esa API
  • Cuanto más dependas de una API de terceros, mayor será el impacto cuando esa API cambie
  • En sistemas grandes, cómo encaja el código entre sí y qué partes dependen de cuáles se vuelve un problema más difícil cuanto más tiempo pasa
  • Si en vez de ver las líneas de código como “líneas producidas” las ves como “costo incurrido”, entonces borrar código pasa a ser una forma de reducir el costo de mantenimiento
  • El objetivo no es solo crear software reutilizable, sino crear software desechable

Paso 0: no escribir código

  • La cantidad de líneas de código no lo dice todo, pero la escala sí importa: 50 líneas, 500, 5,000, 10,000 o 25,000
  • Reemplazar un monolito de un millón de líneas requiere más tiempo, dinero y esfuerzo que reemplazar uno de 10,000 líneas
  • Cuanto más código haya, más difícil será eliminarlo, pero reducir una sola línea de código casi no ahorra nada
  • El código más fácil de eliminar es, desde el principio, el código que no se escribió

Paso 1: copiar y pegar

  • El código reutilizable suele ser más fácil de crear después de que aparezcan varios casos de uso reales, en lugar de intentar anticiparlos desde el inicio
  • Hacer copiar y pegar unas cuantas veces dentro de la base de código ayuda a entender mejor cómo se usa realmente
  • En el momento en que conviertes algo en una API compartida, ese código se vuelve más difícil de cambiar
  • El código que llama a una función termina dependiendo no solo del comportamiento documentado, sino también del comportamiento intencional o accidental observable en la implementación
  • Eliminar código dentro de una función es más simple que eliminar la función misma

Paso 2: dejar de copiar y pegar

  • Cuando un patrón de código se ha repetido lo suficiente, llega el momento de extraerlo a una función
  • Aquí entran códigos utilitarios frecuentes sobre la librería estándar, como abrir un archivo de configuración y devolver una tabla hash, o eliminar un directorio
  • Es mejor que util sea un directorio en vez de un solo archivo, y poner distintas utilidades en archivos separados
    • Un único archivo util sigue creciendo y, una vez que crece demasiado, se vuelve difícil de dividir
  • El código menos específico de una aplicación o proyecto es más fácil de reutilizar y menos probable de cambiar o eliminarse
    • Aquí entran códigos de librería como logging, APIs de terceros, file handles o procesos
    • Las listas, tablas hash y colecciones no suelen eliminarse fácilmente no solo por sus interfaces simples, sino porque su alcance no crece con el tiempo
  • La clave es mantener lo más lejos posible las partes difíciles de eliminar de las partes fáciles de eliminar

Paso 3: usar más boilerplate

  • Crear una librería puede evitar copiar y pegar, pero en la práctica suele obligarte a escribir mucho boilerplate para usarla
  • El boilerplate se parece al copiar y pegar en que cada vez cambias un poco el lugar o el contexto
  • Este tipo de duplicación acepta ser más verbosa a cambio de reducir dependencias y ganar flexibilidad
  • Las librerías que requieren boilerplate suelen aparecer cuando es difícil mezclar políticas y protocolos, como en protocolos de red, wire formats o herramientas de parsing
    • El protocolo trata de lo que un programa puede hacer
    • La política trata de lo que un programa debe hacer
  • Muchas veces este código es difícil de eliminar porque responde a requisitos para comunicarse con otras computadoras o manipular otros archivos
  • Es importante no esparcir la lógica de negocio dentro de este tipo de código
  • Aunque impliquen más líneas, es preferible escribirlas en zonas que luego sean fáciles de eliminar

Paso 4: no usar boilerplate

  • Cuando el boilerplate se vuelve excesivo, llega el momento de crear una librería con opinión sobre políticas, flujos de trabajo y estado, que envuelva a una librería flexible
  • Crear una API fácil de usar se parece mucho a convertir el boilerplate en librería
  • El cliente HTTP de Python requests es un ejemplo: ofrece una interfaz simple sobre el más verboso urllib3
    • requests cubre el flujo de trabajo típico de uso de HTTP y oculta detalles prácticos
    • urllib3 ofrece pipelining, gestión de conexiones y más, sin ocultar esos detalles al usuario
  • Envolver una librería con otra no es solo ocultar detalles, sino también separar responsabilidades
  • Conviene no poner lógica de negocio en el directorio util, y en su lugar apilar librerías fáciles de usar sobre librerías fáciles de implementar
  • A veces también conviene envolver librerías de terceros
    • Así puedes crear una librería adaptada a tu propio código sin amarrar todo el proyecto a una elección específica
  • Una API fácil de usar y una API extensible muchas veces entran en conflicto
  • El layering se parece menos a escribir código que luego vas a borrar, y más a hacer utilizable el código difícil de eliminar sin contaminar con él la lógica de negocio

Paso 5: escribir grandes bloques de código

  • Aunque uses copiar y pegar, refactorización, layering y composición, el código al final tiene que hacer algo, así que a veces hace falta un gran bloque de código que mantenga unido el resto
  • La lógica de negocio puede caracterizarse por casos límite interminables y hacks rápidos
  • El código de videojuegos o el código escrito por founders también puede verse como el mismo tipo de código que toma atajos para ahorrar mucho tiempo
  • A veces es más fácil eliminar un gran error que eliminar 18 errores pequeños entrelazados
  • Como gran parte de la programación es exploratoria, puede ser más rápido equivocarse varias veces e iterar que intentar acertar desde el principio
  • Al crear tu primer juego, es mejor no empezar construyendo el motor; y antes de escribir una aplicación, suele ser mejor no empezar creando primero un framework web
  • Un monorepo es una concesión similar
    • Es difícil saber de antemano cómo dividir el código, y un gran error puede ser más fácil de desplegar que 20 componentes fuertemente acoplados
  • Si sabes que un código pronto será desechado, eliminado o reemplazado con facilidad, puedes tomar más atajos
  • El objetivo no es repetir diez veces el mismo bloque de barro hasta perfeccionar el error, sino iterar cometiendo errores nuevos y asumiendo riesgos nuevos cada vez
  • Los proyectos al final fracasan o se convierten en código legacy, y el fracaso ocurre más seguido que el éxito
  • Suele ser más fácil eliminar todo un sistema que borrar sus partes una por una

Paso 6: dividir el código en partes

  • Un gran bloque de barro es lo más fácil de construir, pero tiene el mayor costo de mantenimiento
  • Un cambio que parece simple puede terminar tocando con parches temporales casi todas las partes de la base de código
  • El código que como un todo era fácil de borrar se vuelve difícil de borrar por partes
  • Es mejor dividir los módulos no por funciones comunes, sino por lo que no comparten con el resto y por las decisiones de diseño que deben ocultarse
  • Siguiendo el criterio de D. Parnas, puedes listar las decisiones de diseño difíciles o propensas a cambiar, y diseñar cada módulo para que las oculte del resto
  • Los módulos no se crean para reutilización, sino para permitir cambios
  • El principio de responsabilidad única puede verse como “cada módulo debe tratar solo un problema difícil”, pero más importante aún es que “cada problema difícil debe tratarse en un solo módulo”
  • Si un módulo hace dos cosas, muchas veces eso significa que para cambiar una parte también tendrás que cambiar la otra
  • Un solo componente terrible con una interfaz simple puede ser más fácil que dos componentes que requieren coordinación minuciosa

Acoplamiento débil e interfaz uniforme

  • A un sistema donde puedes eliminar una parte sin reescribir las demás normalmente se le llama acoplamiento débil
  • El acoplamiento débil se parece al estado en el que no necesitas cambiar demasiado código cuando cambias de idea
  • Incluso hardcodear una variable una sola vez, o usar una bandera de línea de comandos en lugar de una variable, puede ser acoplamiento débil según el caso
  • Microsoft Windows logra este objetivo separando APIs externas e internas
    • Las APIs externas están ligadas al ciclo de vida de los programas de escritorio
    • Las APIs internas están ligadas al kernel subyacente
    • Ocultar APIs permite ganar flexibilidad sin romper demasiado software
  • HTTP también ofrece un ejemplo de acoplamiento débil
    • Puedes poner una caché delante de un servidor HTTP
    • Puedes mover imágenes a un CDN y cambiar solo los enlaces sin romper el navegador
    • Los códigos de error HTTP asignan códigos únicos a problemas comunes para que el cliente pueda manejar muchos errores por su cuenta
  • La forma de manejar fallas debe considerarse junto con la división del código en partes pequeñas

Manejo de fallas y acoplamiento

  • Erlang/OTP usa una forma relativamente singular de tratar fallas mediante árboles de supervisión
  • Cada proceso de un sistema Erlang normalmente es iniciado y vigilado por un supervisor
    • Si el proceso tiene un problema, termina
    • Si el proceso termina, el supervisor lo reinicia
    • El supervisor es iniciado por un bootstrap process, y si el supervisor falla, el bootstrap process lo reinicia
  • La idea central es que muchas veces resulta más rápido fallar pronto y reiniciar que intentar manejar el error
  • Algunas fallas transitorias pueden contenerse apagando y encendiendo de nuevo
  • El manejo y la recuperación de errores conviene hacerlos en las capas externas de la base de código, algo conocido como el principio end-to-end
  • Suele ser más fácil manejar fallas en los extremos que en medio de la conexión, y aunque se manejen internamente, de todos modos hará falta una verificación al nivel más alto
  • El manejo de errores es una de varias formas en que un sistema puede quedar fuertemente acoplado

IMAP, sistemas de archivos, SQL y middleware

  • IMAP es un caso especialmente excepcional donde casi toda operación tiene opciones y tratamientos propios, lo que vuelve doloroso el manejo de errores
  • En IMAP, los errores pueden aparecer en medio del resultado de otras operaciones
  • En vez de UUID, se crean tokens únicos para identificar cada mensaje, y esos tokens también pueden cambiar en medio de los resultados de una operación
  • Muchas operaciones de IMAP no son atómicas
  • Tardó más de 25 años en aparecer una forma confiable de mover correo de una carpeta a otra
  • También existen una codificación especial UTF-7 y una codificación base64 propia
  • Los sistemas de archivos y las bases de datos son mejores casos de comparación para almacenamiento remoto
    • Un sistema de archivos tiene un conjunto fijo de operaciones y varios objetos
    • SQL parece una interfaz más amplia que un sistema de archivos, pero sigue el patrón de varias operaciones sobre conjuntos y varias filas
  • No siempre puedes intercambiar bases de datos entre sí, pero es más fácil encontrar algo que funcione con SQL que con un lenguaje de consultas inventado por ti mismo
  • Finagle de Twitter usa una API común para servicios, lo que facilita agregar manejo de timeouts, mecanismos de retry y verificaciones de autenticación tanto al código cliente como al servidor
  • Un buen ejemplo de acoplamiento débil suele ser también un ejemplo de interfaz uniforme
  • Una base de código saludable no necesita estar perfectamente modularizada, pero sí debe haber suficiente distancia entre sus partes móviles
  • El código débilmente acoplado no necesariamente es fácil de eliminar, pero sí mucho más fácil de reemplazar y modificar

Paso 7: seguir escribiendo código

  • Si puedes escribir código nuevo sin tener que lidiar con el código viejo, experimentar con ideas nuevas se vuelve mucho más fácil
  • La clave no es si usas microservicios o monolito, sino poder montar uno o dos experimentos sobre el sistema mientras todavía estás descubriendo qué quieres hacer
  • Las feature flags son una forma de poder cambiar de idea más adelante
  • Las feature flags no solo sirven para experimentar con funciones, sino también para desplegar cambios sin volver a desplegar todo el software
  • Google Chrome descubrió que la parte más difícil de su ciclo regular de lanzamientos era el tiempo necesario para fusionar ramas de funcionalidades de larga duración
  • Si puedes activar y desactivar código nuevo sin recompilar, puedes dividir cambios grandes en merges pequeños y evitar afectar el código existente
  • Si las nuevas funciones aparecen antes en la misma base de código, se vuelve más claro el impacto que el desarrollo prolongado de una función tiene sobre otras partes
  • Las feature flags no son solo simples interruptores de línea de comandos, sino una manera de separar el lanzamiento de funciones de la fusión de ramas y del despliegue de código
  • Cuando desplegar software nuevo puede tomar horas, días o semanas, la capacidad de cambiar de idea en tiempo de ejecución se vuelve aún más importante

El buen código es código legacy que no estorba

  • Más importante que el hecho de iterar es tener un bucle de retroalimentación
  • La clave no es crear módulos para reutilizarlos, sino aislar componentes para facilitar cambios
  • Adaptarse al cambio no solo incluye desarrollar funciones nuevas, sino también eliminar funciones viejas
  • Escribir código extensible implica esperar que tu primera decisión siga siendo correcta dentro de tres meses
  • El código eliminable parte del supuesto contrario
  • El layering, el aislamiento, las interfaces uniformes y la composición son menos formas de producir “buen software” en abstracto, y más formas de crear software que pueda cambiar con el tiempo
  • No hace falta tirar todo, pero sí hay que borrar una parte
  • El buen código no es el código que acertó desde el principio, sino el código legacy que no estorba
  • El buen código es código fácil de eliminar

1 comentarios

 
GN⁺ 2024-10-30
Opiniones de Hacker News
  • Una frase que me gusta es la simplicidad es robustez.
    Similar a la ley de cambio continuo de Lehman, significa que cuanto menor es la complejidad de un sistema, más fácil es modificarlo.
    Creo que es mejor prepararse para el futuro con código intuitivo que escribir código extensible pensando en el futuro.
    Por ejemplo, abstraer solo cuando realmente sea necesario, permitir duplicación simple, empezar al principio con un monolito y escalar verticalmente antes de escalar horizontalmente.
    He creado varios sistemas de 0 a 1, y el patrón común en todos fue ese.
    https://en.m.wikipedia.org/wiki/Lehman%27s_laws_of_software_...

    • Es cierto, pero al aplicar el principio de la simplicidad es robustez también hay que entender la complejidad inherente.
      No manejar casos límite no hace que el código sea robusto, aunque parezca más simple.
    • La regla que sigo es esta: la primera vez simplemente lo escribo, la segunda lo copio, y la tercera considero refactorizar.
    • Estoy de acuerdo, pero no sé si la expresión simple is robust es lo bastante intuitiva.
      Abre la discusión sobre qué es “simplicidad” y cómo se aplica a un sistema, y es una pregunta lo bastante compleja como para que Rich Hickey la haya tratado.
      Quizá “lo tonto es robusto” o “lo directo es robusto” capture mejor la intención.
    • Estoy muy de acuerdo. Demasiada basura en el software nace de intentar resolver problemas imaginarios.
      Simplemente escribe código que haga lo que necesitas. No inventes problemas hipotéticos de escalabilidad, no crees abstracciones ingeniosas para parecer inteligente; escríbelo como monolito, súbelo a una VM y ya puede operar.
      Si aparece un problema, lo resuelves entonces; idealmente, después de que el flujo de caja ya sea positivo.
      ¿Por qué una startup de “Airbnb para perros” con 0 usuarios se preocupa por C100K? ¿AWS te convenció de pagar por serverless por tu propio beneficio, o para sacarte dinero?
    • La complejidad de la lógica de negocio no desaparece solo porque uno lo desee. Si es enorme y está entrelazada, el código también lo será.
  • Artículos relacionados:
    Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=24989351 - Nov 2020 (30 comments)
    Write code that is easy to delete, not easy to extend (2016) - https://news.ycombinator.com/item?id=23914486 - July 2020 (109 comments)
    Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=18761739 - Dec 2018 (2 comments)
    Write code that is easy to delete, not easy to extend - https://news.ycombinator.com/item?id=11093733 - Feb 2016 (133 comments)

  • Para resumir en pocas palabras los errores que cometí de joven: ahora creo en lo contrario, en diseñar para poder borrar.
    Antes pensaba que podía crear una obra de arte maravillosa que anticipara todas las situaciones y satisficiera todos los requisitos. Pero nadie puede predecir tan bien las necesidades futuras.
    Algún día, lo que yo hice será “esa estupidez” para alguien, y por mucho orgullo que me dé hoy, puede ser totalmente legítimo que lo destruyan todo.
    Por eso es mejor esforzarse en que sea fácil de eliminar. Esto muchas veces reduce el acoplamiento, pero lo importante es que no es lo mismo que el desacoplamiento de un desarrollador joven y entusiasta que quiere separar todo en un framework metaconfigurable.
    A veces, un acoplamiento fuerte que sea fácil de entender es mejor.
    https://news.ycombinator.com/item?id=41219130

    • Puedes hacerlo fácil de eliminar, pero otras personas pueden estar encantadas de crear abstracciones y lógica, incrustarlas en el núcleo del proyecto y, con el tiempo, dejarlas tan solidificadas que sea imposible quitarlas.
      Por ejemplo, aparecen cosas como CommonExcelFileParser, CommonExcelFileParserUtilities, HasExcelParseStatus, ProductImportExcelParser, ProductImportExcelParserView, ProductImportExcelParserResultHandler, y terminan convirtiéndose en la base del código alrededor.
      Es parecido a cuando empiezas un proyecto frontend con React o Angular y migrarlo a otra cosa se vuelve una tarea de Sísifo.
      En la práctica, la gente termina construyendo plataformas enteras y, aunque haya decisiones que causarán problemas en el futuro, por el acoplamiento se vuelve mucho más difícil refactorizar que una base de código con menos abstracción.
      Parece que a la gente le gusta más hacer esto que aplicar KISS y YAGNI y escribir código fácil de borrar, así que no sé qué hacer cuando pasa.
    • Aun así, depende del contexto. Si es una aplicación de negocio, entonces sí, y diez veces sí.
      Los requisitos de negocio cambian y se mueven, así que no intentes predecirlos; escribe cosas que sean fáciles de reemplazar o desechar.
      Los frameworks y las bibliotecas son algo distintos. También deben adaptarse a los cambios del mundo, pero pueden hacerlo a un ritmo mucho más moderado.
      El mayor problema es cuando, en una aplicación de negocio que ya usa un framework como Rails o Asp.Net, los desarrolladores quieren crear otro “framework”.
    • Algunas cosas cambian, y a veces uno elige la abstracción equivocada.
      Si no estás escribiendo el kernel de Linux, no deberías escribir como si fuera el kernel de Linux.
  • Es bastante extraño que este artículo no trate en absoluto las pruebas y la observabilidad.
    Las pruebas también tienen un costo de mantenimiento, pero reducen el riesgo de romper algo cuando eliminas algo.
    Además, si expusiste un servicio a llamadores externos, necesitas tanto una forma sólida de marcar algunas llamadas como obsoletas para eliminarlas después, como una manera de observar si todavía se invocan y quién las invoca.
    Hace poco hice por primera vez una eliminación semiautomática de resolvers de GraphQL expuestos, y como ya había métricas sobre con qué frecuencia se usaba cada resolver, las pude parsear para obtener una lista de resolvers que no se podían eliminar.
    GraphQL ya tiene una anotación deprecated, pero nuestro servicio no la trataba de forma especial.
    Así que agregamos observabilidad para marcar cuando se invocaba una función deprecated y, después de dejarla correr en producción el tiempo suficiente, pudimos eliminar de forma segura código expuesto al exterior.

    • Simplificando un poco, si haces algo fácil de eliminar, evitas introducir bugs involuntarios al eliminarlo.
      Si empiezas a hacerlo excesivamente complejo, todo termina convertido en un enredo donde todo está conectado con todo, y los desarrolladores ya no pueden saber qué impacto tendrá un cambio.
      Claro que hay muchas formas de arruinarlo. Puedes seguir principios tontos de “buenas prácticas”, o hacer “microservicios” de una forma en la que no sabes quién consume qué servicio. Pero entonces no lo hiciste fácil de eliminar.
      El consumo externo es un buen ejemplo. Tiene sentido darles a los consumidores una advertencia razonable sobre la retirada de un servicio, pero si no puedes apagarlo de verdad cuando quieres, no es un sistema diseñado para poder eliminarse fácilmente.
      Si ese enfoque es el correcto, está bien hacerlo así. Pero esperar que las pruebas y la observabilidad te avisen si algo se rompe probablemente no funcione muy bien.
      No estoy en contra de las pruebas en sí, pero no me parecen una gran red de seguridad para decirte si rompiste algo en una cadena larga y compleja. En la práctica, también es muy difícil tener una cobertura de pruebas que realmente te proteja.
    • Si hay muchas líneas de código, es esperable que también haya cierta cantidad proporcional de líneas de pruebas.
      Si eliminas parte del código, también puedes eliminar parte de las pruebas.
      Como en el artículo se habla solo del código, se puede entender que los efectos relacionados sobre las pruebas están incluidos de forma implícita.
      No se puede asumir que, porque el artículo no mencione las pruebas, esté diciendo que no hay que escribir pruebas.
    • Las pruebas son buenas, pero programar no termina en escribir pruebas. No hace falta mencionar las pruebas en todos los artículos.
  • Al ver esta parte, siento que el título no siempre es correcto: el código fácil de eliminar suele ser también código fácil de extender.
    Porque está estratificado, es modular y aísla las distintas piezas mediante abstracciones como interfaces u otros contratos de tipos.

  • Les he dicho a estudiantes de física computacional que el mejor cálculo es el que no tienes que hacer.

  • Personalmente, divido el código en dos partes: lógica de negocio e implementación real.
    La lógica de negocio, por su naturaleza, puede duplicarse, pero no debería duplicar demasiados detalles técnicos.
    La implementación real puede ser todo lo desordenada que quiera, siempre que no contenga directamente la lógica de negocio y se mantenga independiente de la aplicación.
    Así, cuando te das cuenta de que algo es un desastre y no funciona bien, tienes la opción de borrar toda la implementación en lugar de intentar arreglarla a la fuerza rastreando la especificación real desde la implementación.

  • La frase del primer párrafo, “el problema de la reutilización de código es que impide cambiar de opinión más adelante”, es claramente un error.
    En términos generales, es falsa. Si cambias de opinión y el código está copiado y pegado en diez lugares, tienes que arreglar diez lugares.
    En cambio, si está dentro de una función, solo tienes que cambiarlo una vez. Incluso si después descubres que una de las diez llamadas no debería cambiar, en ese momento puedes copiar y pegar o generalizar más la función.
    Como cruzar la calle sin mirar, copiar y pegar casi siempre es una mala idea.

    • En mi experiencia, el código malo copiado y pegado termina en una tarde molesta de pago de deuda técnica y correcciones.
      Pero una mala abstracción deriva en meses de pago de deuda técnica.
      Claro, la respuesta es “no hagas malas abstracciones”, pero todos sabemos cómo termina eso en equipos y con requisitos de producto cambiantes.
    • El código reutilizado muchas veces es correcto en varios lugares, así que para cambiarlo hay que bajar la velocidad y separar esos puntos.
      Tenemos un submódulo de git con widgets de UI comunes, y ahora es casi imposible cambiar uno de ellos, así que es más fácil copiar el componente dentro del proyecto y modificarlo localmente.
      Eso es un problema. El código compartido debería mantenerse al mínimo posible, y el hecho de compartirlo dificulta los cambios.
    • ¿Qué pasa si, de 10 llamadas a una función, 3 deben cambiar de una forma, 5 de otra, y las 2 restantes ya no usan la misma abstracción y hay que reescribirlas por completo?
      Si todo está en una sola función, la mayoría de los desarrolladores intentará cambiar esa función para satisfacer los 10 casos, aunque en primer lugar nunca debió haber sido una sola función.
      Es mucho más fácil corregir diez lugares copiados y pegados que desatar un nudo mal hecho que, una vez atado, mantiene unidas distintas piezas del sistema.
    • El autor probablemente respondería que ese código debería haberse movido a un módulo o una función.
      A primera vista parece contradecirse sobre este tema, pero si se lee con calma, está usando el copiar y pegar como una señal de qué código debería abstraerse y de cuál es el patrón que realmente conviene seguir.
  • Es raro que se sigan repitiendo todo tipo de mandamientos sobre software, principios casi religiosos.
    En papel todos se ven excelentes y suenan a sentido común, pero después de 50 años el software sigue siendo basura en el 90% de los casos.
    Aun así, estas cosas se siguen sacando a relucir como si fueran ideas geniales o balas de plata.

    • Creo que ese 90% de basura lo escribe gente que no lee ni escribe artículos como este.
  • De esto se desprende un excelente corolario: el código malo permanece más tiempo porque es mucho más difícil de eliminar.