En bases de código complejas, las interacciones complejas de los efectos secundarios son lo más difícil de lo que encuentro para razonar. Solo puedo hablar personalmente dada la forma en que funciona mi cerebro. Los efectos secundarios y los estados persistentes y las entradas de mutaciones, etc., me hacen pensar en "cuándo" y "dónde" suceden las cosas para razonar sobre la corrección, no solo "qué" está sucediendo en cada función individual.
No puedo concentrarme solo en "qué". No puedo concluir, después de probar exhaustivamente una función que causa efectos secundarios, que se extenderá un aire de confiabilidad a lo largo del código que lo usa, ya que las personas que llaman pueden usarlo incorrectamente al llamarlo en el momento incorrecto, desde el hilo incorrecto, en el incorrecto orden. Mientras tanto, una función que no causa efectos secundarios y solo devuelve una nueva salida dada una entrada (sin tocar la entrada) es prácticamente imposible de usar de manera incorrecta.
Pero creo que soy un tipo pragmático, o al menos trato de serlo, y no creo que necesariamente tengamos que eliminar todos los efectos secundarios al mínimo para razonar sobre la corrección de nuestro código (al menos Me resultaría muy difícil hacerlo en lenguajes como C). Donde encuentro muy difícil razonar sobre la corrección es cuando tenemos la combinación de flujos de control complejos y efectos secundarios.
Los flujos de control complejos para mí son de naturaleza gráfica, a menudo recursiva o recursiva (colas de eventos, por ejemplo, que no llaman directamente a eventos recursivamente pero son de naturaleza "recursiva"), tal vez haciendo cosas en el proceso de atravesar una estructura de gráfico vinculada real, o procesar una cola de eventos no homogénea que contiene una mezcla ecléctica de eventos para procesar que nos lleva a todo tipo de diferentes partes de la base de código y todos desencadenan diferentes efectos secundarios. Si intentara dibujar todos los lugares en los que finalmente terminará en el código, se parecería a un gráfico complejo y potencialmente con nodos en el gráfico que nunca esperó que hubieran estado allí en ese momento dado, y dado que están todos causando efectos secundarios,
Los lenguajes funcionales pueden tener flujos de control extremadamente complejos y recursivos, pero el resultado es tan fácil de comprender en términos de corrección porque no hay todo tipo de efectos secundarios eclécticos en el proceso. Es solo cuando los flujos de control complejos se encuentran con efectos secundarios eclécticos que encuentro que induce dolor de cabeza tratar de comprender la totalidad de lo que está sucediendo y si siempre hará lo correcto.
Entonces, cuando tengo esos casos, a menudo me resulta muy difícil, si no imposible, sentirme muy seguro acerca de la corrección de dicho código, y mucho menos muy seguro de que puedo hacer cambios a ese código sin tropezar con algo inesperado. Entonces, la solución para mí es simplificar el flujo de control o minimizar / unificar los efectos secundarios (al unificar, quiero decir que solo causo un tipo de efecto secundario a muchas cosas durante una fase particular del sistema, no dos o tres o un docena). Necesito que suceda una de esas dos cosas para permitir que mi cerebro simplón se sienta seguro acerca de la corrección del código que existe y la corrección de los cambios que introduzco. Es bastante fácil confiar en la exactitud del código que introduce efectos secundarios si los efectos secundarios son uniformes y simples junto con el flujo de control, de la siguiente manera:
for each pixel in an image:
make it red
Es bastante fácil razonar sobre la corrección de dicho código, pero principalmente porque los efectos secundarios son muy uniformes y el flujo de control es muy simple. Pero digamos que teníamos un código como este:
for each vertex to remove in a mesh:
start removing vertex from connected edges():
start removing connected edges from connected faces():
rebuild connected faces excluding edges to remove():
if face has less than 3 edges:
remove face
remove edge
remove vertex
Entonces, este es un pseudocódigo ridículamente simplificado que típicamente implicaría muchas más funciones y bucles anidados y muchas más cosas que tendrían que continuar (actualizar múltiples mapas de textura, pesos óseos, estados de selección, etc.), pero incluso el pseudocódigo hace que sea tan difícil razón sobre la corrección debido a la interacción del flujo de control complejo tipo gráfico y los efectos secundarios que están ocurriendo. Entonces, una estrategia para simplificar eso es diferir el procesamiento y solo enfocarse en un tipo de efecto secundario a la vez:
for each vertex to remove:
mark connected edges
for each marked edge:
mark connected faces
for each marked face:
remove marked edges from face
if num_edges < 3:
remove face
for each marked edge:
remove edge
for each vertex to remove:
remove vertex
... algo en este sentido como una iteración de simplificación. Eso significa que estamos pasando por los datos varias veces, lo que definitivamente está incurriendo en un costo computacional, pero a menudo encontramos que podemos multiprocesar dicho código resultante más fácilmente, ahora que los efectos secundarios y los flujos de control han adquirido esta naturaleza uniforme y más simple. Además, cada bucle se puede hacer más amigable con el caché que atravesar el gráfico conectado y causar efectos secundarios a medida que avanzamos (por ejemplo: use un conjunto de bits paralelos para marcar lo que debe atravesarse para que luego podamos hacer los pases diferidos en orden secuencial ordenado usando máscaras de bits y FFS). Pero lo más importante, encuentro que la segunda versión es mucho más fácil de razonar en términos de corrección y cambio sin causar errores. Así que eso'
Y después de todo, necesitamos que ocurran efectos secundarios en algún momento, o de lo contrario solo tendríamos funciones que generarán datos sin ningún lugar a donde ir. A menudo necesitamos grabar algo en un archivo, mostrar algo en una pantalla, enviar los datos a través de un socket, algo de este tipo, y todas estas cosas son efectos secundarios. Pero definitivamente podemos reducir la cantidad de efectos secundarios superfluos que ocurren, y también reducir la cantidad de efectos secundarios que ocurren cuando los flujos de control son muy complicados, y creo que sería mucho más fácil evitar errores si lo hiciéramos.