Rebuses en el código y cómo descifrarlos. El poder secreto de los identificadores

El código puro se lee como una prosa bien escrita.
Grady Butch en código limpio

Rebus como código




¿Qué es un rebus? Este es un mensaje encriptado. El autor del acertijo toma texto humano ordinario y lo codifica usando dibujos, números y letras. Y observamos dicho cifrado e intentamos leer el texto fuente.

El acertijo tiene dos formas. Por un lado, el acertijo es el texto original sin cifrar y, por otro, los dibujos cifrados. El texto es el "qué" del acertijo, su significado, mensaje. Las imágenes son "cómo": cómo se cifra exactamente el mensaje, por qué medios. Adivinando el acertijo, traducimos "cómo" a "qué".

Los dibujos son el lenguaje del acertijo, su arsenal de medios expresivos. El rebusnik, por así decirlo, nos habla con la ayuda de estos dibujos, informa algo. No se le permite usar palabras humanas normales.

Así es como se leen los rompecabezas:



El código es como un acertijo


El código del programa tiene algo en común con el rebus: también tiene su propio "qué" y "cómo". Y a veces también tiene que ser descifrado.

El "qué" de un código es su propósito, es decir, ese efecto y el resultado final que esperamos de él. ¿Qué está haciendo exactamente?

"Cómo" del código: de qué manera concreta cumplirá su "qué", con qué tareas específicas, multiplicaciones, comparaciones; implementación del algoritmo, instrucciones para el procesador. Es un lenguaje de código permitido, su arsenal de medios expresivos.

Martin Fowler habla de esto de esta manera ( "Function Length" , original ):
Smalltalk en esos años trabajó en máquinas en blanco y negro. Si necesita resaltar texto o gráficos, debe revertir el video. La clase en Smalltalk, responsable del cronograma, contenía el método 'resaltado', y en su implementación solo había una línea: una llamada al método 'inverso'. El nombre del método fue más largo que la implementación, pero no importó, porque hay una gran distancia entre la intención y la implementación de este código.
Aquí lo más destacado es el "qué". En la terminología de Martin - intención : "resaltar un fragmento de la imagen". El nombre expresa lo que hace esta función. El reverso es cómo, implementación . Cómo se realiza exactamente el resaltado (usando la inversión de imagen). Esa es la diferencia entre "qué" y "cómo".

A pesar de que el nombre del método es más largo que su implementación, la existencia de dicho método tiene sentido por una razón muy simple. Cuando vemos una llamada inversa en el código, debemos entender o recordar que la inversión de imagen se utiliza para hacer que esta imagen sea más visible. Cuando vemos resaltado, simplemente leemos: "haz que este fragmento sea más visible". En el primer caso, dedicamos un poco de esfuerzo mental para comprender la misión asignada al código, en el segundo, no. En el primer caso, vemos un acertijo frente a nosotros, que requiere descifrado, en el segundo, una historia en un lenguaje comprensible.

Un programador, cuando escribe un programa, es como un acertijo. El programador cifra la descripción humana del algoritmo utilizando las herramientas de lenguaje de programación disponibles (más primitivas que el lenguaje humano). Cifra "qué" con "cómo". Y más tarde, él o su colega leen el código, descifrando estos rechazos de la descripción inicial del algoritmo. Si en el proceso de leer el código no podemos entender de inmediato a qué resultado conducirá la ejecución de cada fragmento, es decir, cuál es el propósito, el significado del código, entonces este código es un rebus, y debe reescribirse en un lenguaje claro.

El problema con los acertijos en el código es que siempre requieren un esfuerzo mental. Incluso si no llevamos a cabo el conjunto completo de operaciones de descifrado en nuestras mentes, pero simplemente recordamos estúpidamente el significado de algunos acertijos, seguirá creando una carga: en primer lugar, recordar su significado y, en segundo lugar, al momento de registrar la transformación rebus en este valor.

Descifrar el acertijo al leer el código es la transformación mental de la que habla Tim Ottinger en el libro Clean Code. Es cierto, allí los analiza en el contexto de la asignación de nombres inteligibles a las variables, pero el problema se ve afectado exactamente por lo mismo. Palabra a Tim:
Como regla general, los programadores son muy inteligentes. Y a las personas inteligentes a veces les gusta mostrar el poder de la inteligencia, demostrando su habilidad para hacer malabarismos mentales. Al final, si recuerda que la variable r contiene una URL con un host remoto y un esquema convertido a minúsculas, esto es claramente indicativo de su mente.
Una de las diferencias entre un programador inteligente y un programador profesional es que un profesional comprende: la claridad es primordial. Los profesionales usan su poder para bien y escriben código que sea comprensible para otras personas.
Incluso una pequeña carga de cada rebus puede convertirse en un problema si hay muchos de esos rebus. Probablemente haya encontrado un código cuya lectura es simplemente agotadora. Saber: los rechazos en el código son los culpables de su cansancio. Los autobuses exacerban la fatiga de incluso su propio autor directamente en el proceso de escribir código. Después de todo, mientras escribe código, el programador también re-lee continuamente lo que se escribió. A pesar de que el autor no descifra sus propios acertijos, sino que simplemente recuerda, todavía crean una carga. ¡La trampa es que el autor simplemente no ve rompecabezas en su propio código ! ¡Intenta imaginar cuánto esfuerzo mental puedes ahorrar en la noche, si comienzas a deshacerte de los acertijos en tu código por la mañana!

Por lo tanto, para reducir la fatiga de escribir y leer códigos, debe evitar los acertijos. ¿Pero cómo hacerlo?

El lenguaje del código. Fuerza del identificador


Estoy de acuerdo con la declaración de Grady Butch de que el código limpio se lee como una buena prosa. Esta es una condición necesaria, aunque no suficiente. La mayoría de nosotros entenderá intuitivamente lo que está en juego, pero me gustaría obtener al menos alguna definición: qué es: buena prosa.

Le pregunté a mis colegas escritores: ¿en qué se diferencia la buena prosa de la mala prosa? Todos respondieron de manera diferente, pero de alguna manera enfatizaron la importancia del lenguaje: debe ser rico, debe crear imágenes claras en la mente y el alma del lector. Cuando el lector tiene fácilmente una imagen clara que el autor quería dibujar para él, estamos tratando con una buena prosa.

El código le dice al procesador lo que debe hacer. Al mismo tiempo, un buen código le dice al programador, y, además, ¡muy sinceramente! ¿Qué está haciendo aquí? Es decir, establece su algoritmo lo más cerca posible de cómo lo habría hecho el propio autor en un lenguaje natural. Nuestro código debe hacerlo muy bien, de lo contrario, un maníaco desenfrenado con una motosierra o una escopeta puede venir a nuestra casa. El código no debe ser un acertijo.

¿Qué herramientas tiene el código para no ser un acertijo?

Una persona puede entender fácilmente una historia en nombre del código si el código en sí habla en lenguaje humano. Esto solo se puede lograr con la ayuda de identificadores: nombres de funciones, clases, variables y constantes, porque solo en los identificadores podemos usar cualquier palabra del lenguaje humano que necesitemos .

Por supuesto, las palabras clave de un lenguaje de programación también son palabras humanas, pero su vocabulario es demasiado miserable. Algo así como el lenguaje de Ellochka el Ogro: no se puede escribir una buena prosa en él.

Por lo tanto, es vital que el código del programa contenga tantos identificadores correctamente elegidos como sea posible. Para que su totalidad forme la prosa muy bien escrita.

Vea lo fácil que es leer líneas de código cuando los nombres de variables y métodos están bien elegidos:

pageData.hasAttribute("Test") dom_tree.to_html() emails_str.split(',') 

Mirando estas frases cortas, es fácil entender de qué están hablando. Sabemos qué resultado obtenemos, porque los identificadores nos lo dicen. Ahora imagine que en el lugar de cada una de esas llamadas está su implementación: ¿cuánto disminuirá la velocidad de lectura de dicho código "encriptado"?

Muchas de las técnicas de refactorización más simples: constantes con nombre, selección de un método, reemplazo de una variable con una llamada al método, una variable explicativa, división de una variable temporal, etc., tienen que ver con cómo hacer que el código hable lenguaje humano, en otras palabras, cómo evitar acertijos .

Método Rebus


Cuando leía Pure Code, periódicamente me visitaba el pensamiento: "¡Qué demonios!".

Desde la altura de sus 40 años de experiencia, Robert Martin nos da consejos sobre cómo mejorar el código. Por ejemplo:
Primera regla: las funciones deben ser compactas. La segunda regla: las funciones deberían ser aún más compactas.
Y luego admite que no puede fundamentar científicamente su afirmación. Honestamente, no científico, también lo hace mal. El requisito de la compactación de la función ya está empezando a parecerse a un dogma, por eso el debate sobre la cuestión de la duración de una función no ha disminuido durante tantas décadas.

Y Bob también ofrece escribir cada función para que realice una sola operación. Además, cuál es esta operación, tampoco está muy clara. Tenemos que pedir ayuda al principio de un único nivel de abstracción, que confunde aún más la situación. Todo esto es demasiado brumoso.

Martin Fowler es más pragmático.

Me parece que el argumento sobre la separación de intención y realización tiene más significado. Si, mirando un fragmento de código, necesita hacer un esfuerzo para comprender lo que está haciendo, entonces debe ponerlo en una función y darle un nombre de acuerdo con este "qué". Luego, la próxima vez, el propósito de la función será inmediatamente obvio, y en la mayoría de los casos no le importará cómo la función hace su trabajo.

El original
Sin embargo, el argumento que tiene más sentido para mí es la separación entre intención e implementación. Si tiene que hacer un esfuerzo para mirar un fragmento de código para averiguar qué está haciendo, entonces debe extraerlo en una función y nombrar la función después de ese "qué". De esa manera, cuando lo lea de nuevo, el propósito de la función salta a la vista, y la mayoría de las veces no tendrá que preocuparse por cómo la función cumple su propósito, que es el cuerpo de la función.
Ya mejor. ¿Ves ahora lo que Martin quería decir en este pasaje? Él quiso decir: eliminemos los rompecabezas. Deje que el código mismo nos diga cuál será el resultado y cómo, déjelo estar escondido en algún lugar más alejado, en la definición de la función. Deje que todos los rompecabezas se descifren. Sin acertijos, sin esfuerzo.

En ningún caso debe aplicar ciegamente métodos de refactorización. Esto es tan obvio, pero ¿cómo entender cuándo es realmente necesaria la refactorización y cuándo no? El método rebus dice: si después de refactorizar, el rebus no desaparece, entonces no se necesita refactorizar .

Si no puede encontrar un nombre para una nueva función que explique claramente lo que está sucediendo en ella, es una señal de que está haciendo algo mal aquí. Intente seleccionar un fragmento de código ligeramente diferente en la función, para que pueda encontrar rápidamente un nombre corto y comprensible.

Un ejemplo de decodificación de rompecabezas en código (no muy exitoso)


Como tal, citaré un fragmento del libro "Código limpio" que me gustó. Antes de refactorizar, vemos un código lleno de rompecabezas. La refactorización fue realizada por el autor del libro de acuerdo con las reglas de buen código promovidas por él, y, solo una coincidencia, el código refactorizado se ve exactamente como el código en el que se descifran los rechazos.

El autor de refactorización aplicó completamente identificadores legibles por humanos (nombres de clase, métodos y variables) para indicar qué hace realmente el código. Es una pena que no en todas partes haya tenido éxito, y en algunos lugares aparecieron nuevos acertijos en lugar de los acertijos anteriores.

Por ejemplo, el método de inclusión más utilizado en este pasaje

 private void include(String pageName, String arg) throws Exception { WikiPage inheritedPage = findInheritedPage(pageName); if (inheritedPage != null) { String pagePathName = getPathNameForPage(inheritedPage); buildIncludeDirective(pagePathName, arg); } } 

El nombre no refleja en absoluto lo que está sucediendo en la implementación. ¿Qué incluye y dónde?

Mirando la llamada a este método:

 include("TearDown", "-teardown"); 

Es imposible decir qué resultado iba a lograr el autor del código aquí.

Siguiente: ¿qué hace buildIncludeDirective? A juzgar por el nombre, debería redactar algún tipo de directiva de inclusión, ¿y qué? ¿Traerla de vuelta? Pero no Inmediatamente lo agrega al resultado general.

Y aquí hay otro updatePageContent. ¿Qué nos dice updatePageContent sobre qué resultado obtenemos después de llamar al método? Nada Algunos contenidos de la página serán reemplazados por nadie sabe qué. ¿Por qué se realizó la refactorización llamada extracción de método aquí? ¿Ayudó a deshacerse del rebus? No ayudó, pero solo confundió más el código. Aquí tenemos el caso en el que es preferible el cuerpo del método. Construcción

 pageData.setContent(newPageContent.toString()); 

mucho más claro que el updatePageContent críptico ().

Como entretenimiento, sugiero a los lectores que busquen otros lugares malos en el código refactorizado .

Para justificar a Bob, puedo decir que este código ya no está en la versión actual de FitNesse. Aparentemente, a su vez, él también fue refactorizado una vez.

Conclusión


La longitud de la función es un criterio demasiado vago para determinar la calidad de la función. "Funciones cortas" no es igual a "buenas funciones". La longitud de la función no es un criterio, olvídalo todo.

Un buen código debería dar respuestas a las preguntas del programador: por qué está él (el código) aquí, qué demonios está haciendo aquí, está logrando el resultado. Estas respuestas solo pueden darse usando identificadores.

Como ejemplo de qué tipo de respuestas no debería dar el código, quiero dar un extracto de un libro divertido.
"Soy Ronan, Víctor del Mal", dijo lentamente. - Y este es Tarl. Te queremos un poco
hacer preguntas Si mientes, mueres. Lo tengo
"Yo, tío, para siempre", suspiró. Por favor Lo diré todo.
"Eso es bueno", continuó Ronan. - Nombre?
- Ronan, ganador del mal.
- ¡Sí, no mío, idiota!
"Ah, sí, entonces Tarle", respondió el orco en tono de disculpa.
- ¡Y no el mío! Murmuró Tarle. - Tu nombre, club! Nombre!
"El nombre es el nombre que uso para distinguirme de los demás", murmuró el orco.
- Bueno, dale este nombre aquí! Gritó Tarle.
Orka amaneció de repente.
- Ah! Espinilla!
"Así que Grano, ¿qué haces aquí?"
"Lo puse en mis pantalones", fue la respuesta veraz.
Ronan arrugó la nariz con disgusto.
"¡No, te pregunto qué está haciendo tu banda de orcos aquí!"
Los ojos de Grano se giraron rápidamente, mirando alrededor de la escena.
"La mayoría de las personas no tienen cabeza aquí", murmuró.
Tarle tocó a Ronan en el hombro.
"Déjame intentarlo", dijo con confianza y se volvió hacia el asustado orco. - dime
Espinilla —continuó—, ¿por qué estás aquí?
- Oh, tío, y no preguntes. La filosofía existencial para mí es solo un bosque oscuro.
"Escucha, eructo dragón", gruñó ahogado. - Tu pandilla de orcos tenía un especial
razón para venir aquí ¿Qué es, en el bosque?
- Hay muchos arboles.
Los ojos de Ronan se hincharon y Tarle se volvió. El grano, sintiendo que no había dado la respuesta que se esperaba, comenzó a murmurar más.
- Y si quieres saber sobre la razón, y no sobre el bosque, todo es porque esa persona en el pub
nos pagó para venir aquí y matarte.
James Bibby, Ronan el Bárbaro

Source: https://habr.com/ru/post/es419269/


All Articles