3 puntos por GN⁺ 2023-09-17 | 1 comentarios | Compartir por WhatsApp
  • La postura es que, más que separar niveles de abstracción dividiendo el código en funciones pequeñas, el código lineal que fluye de arriba hacia abajo facilita seguir el flujo completo.
  • Si se crea una estructura top-down mediante extracción de funciones, puede ser necesario ir y venir para verificar entre funciones con nombres parecidos como bake y bakePizza.
  • Como ocurre con la ubicación del precalentamiento del horno o con el resultado de pasar dos veces la pizza, las funciones pequeñas pueden revelar la intención, pero también ocultar el comportamiento real.
  • Si al código lineal se le agregan comentarios por etapas, se puede explicar la intención de la tarea sin aumentar las referencias indirectas, por lo que puede ser más legible que agregar más abstracción.
  • Extraer funciones pequeñas que se usan una sola vez provoca una pérdida de linealidad y, como en la forma de crear el horno del ejemplo, en código real incluso puede exponer problemas de rendimiento.

Cuando la linealidad es más importante que extraer funciones

  • El ejemplo de Google Testing Blog compara dos implementaciones de createPizza y considera que la implementación de la derecha es más fácil de leer y top-down porque no mezcla niveles de abstracción.
  • Desde la postura contraria, lo más importante es que la implementación de la izquierda es código que se lee linealmente de arriba hacia abajo en la pantalla.
    • En la implementación de la derecha, para entender todo el comportamiento hay que moverse entre varias definiciones de funciones pequeñas.
    • Incluso en la forma de mostrarlo, se omite parte del código de la derecha, por lo que ambas implementaciones parecen tener un tamaño similar, pero en realidad la de la derecha es más larga.
  • Extraer funciones puede hacer que sea difícil conocer suficientemente el comportamiento solo por el nombre.
    • Cuando existen tanto bake como bakePizza, no es fácil saber de inmediato qué función calienta el horno.
    • Para comprobar si al pasar dos veces la misma pizza la operación es idempotente o si arruina el resultado, hay que ver la implementación interna.

Código lineal con comentarios y el ejemplo del horno

  • Se evalúa que la forma más fácil de leer es una versión del código lineal de la izquierda con los nombres de las funciones de la derecha como comentarios.
    • Comentarios como Prepare pizza, Add toppings, Heat oven, Bake pizza, Box and slice muestran la intención de cada paso.
    • La legibilidad no proviene de capas adicionales de abstracción ni de referencias indirectas, sino de explicar correctamente lo que se está haciendo en ese momento.
  • La conclusión se acerca a no extraer del código lineal funciones pequeñas que se usan una sola vez.
    • Se considera que los beneficios de extraer funciones pequeñas no compensan la pérdida de linealidad.
  • El manejo del horno en el ejemplo también es estructuralmente incómodo.
    • Precalentar el horno es una acción autocontenida, por lo que sería más adecuado que fuera un método del horno.
    • El flujo de crear y precalentar un horno nuevo cada vez que se hace una pizza no coincide con un uso realista.
    • En código real también aparecen estructuras así y, a veces, pueden generar problemas de rendimiento.
  • Es muy posible que el horno deba recibirse como parámetro en lugar de crearse dentro de createPizza.
    • Proveer el horno se parece más a una responsabilidad del llamador.
    • Si el flujo consiste en meter la pizza en una caja, una interfaz que devuelva la caja, y no la pizza, podría ser más natural.

1 comentarios

 
GN⁺ 2023-09-17
Opiniones de Hacker News
  • Es una cuestión de estilo y, como en la cocina, tanto demasiada sal como muy poca pueden arruinar el platillo.
    Espero que nadie aquí esté proponiendo una función dios de 1000 líneas, pero un máximo de 5 líneas por función tampoco resulta legible. Decidir dónde dividir requiere criterio, buen instinto e iteración. Que la primera abstracción que intentaste haya sido mala no significa que haya que abandonar las abstracciones; después de refactorizar algunas veces, pueden aparecer clases y API que encajen bien con el dominio del negocio.
    Al mismo tiempo, no hay que apresurarse demasiado con las abstracciones ni actuar como si unas pocas líneas duplicadas fueran una herida mortal. Las abstracciones prematuras suelen agrupar código que no necesita evolucionar en conjunto. Extraer una función que solo se llama desde un lugar, para ocultar una unidad de trabajo, puede dejar más limpio un algoritmo, y es especialmente útil cuando oculta boilerplate o la mezcla de lógica de negocio con intereses de infraestructura como el manejo de conexiones a la DB. Pero hay que usarlo con cuidado, y conviene evitar dividir pasos que deberían estar en el mismo nivel de abstracción.

    • La clave es esta parte. Los desarrolladores principiantes tienden a escribir funciones enormes, y los desarrolladores entusiastas que acaban de leer libros como Clean Code por primera vez intentan partir todo en un millón de funciones de unas pocas líneas.
      Una persona con la que trabajé extraía todas las condiciones booleanas a funciones porque “era más legible”, y no escribía ningún comentario porque “los comentarios son malos”. No me gusta ese libro porque crea fanáticos que siguen ciegamente ese tipo de malos consejos.
    • ¿Por qué no se puede tener una función dios de 1000 líneas? ¿Quién dijo que era peor y qué estudio llegó a esa conclusión?
      A veces el dominio puede requerir una función dios de 1000 líneas, y que la lógica y las tareas estén reunidas en un solo lugar puede ser mucho más legible que 20 funciones de 50 líneas. Al final, para entenderlo todo tendrás que leer esas 20 funciones de todos modos, y alguien podría intentar reutilizar algunas de ellas, ajustarlas para 2 o 3 requisitos que no existían en la tarea original, y terminar acoplando cierta lógica con casos de uso no relacionados.
      Si esa función es una función pura, me da igual si tiene 1000 o 10000 líneas; aun así me parece aceptable.
    • Siguiendo con la analogía de la cocina, si al explicar cómo preparar una comida hay un punto en el que hay que agregar un fond, tiene sentido explicar cómo hacer el fond en una sección aparte. El fond es algo independiente y solo toca el platillo en un punto, así que sacarlo afuera puede estar bien e incluso ser beneficioso.
      Las recetas de cocina ya están muy abstraídas. Cuando dicen “sofreír la cebolla”, asumen que ya sabes cómo cortar una cebolla y cuál es el algoritmo para sofreír. Si escribieras todo inline, se volvería ilegible.
      Con el código pasa algo parecido. Si excluyes estrictamente las abstracciones, bajas hasta el nivel más bajo que permite el lenguaje, y eso definitivamente no es código legible. Por ejemplo, si en vez de usar el método decode de Python intentaras hacer tú mismo la decodificación Unicode, sería muy difícil entender qué hace realmente el programa. Nadie lo hace solo porque el lenguaje ofrece abstracciones simples y bien validadas; entonces, ¿qué diferencia hay con crear tus propias abstracciones simples y bien validadas para usarlas en toda la lógica de negocio?
      La parte difícil es crear abstracciones tan bien elegidas que nadie tenga que volver a tocarlas.
    • El feedback que suelo darles a los equipos de la empresa es que den un paso atrás, miren el dominio del problema más amplio y piensen si estas cosas son necesariamente iguales o solo accidentalmente iguales.
      Que ahora las líneas de código se vean parecidas no significa que deban ser iguales en el futuro ni que haya que mantenerlas iguales. Si fuerzas la unión de dos casos de uso distintos solo porque “el código está casi repetido”, con el tiempo es fácil que termines con una abstracción que no abstrae nada.
      Si los casos de uso divergen demasiado, la implementación termina empujando mucha lógica hacia el lado del llamador, o exponiendo las diferencias como flags y manteniendo internamente dos implementaciones distintas una al lado de la otra. Lo primero es una abstracción superficial y aporta poco valor; lo segundo es menos claro que dos implementaciones independientes.
    • Siempre preferiría una función de 1000 líneas bien estructurada antes que un mal spaghetti de cientos de funciones pequeñas.
  • El código de ejemplo es tan simple que, obviamente, el código lineal se lee mejor, pero esa idea no escala bien.
    También hay que considerar la reutilización y la facilidad para hacer pruebas unitarias, y si se mete todo el código en una sola función, todas las variables locales —que pueden o no estar relacionadas con el bloque de código que se está leyendo— quedan dentro del alcance, lo que puede volver más difícil razonar sobre él.
    Dicho eso, al mirar atrás a mis épocas de menos experiencia, muchas veces tomé código lineal perfectamente válido y lo modularicé demasiado, convirtiéndolo en código menos mantenible que obligaba a saltar de un lado a otro. La forma en que se escribió originalmente está más cerca del flujo de pensamiento que tenía en la cabeza en ese momento, y tiene la ventaja de que el lector probablemente también lo interprete así. Con una refactorización excesiva, eso puede desaparecer.
    Al final, programar se parece más a una artesanía, y la experiencia ayuda a elegir según la situación.

    • Una de las funciones que recibió mejores reviews en el trabajo era un monstruo de 2000 líneas, con un estilo lineal que tenía 9 ámbitos de variables separados como si fueran etapas.
      Tenía un único propósito. Era una tarea para convertir páginas HTML individuales que se usaban en un rincón de la app, en una plataforma, en un carrusel que imitaba la sensación nativa de otra plataforma, y estaba extremadamente especializada para esa plataforma y esa área de la app.
      Podría haber convertido cada uno de los 9 ámbitos en funciones, pero entonces los desarrolladores habrían querido reutilizarlas. Cada etapa tenía supuestos sutiles sobre lo que había ocurrido en la etapa anterior, y para convertirlas en funciones separadas habría que volver a revisar esos supuestos, generalizarlos y verificar que cada método funcionara de manera independiente. No había motivo para gastar ese costo en código que casi no se necesitaba en otros lugares.
      Tampoco era más difícil de depurar, había pruebas de punta a punta, y el estado de las etapas intermedias no se filtraba fuera de la función. De hecho, con el tiempo otros 2 desarrolladores contribuyeron con modificaciones, funcionó bien y además fue rápido de escribir.
      El código lineal escala bien y resuelve problemas. No siempre es la forma que uno quiere, pero en muchas más situaciones de las que se piensa hace la vida mucho más fácil.
      Mi reacción al ver por primera vez el monstruo de 2000 líneas no fue buena, pero con solo mirarlo 5 minutos era difícil encontrar defectos reales; con unas pocas pruebas, los miedos eran solo temores que no se materializaban.
    • ¿Dónde está la evidencia de que el código dividido en funciones escala? Cuando aumenta la complejidad total del código, también aumenta la posibilidad de que quede cortado en decenas de funciones hasta volverse ilegible.
      En algún momento uno se da cuenta de que esas decenas de funciones deben llamarse en un orden específico y que cada una se usa una sola vez. Al final, es como obligar a quien quiera usar esas funciones de forma útil a conocer un orden de combinación casi mágico.
    • Decir que “la idea no escala” es incorrecto, y decir que “programar es una artesanía y la experiencia ayuda a juzgar la situación” es correcto.
      La razón central por la que una función lineal enorme a menudo es más legible y deseable es que permite mantener simultáneamente varios conceptos y relaciones en un solo bloque, sin cambio de contexto, lo que ayuda a entender. Un defensor extremo de esto es Arthur Whitney, inventor del lenguaje K, que escribe código extremadamente conciso, casi incomprensible para los demás, para meter la mayor cantidad posible en una sola pantalla.
      Como ejemplo personal, me resultó mucho más fácil leer, entender y depurar una enorme función de procesamiento de mensajes de Windows, es decir WndProc, con la lógica de negocio dentro de un gran switch, que una versión de Visual C++ donde los manejadores de mensajes estaban separados en funciones distintas.
      También vi código de ejemplo para microcontroladores donde un ejemplo de uso de ADC estaba completo en un solo archivo, y otra versión dividida en varios archivos como main.c, config.c, interrupts.c, timer.c, etc.; aunque no llegaba a 200 líneas, la segunda era difícil de entender por el cambio de contexto.
    • He visto muchos casos en los que la gente, por costumbre, extrae a funciones separadas mucho código lineal que no se va a reutilizar.
      Estos fragmentos de código suelen terminar como funciones private de una clase, y tienen estado. Al ser funciones private, en la práctica también son difíciles de probar.
      Ahora aparecen montones de funciones private que se llaman una sola vez y que normalmente modifican estado con efectos secundarios. Si están justo al lado del llamador, en casos simples todavía pueden leerse, pero con el tiempo alguien agrega otra función entre la función que llama y la función extraída.
      Entonces, a menos que uno mire el grafo de llamadas o busque dentro del archivo de la clase, esos fragmentos de código cuyo punto de llamada se desconoce terminan modificando distintos estados con efectos secundarios.
      Si van a hacer que el código sea no lineal, al menos me gustaría que consideraran convertir las funciones private extraídas en funciones internas de la función llamadora, cuando el lenguaje lo permita. Así queda claro que no se llaman desde otros lugares.
      En bases de código reales, esto tampoco es una elección binaria, sino más bien un arte de combinar ambas formas para lograr algo legible y mantenible.
    • Si una función realmente es lineal, una función larga no es tan mala. Pero el ejemplo real no es lineal y contiene varias ramas.
      ¿La gente probará todas esas ramas? ¿O escribirá solo una prueba que mete una pizza y se limitará a ver si más o menos funciona? Probar varias ramas desde afuera suele ser engorroso, y como es más molesto que probar funciones pequeñas y especializadas, parece más probable lo segundo.
  • Decir que “el código lineal no escala” es más bien lo contrario. En una base de código grande, la verdadera pesadilla son las funciones pequeñas y concisas con pilas de llamadas profundamente anidadas.
    No queda claro dónde agregar código nuevo, hay que rastrear todos los caminos por los que el código podría ser invocado, lo que aumenta exponencialmente la dificultad de entender el impacto de los cambios, y además aparecen subrutinas duplicadas.
    En el 99% de los casos no creaste una buena abstracción, así que es mejor usar código lineal. Prefiero copiar/pegar antes que semánticas de función dudosas.

    • Otro riesgo es que, si agregas print_table(), alguien lo encuentra, lo usa en su propio código y le agrega un pequeño flag para ajustar la salida a su caso de uso.
      12 meses después termina viéndose así:
      print_table(
      rows,
      headers = None,
      is_unicode = False,
      left_align = False,
      align = [],
      remove_emoji = None,
      max_width = 80,
      potato_mode = 7,
      _debug_frontend = not FLAGS.dont_debug,
      ellipsis_for = 0,
      no_print = False,
      )
    • Esto describe un problema de legibilidad y, en esencia, equivale a decir que la legibilidad perjudica la escalabilidad.
      Si se ven ambos conceptos como ortogonales, salvo por el hecho de que la legibilidad puede influir en la escalabilidad, el código lineal no escala tan bien como el código modular. Vale la pena conocer esta dicotomía y considerarla según el contexto.
      Aun así, sigo sin estar de acuerdo. Si una función pequeña es una función pura, no genera problemas de legibilidad. Eso significa que no toca estado, no inyecta lógica en el código y se debe minimizar explícitamente la inyección de dependencias y el paso de funciones a otras funciones.
      Si armas un pipeline de funciones puras que solo pasan datos, el resultado es legible y escalable. Hay muchos menos casos en los que tengas que reescribir lógica por fallas de diseño, y al combinar funciones puras el código se vuelve como Lego. Refactorizar también se parece más a reorganizar y recombinar elementos primitivos existentes.
  • El código de ejemplo habría distraído menos si al menos hubiera intentado mantener significativa la metáfora de la pizza, o si no hubiera sido código Go de bajo nivel.
    prepare es un nombre terrible para una función. Un Gopher experimentado probablemente le habría puesto algo como NewPizzaFromOrder.
    No veo razón para tener addToppings como función aparte. Si hiciera falta, personalmente lo habría convertido en un método de Pizza, como func (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ }. Una pizza real es mutable, así que el método modifica el receptor.
    Tampoco entiendo por qué se instancia un horno nuevo cada vez que se hornea una pizza. Debería empezar con un horno existente, llamar a oven.Preheat() y luego a oven.Bake(pizza). Más aún, oven.Preheat() podría devolver un nuevo tipo de Oven que exponga .Bake(), para impedir en tiempo de compilación el error de hornear sin precalentar. En otra parte podría haber una interfaz Baker, y quizá una implementación ToasterOven donde el precalentamiento no sea tan importante y no haga falta.
    Aunque no cambiara el código, habría reordenado las declaraciones para que siguieran el flujo esperado. Así, al revisar funciones que se llaman entre sí, no habría que saltar arriba y abajo por la página.
    No tengo tiempo, así que lo dejo aquí, pero este código ya es un ejemplo demasiado malo incluso para iniciar el debate de “cuál es más legible”.

  • John Carmack dijo casi lo mismo, y desde entonces lo sigo aplicando. El código lineal es naturalmente más fácil de leer porque sigue el orden de ejecución y minimiza los saltos visuales.
    Cierto código tiene que ser no lineal para permitir reutilización, y entonces la ejecución se convierte en un grafo. Si el código no aprovecha la reutilización propia de una estructura de grafo, no hace falta introducir un vértice donde basta con una sola arista.
    http://number-none.com/blow/blog/programming/2014/09/26/carm...

    • Algo que Carmack sí dijo, pero que no está en el texto original, es que si puedes extraer lógica sin efectos secundarios a una función separada, por lo general es una buena idea.
      En este caso, creo que el código de la izquierda habría quedado mejor con algo como pizza.Toppings = get_pizza_toppings(order.kind), dejando la modificación de la pizza como foco en la función principal.
  • Coincido en cierta medida en que el código lineal es más fácil de leer, pero eso por sí solo no lo convierte en una buena práctica de código
    Creo que el buen código lineal es más fácil de leer, pero su mantenibilidad y facilidad de prueba son mucho peores. Tengo décadas de experiencia y también hago evaluaciones externas de estudiantes de CS, y entre las buenas prácticas que he visto en la realidad durante muchos años, lo único seguro ha sido mantener las funciones pequeñas
    No es que me gusten particularmente las abstracciones, ni creo que haya que evitar a toda costa la duplicación de código, pero si haces funciones lo más cercanas posible a un único propósito, tu yo del futuro te lo va a agradecer
    Si un código como el del ejemplo corre en producción durante 10 años, cada sección va a cambiar. Con suerte, los comentarios también se actualizarán, pero en la mayoría de los casos no será así. Las pruebas unitarias también se volverán grandes y difíciles de manejar, cada vez más descuidadas, y alguien puede olvidarse de modificar una parte de una prueba que no parece estar claramente relacionada con el cambio. Es muy probable que el código también se vuelva menos legible con el tiempo. No por intención ni por incompetencia, sino por razones humanas como la presión de tiempo
    En un mundo perfecto no haría falta separar responsabilidades, pero vivimos en un mundo imperfecto, y cuanto más pequeñas sean las funciones y menos responsabilidades tengan, más fácil será lidiar con esa imperfección con el paso del tiempo

    • Correcto, es menos fácil de probar, pero el caso actual son cambios de estado que deben ejecutarse en un orden específico
      Si estás pasando un objeto por un flujo de estados concretos, creo que es mejor dividirlo y marcar las transiciones con tipos, o bien escribirlo como una sola función grande. Por ejemplo, si bakePizza recibe RawPizza y devuelve BakedPizza, puedes hacer cumplir el orden de las llamadas en tiempo de compilación
      Prefiero lo primero por legibilidad, corrección y facilidad de prueba, pero en la mayoría de los lenguajes de programación, cambiar el tipo de un objeto requiere crear un objeto nuevo y tiene un costo en tiempo de ejecución. Si es una ruta de código caliente, tiene sentido modificar en el lugar, y en ese caso es mejor dejarlo en una sola función lineal
    • Hace poco empecé a leer Software Design for Flexibility de Sussman, y esto se relaciona directamente con ese tema
      https://mitpress.mit.edu/9780262045490/
  • Un correo relacionado de John Carmack: http://number-none.com/blow/blog/programming/2014/09/26/carm...
    Discusión: https://news.ycombinator.com/item?id=12120752

  • Totalmente de acuerdo. Antes estaba en el bando contrario
    La tensión básica aquí está entre la localidad del comportamiento por un lado y el deseo de mostrar claramente una vista de alto nivel tipo “tabla de contenidos” por el otro. Para que el código sea legible, la localidad es más importante. Como dice el artículo, la perspectiva de tabla de contenidos puede hacerse suficientemente clara con comentarios de sección
    Hay una razón aún más importante para preferir el código lineal. Al navegar por toda una base de código, es mucho más fácil si los “bloques”, es decir, funciones, clases o unidades impuestas por el lenguaje, se corresponden aproximadamente con casos de uso de negocio. Si no, el espacio de búsqueda se vuelve demasiado grande y uno tiene que reconstruir el todo a partir de las piezas. La estructura del código debería hacer ese trabajo por ti
    Si varias “cosas” están todas relacionadas con una sola tarea, por ejemplo registrarse o comprar, conviene mantenerlas juntas también en el código. Es mucho más fácil encontrarlas y cambiarlas. Solo hay que dividir en subfunciones cuando se necesite reutilización, no dividir solo por organización
    [0] https://htmx.org/essays/locality-of-behaviour/

    • Yo fui en la dirección contraria. Antes estaba en el bando del código lineal, pero ahora estoy del lado de más funciones
      La razón principal es el estado. Cuanto más larga es una función, más amplio es el alcance de las variables locales. Cualquier variable puede cambiarse en cualquier parte de la función, y el flujo de datos no queda claro de inmediato. Con más funciones, el alcance se mantiene pequeño y el flujo de datos es más explícito
      Como efecto secundario, también se reduce la indentación
      Al mismo tiempo, no me gustan las funciones demasiado pequeñas. Porque se vuelve difícil encontrar dónde ocurre el trabajo real
    • ¿Qué pasa con las pruebas ante la frase “solo dividir en subfunciones cuando se necesite reutilización, no dividir solo por organización”? ¿Y con reducir el estado que hay que mantener en la cabeza? ¿Con liberar recursos? ¿Con entender el impacto de los cambios?
      Imagina un proceso de cierre diario con 10 pasos no reutilizables que deben ejecutarse en orden, y cada paso tiene 100 líneas. Cada paso usa datos parecidos al anterior, pero no iguales. ¿De verdad elegirías una única función de 1000 líneas?
  • Ambos se leen de forma lineal. La versión que extrae funciones pequeñas tiene una tabla de contenidos en la parte superior de la página y resume el flujo de datos entre pasos. Si vas a leerlo todo, parece un orden de lectura atractivo
    Sin embargo, para mantener esta legibilidad, cuando cambia el orden de los pasos también hay que mover la ubicación de las funciones. Si son funciones private y solo se llaman desde la tabla de contenidos, está bien. Pero nada obliga a mantener el orden, ni tampoco obliga a pensar en el flujo de lectura completo
    Cuando una función empieza a reutilizarse, a menudo deja de ser posible linealizarla. A veces la gente se rinde y las ordena alfabéticamente, o simplemente quedan en orden aleatorio

  • Según mi experiencia, cuanto más familiarizada está una persona con el código, más tiende a pensar que meter el código en funciones pequeñas es el camino correcto.
    Como ya construyó un modelo mental de ese código, para esa persona la implementación más limpia es una con muy pocas líneas.
    Pero cuando llega la siguiente persona, tiene que ir y venir por todos lados para construir el mismo modelo mental sin el contexto original, haciendo push/pop en su pila mental, y eso es mucho más difícil.

    • No es así si el código tiene sentido. Si es código bien escrito, con abstracciones elegantes, interfaces delgadas y documentación adecuada, no hace falta ir y venir tanto.
      Por ejemplo, ¿qué tan seguido leemos el código fuente de la biblioteca estándar del lenguaje que usamos? Casi nunca; normalmente vemos la firma del método y, si es algo un poco complejo o nuevo, leemos la documentación.
      El punto de una interfaz es hacer que te importe qué hace un método, no cómo está implementado. Eso se explica mediante una combinación de contexto, nombres y documentación. Pero muchos desarrolladores no lo entienden o no les importa, y escriben código que no tiene sentido, sea lineal o modular.
      Por ejemplo, si en una clase de servicio hay que llamar a un método para obtener ciertos datos, a otro método para obtener otros datos, y a un tercer método para obtener datos que deben combinarse con los dos anteriores, ¿cuál es el sentido de ese servicio? Es como exponer toda la complejidad interna hacia afuera.
      No se trata de imponer métodos pequeños. Veinte funciones de 5 líneas que se llaman una sola vez, hacen algo muy específico y deben llamarse en el orden correcto no tienen sentido. Eso no es código limpio; se parece más a programación cargo cult.
      Lo importante es abstraer adecuadamente para que tenga sentido tanto para los nuevos integrantes del equipo como para los más experimentados, sea fácil de razonar y la complejidad quede oculta en el lugar correcto. No es fácil, pero es posible.
    • No estoy de acuerdo, pero quizá haya una diferencia entre quienes leen y piensan de abajo hacia arriba y quienes piensan de arriba hacia abajo.
      Mi hijo, aunque era bastante inteligente, tuvo dificultades en la escuela, y uno de varios especialistas explicó que la escuela suele enseñar de abajo hacia arriba, mientras que mi hijo aprendía de forma muy de arriba hacia abajo. Él necesitaba primero una visión general antes de entrar en los detalles; otras personas primero captan los detalles y luego ensamblan la visión general. La escuela normalmente enseña pensando en el segundo grupo.
      Puede que entre programadores exista una diferencia similar.
    • Eso de que “la siguiente persona tiene que ir y venir por todos lados” solo aplica cuando esa persona no sabe leer código. El código debe leerse, al menos al principio, tal como está escrito; intentar leerlo en el orden en que se ejecuta es una forma equivocada.
      Si el desarrollador anterior escribió una función BakePizza, basta con asumir que la pizza se horneará correctamente y pasar a la siguiente línea. Si, al intentar entender cómo funciona el restaurante, te pierdes en detalles como la temperatura del horno, terminarás sin entender cómo funciona el restaurante y además olvidarás la temperatura exacta del horno.
    • Por eso necesitamos mejores herramientas, como editores de código proyectivos.
      El editor debería tener un toggle para hacer inline temporalmente una función. Así ya no haría falta ir y venir.