He escuchado muchas veces cuando otros desarrolladores usan esa frase para "anunciar" algunos patrones o desarrollar mejores prácticas. La mayoría de las veces esta frase se usa cuando se habla de los beneficios de la programación funcional.
La frase "Fácil de razonar" se ha utilizado tal cual, sin ninguna explicación o muestra de código. Entonces, para mí, se convierte en la próxima palabra "zumbido", que los desarrolladores más "experimentados" usan en sus conversaciones.
Pregunta: ¿Puede proporcionar algunos ejemplos de "No es fácil razonar sobre", de modo que pueda compararse con los ejemplos de "Fácil razonar sobre"?
Respuestas:
En mi opinión, la frase "fácil de razonar", se refiere al código que es fácil de "ejecutar en su cabeza".
Cuando se mira un fragmento de código, si es breve, claramente escrito, con buenos nombres y una mínima mutación de valores, trabajar mentalmente en lo que hace el código es una tarea (relativamente) fácil.
Un código largo con nombres pobres, variables que cambian constantemente de valor y ramificaciones enrevesadas normalmente requerirán, por ejemplo, un bolígrafo y una hoja de papel para ayudar a realizar un seguimiento del estado actual. Por lo tanto, dicho código no se puede resolver fácilmente en su cabeza, por lo que no es fácil razonar sobre ese código.
fuente
"Code easy to reason about" almost exclusively alludes to its mathematical properties and formal verification
: eso suena más o menos como una respuesta a la pregunta. Es posible que desee publicar eso como una respuesta en lugar de estar en desacuerdo sobre cuál es la respuesta (subjetiva) en los comentarios.Es fácil razonar sobre un mecanismo o parte de código cuando necesita tener en cuenta algunas cosas para predecir lo que hará, y las cosas que debe tener en cuenta están fácilmente disponibles.
Las funciones verdaderas sin efectos secundarios y sin estado son fáciles de razonar porque la salida está completamente determinada por la entrada, que está justo allí en los parámetros.
Por el contrario, un objeto con estado es mucho más difícil de razonar, porque debe tener en cuenta en qué estado se encuentra el objeto cuando se llama a un método, lo que significa que tiene que pensar qué otras situaciones podrían llevar al objeto a estar en un estado estado particular.
Peor aún son las variables globales: para razonar sobre el código que lee una variable global, debe comprender en qué parte de su código se puede establecer esa variable y por qué, y puede que ni siquiera sea fácil encontrar todos esos lugares.
Lo más difícil de razonar es la programación multiproceso con estado compartido, porque no solo tiene estado, tiene múltiples hilos que lo cambian al mismo tiempo, por lo que razonar sobre lo que hace un fragmento de código cuando lo ejecuta un solo hilo. tiene que permitir la posibilidad de que en cada punto de ejecución, algún otro hilo (¡o varios de ellos!) podría estar ejecutando casi cualquier otra parte del código y cambiar los datos que está operando bajo sus ojos. En teoría, eso se puede gestionar con mutexes / monitores / secciones críticas / como se llame, pero en la práctica ningún ser humano es capaz de hacerlo de manera confiable a menos que reduzcan drásticamente el estado compartido y / o el paralelismo a muy pequeño secciones del código.
fuente
make
o incluso la especialización de plantillas C ++ y la sobrecarga de funciones) pueden ponerlo nuevamente en la posición de considerar todo el programa. Incluso una vez que crees que has encontrado la definición de algo, el lenguaje permite una declaración más específica en cualquier parte del programa para anularlo. Su IDE podría ayudar con esto.sealed
no ser el predeterminado?En el caso de la programación funcional, el significado de "Fácil de razonar" es principalmente que es determinista. Con eso, quise decir que una entrada dada siempre conducirá a la misma salida. Puede hacer lo que quiera con el programa, siempre que no toque ese código, no se romperá.
Por otro lado, OO es típicamente más difícil de razonar porque la "salida" producida depende del estado interno de cada objeto involucrado. La forma típica en que se manifiesta son efectos secundarios inesperados : al cambiar una parte del código, se rompe una parte aparentemente no relacionada.
... la desventaja de la programación funcional es, por supuesto, que en la práctica, mucho de lo que quiere hacer es IO y estado de gestión.
Sin embargo, hay muchas otras cosas que son más difíciles de razonar, y estoy de acuerdo con @Kilian en que la concurrencia es un buen ejemplo. Sistemas distribuidos también.
fuente
Evitar una discusión más amplia y abordar la pregunta específica:
Me refiero a "La historia de Mel, un verdadero programador" , una pieza de folklore de programadores que data de 1983 y, por lo tanto, cuenta como "leyenda" para nuestra profesión.
Cuenta la historia de un programador que escribe código que prefiere técnicas arcanas siempre que sea posible, incluido el código autorreferencial y auto modificable, y la explotación deliberada de errores de máquina:
Este es un ejemplo de código que es 'difícil de razonar'.
Por supuesto, Mel no estaría de acuerdo ...
fuente
Puedo proporcionar un ejemplo, y uno muy común.
Considere el siguiente código C #.
Ahora considere esta alternativa.
En el segundo ejemplo, sé exactamente lo que está haciendo este código de un vistazo. Cuando veo
Select
, sé que una lista de elementos se está convirtiendo en una lista de otra cosa. Cuando veoWhere
, sé que ciertos elementos se están filtrando. De un vistazo, puedo entender lo quenames
es y hacer un uso efectivo de él.Cuando veo un
for
bucle, no tengo idea de lo que está sucediendo hasta que realmente leo el código. Y a veces tengo que rastrearlo para asegurarme de haber tenido en cuenta todos los efectos secundarios. Tengo que hacer un poco de trabajo para incluso comprender qué nombres son (más allá de la definición de tipo) y cómo usarlos de manera efectiva. Por lo tanto, el primer ejemplo es más difícil de razonar que el segundo.En última instancia, ser fácil de razonar aquí también depende de comprender los métodos
Select
y LINQWhere
. Si no los conoce, el segundo código es más difícil de razonar inicialmente. Pero solo paga el costo para comprenderlos una vez. Usted paga el costo de comprender unfor
ciclo cada vez que usa uno y nuevamente cada vez que cambia. A veces vale la pena pagar el costo, pero generalmente es "más fácil razonar" es mucho más importante.fuente
Una frase relacionada es (parafraseo),
Un ejemplo de relativamente "fácil de razonar" podría ser RAII .
Otro ejemplo podría ser evitar el abrazo mortal : si puede mantener un bloqueo y adquirir otro bloqueo, y hay muchos bloqueos, es difícil asegurarse de que no haya un escenario en el que pueda ocurrir un abrazo mortal. Agregar una regla como "solo hay un bloqueo (global)" o "no se le permite adquirir un segundo bloqueo mientras mantiene un primer bloqueo" hace que el sistema sea relativamente fácil de razonar.
fuente
CComPtr<>
) con función de estilo C (CoUninitialize()
). También me parece un ejemplo extraño, hasta donde recuerdo que invocas CoInitialize / CoUninitialize en el alcance del módulo y durante toda la vida útil del módulo, por ejemplo, dentromain
o fueraDllMain
, y no en un pequeño alcance de función local de corta duración como se muestra en el ejemplo .main
función de punto de entrada ( ) para una aplicación. Inicializa COM al inicio y luego lo desinicializa justo antes de salir. Excepto que tiene objetos globales, como punteros inteligentes COM, que utilizan el paradigma RAII. Con respecto a los estilos de mezcla: un objeto global que inicializó COM en su ctor y no inicializado en su dtor es viable, y lo que Raymond sugiere, pero es sutil y no es fácil razonar.El quid de la programación es el análisis de casos. Alan Perlis comentó sobre esto en el Epigrama # 32: los programadores no deben ser medidos por su ingenio y su lógica, sino por la integridad de su análisis de casos.
Es fácil razonar sobre una situación si el análisis de casos es fácil. Esto significa que hay pocos casos a considerar o, en su defecto, pocos casos especiales ; puede haber algunos espacios grandes de casos, pero que colapsan debido a algunas regularidades, o sucumben a una técnica de razonamiento como la inducción.
Una versión recursiva de un algoritmo, por ejemplo, suele ser más fácil de razonar que una versión imperativa, porque no aporta casos superfluos que surgen de la mutación de variables de estado de soporte que no aparecen en la versión recursiva. Además, la estructura de la recursión es tal que se ajusta a un patrón matemático de prueba por inducción. No tenemos que considerar complejidades como las variantes de bucle y las precondiciones estrictas más débiles y demás.
Otro aspecto de esto es la estructura del espacio del caso. Es más fácil razonar sobre una situación que tiene una división plana o mayormente plana en casos en comparación con una situación jerárquica de casos: casos con sub-casos y sub-sub casos, etc.
Una propiedad de los sistemas que simplifica el razonamiento es la ortogonalidad : esta es la propiedad de que los casos que gobiernan los subsistemas permanecen independientes cuando esos subsistemas se combinan. Ninguna combinación da lugar a "casos especiales". Si un elemento de cuatro casos se combina con un elemento de tres casos ortogonalmente, hay doce casos, pero idealmentecada caso es una combinación de dos casos que permanecen independientes. En cierto sentido, en realidad no hay doce casos; las combinaciones son simplemente "fenómenos emergentes similares a casos" de los que no tenemos que preocuparnos. Lo que esto significa es que todavía tenemos cuatro casos en los que podemos pensar sin considerar los otros tres en el otro subsistema, y viceversa. Si algunas de las combinaciones tienen que ser especialmente identificadas y dotadas de lógica adicional, entonces el razonamiento es más difícil. En el peor de los casos, cada combinación tiene un manejo especial, y luego hay doce casos nuevos, que se suman a los cuatro y tres originales.
fuente
Seguro. Tomar concurrencia:
Secciones críticas impuestas por mutexes: fácil de entender porque solo hay un principio (dos hilos de ejecución no pueden entrar en la sección crítica simultáneamente), pero son propensos tanto a la ineficiencia como al punto muerto.
Modelos alternativos, por ejemplo, programación sin bloqueo o actores: potencialmente mucho más elegantes y potentes, pero terriblemente difíciles de entender, porque ya no puede confiar en conceptos (aparentemente) fundamentales como "ahora escriba este valor en ese lugar".
Ser fácil de razonar es un aspecto de un método. Pero elegir qué método usar requiere considerar todos los aspectos en combinación.
fuente
Limitemos la tarea al razonamiento formal. Porque el razonamiento humorístico, inventivo o poético tiene leyes diferentes.
Aun así, la expresión está débilmente definida y no se puede establecer de manera estricta. Pero eso no significa que deba permanecer tan oscuro para nosotros. Imaginemos que una estructura está pasando una prueba y obteniendo calificaciones para diferentes puntos. Las buenas notas para CADA punto significan que la estructura es conveniente en todos los aspectos y, por lo tanto, "Fácil de razonar".
La estructura "Fácil de razonar" debería obtener buenas notas para lo siguiente:
¿Es subjetiva la prueba? Sí, naturalmente lo es. Pero la expresión en sí es subjetiva también. Lo que es fácil para una persona, no es fácil para otra. Por lo tanto, las pruebas deben ser diferentes para los diferentes dominios.
fuente
La idea de que es posible razonar sobre lenguajes funcionales proviene de su historia, específicamente ML, que se desarrolló como un lenguaje de programación análogo a las construcciones que la lógica para funciones computables utilizó para razonar. La mayoría de los lenguajes funcionales están más cerca de los cálculos formales de programación que los imperativos, por lo que la traducción del código a la entrada de un sistema de sistema de razonamiento es menos onerosa.
Para un ejemplo de un sistema de razonamiento, en el cálculo pi, cada ubicación de memoria mutable en un lenguaje imperativo debe representarse como un proceso paralelo separado, mientras que una secuencia de operaciones funcionales es un proceso único. Cuarenta años después de la prueba del teorema de LFC, estamos trabajando con GB de RAM, por lo que tener cientos de procesos es menos problemático: he usado el cálculo pi para eliminar posibles puntos muertos de unos cientos de líneas de C ++, a pesar de que la representación tiene cientos de procesa el razonador si agota el espacio de estado en alrededor de 3 GB y cura un error intermitente. Esto habría sido imposible en los años 70 o habría requerido una supercomputadora a principios de los 90, mientras que el espacio de estado de un programa de lenguaje funcional de tamaño similar era lo suficientemente pequeño como para razonar en ese momento.
De las otras respuestas, la frase se está convirtiendo en una frase de moda aunque la ley de Moore erosiona gran parte de la dificultad que dificultaba razonar sobre los idiomas imperativos.
fuente
Es fácil razonar sobre un término culturalmente específico, por lo que es tan difícil encontrar ejemplos concretos. Es un término que está anclado a las personas que deben razonar.
"Fácil de razonar" es en realidad una frase muy descriptiva. Si uno está mirando el código y quiere razonar lo que hace, es fácil =)
Está bien, descomponiéndolo. Si está buscando código, generalmente quiere que haga algo. Debes asegurarte de que haga lo que crees que debería hacer. Entonces, desarrollas teorías sobre lo que debería hacer el código, y luego razonas al respecto para tratar de discutir por qué el código realmente funciona. Intenta pensar en el código como un humano (en lugar de como una computadora) e intenta racionalizar los argumentos sobre lo que puede hacer el código.
El peor caso para "fácil de razonar" es cuando la única forma de dar sentido a lo que hace el código es ir línea por línea a través del código como una máquina de Turing para todas las entradas. En este caso, la única forma de razonar algo sobre el código es convertirte en una computadora y ejecutarlo en tu cabeza. Estos peores ejemplos se ven fácilmente en concursos de programación obscurecidos, como estas 3 líneas de PERL que descifran RSA:
En cuanto a lo fácil de razonar, nuevamente, el término es altamente cultural. Tienes que considerar:
Cada uno de estos afecta "fácil de razonar" de manera diferente. Tome las habilidades del razonador como ejemplo. Cuando comencé en mi empresa, se recomendó que desarrollara mis scripts en MATLAB porque es "fácil razonar". ¿Por qué? Bueno, todos en la compañía conocían a MATLAB. Si escogiera un idioma diferente, sería más difícil que alguien me entendiera. No importa que la legibilidad de MATLAB sea atroz para algunas tareas, simplemente porque no fue diseñada para ellas. Más tarde, a medida que avanzaba mi carrera, Python se hizo cada vez más popular. De repente, el código MATLAB se volvió "difícil de razonar" y Python era el lenguaje de preferencia para escribir código sobre el cual era fácil razonar.
También considere qué idoms puede tener el lector. Si puede confiar en que su lector reconocerá una FFT en una sintaxis particular, es "más fácil razonar" sobre el código si se adhiere a esa sintaxis. Les permite ver el archivo de texto como un lienzo en el que pintaste un FFT, en lugar de tener que entrar en los detalles esenciales. Si usa C ++, descubra cuánto se sienten cómodos sus lectores con la
std
biblioteca. ¿Cuánto les gusta la programación funcional? Algunos de los modismos que salen de las bibliotecas de contenedores dependen mucho del estilo idomático que prefiera.También es importante entender qué tipo de preguntas puede interesarle al lector responder. ¿Están sus lectores preocupados principalmente por la comprensión superficial del código, o están buscando errores en las entrañas?
Qué tan seguro debe ser el lector es realmente interesante. En muchos casos, el razonamiento confuso es suficiente para sacar el producto por la puerta. En otros casos, como el software de vuelo FAA, el lector querrá tener un razonamiento irresistible. Me encontré con un caso en el que defendía el uso de RAII para una tarea en particular, porque "puedes configurarlo y olvidarte de él ... hará lo correcto". Me dijeron que estaba equivocado sobre eso. Los que iban a razonar sobre este código no eran el tipo de personas que "solo quieren olvidarse de los detalles". Para ellos, RAII era más como un chad colgante, obligándolos a pensar en todas las cosas que pueden suceder cuando dejas el alcance.
fuente