Legibilidad de código



El código se usa para crear interfaces. Pero el código en sí es una interfaz.


A pesar del hecho de que la legibilidad del c√≥digo es muy importante, este concepto est√° mal definido, y a menudo en forma de un conjunto de reglas: use nombres de variables significativos, divida las funciones grandes en otras m√°s peque√Īas y use patrones de dise√Īo est√°ndar.

Al mismo tiempo, seguro, todos tuvieron que lidiar con un código que cumple con estas reglas, pero por alguna razón es una especie de desastre.

Puede intentar resolver este problema agregando nuevas reglas: si los nombres de las variables se vuelven muy largos, necesita refactorizar la l√≥gica principal; si se han acumulado muchos m√©todos auxiliares en una clase, tal vez deber√≠a dividirse en dos; Los patrones de dise√Īo no pueden aplicarse en el contexto incorrecto.

Dichas instrucciones se convierten en un laberinto de decisiones subjetivas, y para navegarlo, necesitará un desarrollador que pueda tomar la decisión correcta, es decir, que ya debe ser capaz de escribir código legible.

Por lo tanto, un conjunto de instrucciones no es una opción. Por lo tanto, tendremos que formular una imagen más amplia de la legibilidad del código.

Por qué se necesita legibilidad


En la práctica, una buena legibilidad generalmente significa que el código es agradable de leer. Sin embargo, uno no puede llegar lejos en esa definición: en primer lugar, es subjetiva y, en segundo lugar, nos obliga a leer un texto ordinario.

Un c√≥digo ilegible se percibe como una novela que pretende ser un c√≥digo: muchos comentarios que revelan la esencia de lo que est√° sucediendo, hojas de texto que deben leerse secuencialmente, formulaciones inteligentes, cuyo √ļnico significado es ser "inteligente", miedo a reutilizar palabras. El desarrollador est√° tratando de hacer que el c√≥digo sea legible, pero est√° apuntando al tipo incorrecto de lectores.

La legibilidad del texto y la legibilidad del código no son lo mismo.

Traducido a Alconost

El código se usa para crear interfaces. Pero el código en sí es una interfaz.

Si el c√≥digo se ve hermoso, ¬Ņsignifica que es legible? La est√©tica es un agradable efecto secundario de la legibilidad, pero como criterio no es muy √ļtil. Quiz√°s en casos extremos, la est√©tica del c√≥digo en el proyecto ayudar√° a retener a los empleados, pero con el mismo √©xito, puede ofrecer un buen paquete social. Adem√°s, todos tienen su propia idea de lo que significa "c√≥digo hermoso". Y con el tiempo, esta definici√≥n de legibilidad se convierte en un torbellino de disputas sobre tabulaci√≥n, espacios, corchetes, "notaci√≥n de camello", etc. Es poco probable que alguien pierda la conciencia cuando vean la sangr√≠a incorrecta, aunque esto atrae la atenci√≥n al verificar el c√≥digo.

Si el c√≥digo produce menos errores, ¬Ņpuede considerarse m√°s legible? Cuantos menos errores, mejor, pero ¬Ņqu√© mecanismo hay? ¬ŅC√≥mo puedo atribuir las vagas sensaciones agradables que experimentas cuando lees el c√≥digo? Adem√°s, no importa cu√°n fruncidas sean las cejas mientras lee el c√≥digo, esto no agregar√° errores.

Si el c√≥digo es f√°cil de editar, ¬Ņes legible? Pero esta, quiz√°s, es la direcci√≥n correcta del pensamiento. Los requisitos cambian, se agregan funciones, surgen errores, y en alg√ļn momento alguien tiene que editar su c√≥digo. Y para no causar nuevos problemas, el desarrollador necesita comprender exactamente qu√© est√° editando y c√≥mo los cambios cambiar√°n el comportamiento del c√≥digo. Entonces, encontramos una nueva regla heur√≠stica: el c√≥digo legible deber√≠a ser f√°cil de editar.

¬ŅQu√© c√≥digo es m√°s f√°cil de editar?


Inmediatamente quiero difuminar: "El código es más fácil de editar cuando los nombres de las variables se dan significativamente", pero simplemente cambiamos el nombre de "legibilidad" a "facilidad de edición". Necesitamos una comprensión más profunda, y no el mismo conjunto de reglas en una forma diferente.

Comencemos olvidando por un momento que estamos hablando de c√≥digo. La programaci√≥n, que tiene varias d√©cadas de antig√ľedad, es solo un punto en la escala de la historia humana. Limit√°ndonos a este "punto", no podemos profundizar.

Por lo tanto, veamos la legibilidad a trav√©s del prisma del dise√Īo de interfaces que encontramos en casi todos los pasos, y no solo con las digitales. El juguete tiene una funcionalidad que lo hace andar o chirriar. La puerta tiene una interfaz que le permite abrirla, cerrarla y bloquearla. Los datos del libro se recopilan en p√°ginas, lo que proporciona un acceso aleatorio m√°s r√°pido que el desplazamiento. Al estudiar dise√Īo, puede aprender mucho m√°s sobre estas interfaces; pregunte al equipo de dise√Īo si puede. En el caso general, todos preferimos buenas interfaces, incluso si no siempre sabemos qu√© las hace buenas.

El c√≥digo se usa para crear interfaces. Pero el c√≥digo en s√≠, combinado con el IDE, es una interfaz. Una interfaz dise√Īada para un grupo muy peque√Īo de usuarios: nuestros colegas. Adem√°s, los llamaremos "usuarios", para permanecer en el espacio de dise√Īo de la interfaz de usuario.

Con esto en mente, considere estos ejemplos de rutas de usuario:

  • El usuario quiere agregar una nueva funci√≥n. Esto requiere encontrar el lugar correcto y agregar una funci√≥n sin generar nuevos errores.
  • El usuario quiere corregir el error. Necesitar√° encontrar la fuente del problema y editar el c√≥digo para que el error desaparezca y no aparezcan nuevos errores.
  • El usuario quiere asegurarse de que, en casos l√≠mite, el c√≥digo se comporte de cierta manera. Necesitar√° encontrar un c√≥digo espec√≠fico, luego rastrear la l√≥gica y simular lo que sucede.

Y as√≠ sucesivamente: la mayor√≠a de los caminos siguen un patr√≥n similar. Para no complicar las cosas, considere ejemplos espec√≠ficos, pero no olvide que esta es una b√ļsqueda de principios generales, no una lista de reglas.

Podemos suponer con seguridad que el usuario no podrá abrir inmediatamente la sección de código deseada. Esto también se aplica a sus propios proyectos de pasatiempos: incluso si la función la ha escrito usted, es muy fácil olvidar dónde se encuentra. Por lo tanto, el código debe ser tal que sea fácil encontrar el correcto en él.

Para implementar una b√ļsqueda conveniente, necesitar√° un poco de optimizaci√≥n de motor de b√ļsqueda; aqu√≠ es para nosotros que los nombres de variables significativas vienen al rescate. Si el usuario no puede encontrar la funci√≥n, movi√©ndose a lo largo de la pila de llamadas desde un punto conocido, puede comenzar una b√ļsqueda por palabras clave. Sin embargo, no puede incluir demasiadas palabras clave en los nombres. Al buscar por c√≥digo, se busca el √ļnico punto de entrada, desde donde puede continuar trabajando m√°s. Por lo tanto, el usuario debe ayudar a llegar a un lugar espec√≠fico, y si se excede con las palabras clave, habr√° demasiados resultados de b√ļsqueda in√ļtiles.

Si el usuario puede asegurarse de inmediato de que todo está correcto en un nivel particular de lógica, puede olvidar las capas anteriores de abstracción y liberar su mente para la siguiente.

También puede buscar usando la finalización automática: si tiene una idea general de a qué función desea llamar o qué enumeración usar, puede comenzar a escribir el nombre deseado y luego seleccionar la opción adecuada de la lista de finalización automática. Si la función está destinada solo a ciertos casos o si necesita leer cuidadosamente su implementación debido a las características de su uso, puede indicarlo dándole un nombre más auténtico: al desplazarse por la lista de autocompletado, el usuario evitará lo que parece complicado, a menos que, por supuesto, esté seguro que hace

Por lo tanto, es más probable que los nombres cortos y regulares se perciban como opciones predeterminadas, adecuadas para usuarios "casuales". No debería haber sorpresas en las funciones con tales nombres: no puede insertar setters en funciones que parecen simples getters, por la misma razón que el botón Ver en la interfaz no debería cambiar los datos del usuario.




En la interfaz orientada al cliente, las funciones familiares, como una pausa, funcionan casi sin texto. A medida que la funcionalidad se vuelve m√°s compleja, los nombres se alargan, lo que hace que los usuarios disminuyan la velocidad y piensen. Captura de pantalla - Pandora

Los usuarios desean encontrar la información correcta rápidamente. En la mayoría de los casos, la compilación lleva una cantidad considerable de tiempo, y en una aplicación en ejecución tendrá que verificar manualmente muchos casos de borde diferentes. Si es posible, nuestros usuarios preferirán leer el código y comprender cómo se comporta, en lugar de establecer puntos de interrupción y ejecutar el código.

Para hacerlo sin ejecutar el código, se deben cumplir dos condiciones:

  1. El usuario entiende lo que el código está tratando de hacer.
  2. El usuario está seguro de que el código hace lo que dice.

Las abstracciones ayudan a satisfacer la primera condición: los usuarios deberían poder sumergirse en capas de abstracción al nivel de detalle deseado. Imagine una interfaz de usuario jerárquica: en los primeros niveles, la navegación se lleva a cabo en secciones extensas, y luego se concreta cada vez más, hasta el nivel de lógica que debe estudiarse con más detalle.

La lectura secuencial de un archivo o m√©todo se realiza en tiempo lineal. Pero si el usuario puede subir y bajar las pilas de llamadas, esta es una b√ļsqueda en el √°rbol, y si la jerarqu√≠a est√° bien equilibrada, esta acci√≥n se realiza en un tiempo logar√≠tmico. Ciertamente hay espacio para listas en las interfaces, pero debe considerar cuidadosamente si debe haber m√°s de dos o tres llamadas a m√©todos en alg√ļn contexto.




En men√ļs cortos, la navegaci√≥n jer√°rquica es mucho m√°s r√°pida. En el men√ļ "largo" a la derecha, solo 11 l√≠neas. ¬ŅCon qu√© frecuencia encajamos en este n√ļmero en el c√≥digo del m√©todo? Captura de pantalla - Pandora

Diferentes usuarios tienen diferentes estrategias para la segunda condici√≥n. En situaciones de bajo riesgo, los comentarios o los nombres de los m√©todos son evidencia suficiente. En √°reas m√°s arriesgadas y complejas, as√≠ como cuando el c√≥digo est√° sobrecargado con comentarios irrelevantes, es probable que estos √ļltimos sean ignorados. A veces, incluso los nombres de m√©todos y variables estar√°n en duda. En tales casos, el usuario debe leer mucho m√°s c√≥digo y tener en cuenta un modelo l√≥gico m√°s amplio. Limitar el contexto a √°reas peque√Īas que son f√°ciles de mantener tambi√©n ayudar√° aqu√≠. Si el usuario puede verificar de inmediato que todo est√° correcto en un nivel particular de l√≥gica, puede olvidar las capas anteriores de abstracci√≥n y liberar su mente para el siguiente.

En este modo de operación, los tokens individuales comienzan a tener mayor importancia. Por ejemplo, una bandera booleana

element.visible = true/false 

es fácil de entender aisladamente del resto del código, pero esto requiere combinar dos tokens diferentes en la mente. Si utilizar

 element.visibility = .visible/.hidden 

entonces el valor de la bandera se puede entender de inmediato: en este caso, no es necesario leer el nombre de la variable para descubrir que est√° relacionado con la visibilidad. Vimos enfoques similares en el dise√Īo de interfaces orientadas al cliente. En las √ļltimas d√©cadas, los botones Aceptar y Cancelar se han convertido en elementos de interfaz m√°s descriptivos: "Guardar" y "Cancelar", "Enviar" y "Continuar editando", etc., para comprender lo que se har√°, es suficiente para que el usuario vea las opciones propuestas sin leer todo el contexto.


La línea "Modo sin conexión" en el ejemplo anterior indica que la aplicación está fuera de línea. El cambio en el ejemplo a continuación tiene el mismo significado, pero para entenderlo, debe mirar el contexto. Captura de pantalla - Pandora

Las pruebas unitarias tambi√©n ayudan a confirmar el comportamiento esperado del c√≥digo: act√ļan como comentarios, que, sin embargo, se puede confiar en mayor medida, ya que son m√°s relevantes. Es cierto que tambi√©n necesitan completar el ensamblaje. Pero en el caso de una canalizaci√≥n de CI bien establecida, las pruebas se ejecutan regularmente, por lo que puede omitir este paso al realizar cambios en el c√≥digo existente.

En teoría, la seguridad se deriva de una comprensión suficiente: tan pronto como nuestro usuario comprenda el comportamiento del código, podrá realizar cambios de forma segura. En la práctica, debes considerar que los desarrolladores son personas comunes: nuestro cerebro usa los mismos trucos y también es perezoso. Por lo tanto, cuanto menos esfuerzo deba dedicar a comprender el código, más seguras serán nuestras acciones.

El c√≥digo legible deber√≠a pasar la mayor√≠a de las verificaciones de error a la computadora. Una de las formas de hacer esto es usar las comprobaciones de depuraci√≥n "aserci√≥n", pero tambi√©n requieren ensamblaje y arranque. Peor a√ļn, si el usuario se ha olvidado de los casos l√≠mite, afirmar no ayudar√°. Las pruebas unitarias para verificar los casos fronterizos que se olvidan con frecuencia pueden mejorar, pero una vez que el usuario haya realizado cambios, tendr√° que esperar a que se ejecuten las pruebas.

En resumen: el código legible debería ser fácil de usar. Y, como efecto secundario, puede verse hermoso.

Para acelerar el ciclo de desarrollo, utilizamos la funci√≥n de verificaci√≥n de errores integrada en el compilador. Por lo general, en tales casos, no se requiere un ensamblaje completo y los errores se muestran en tiempo real. ¬ŅC√≥mo aprovechar esta oportunidad? En t√©rminos generales, debe encontrar situaciones en las que las comprobaciones del compilador se vuelven muy estrictas. Por ejemplo, la mayor√≠a de los compiladores no observan cu√°n exhaustivamente se describe la declaraci√≥n "if", sino que comprueban cuidadosamente el "interruptor" para ver si faltan condiciones. Si un usuario intenta agregar o cambiar una condici√≥n, ser√° m√°s seguro si todos los operadores similares anteriores fueran exhaustivos. Y cuando la condici√≥n de "caso" cambia, el compilador marcar√° todas las dem√°s condiciones que deben verificarse.

Otro problema com√ļn de legibilidad es el uso de primitivas en expresiones condicionales. Este problema es especialmente grave cuando la aplicaci√≥n analiza JSON, porque solo desea agregar declaraciones "if" alrededor de la cadena o la igualdad de enteros. Esto no solo aumenta la probabilidad de errores tipogr√°ficos, sino que tambi√©n complica la tarea de los usuarios de determinar los posibles valores. Al verificar los casos l√≠mite, existe una gran diferencia entre cu√°ndo es posible cualquier l√≠nea y cu√°ndo, solo dos o tres opciones separadas. Incluso si las primitivas se fijan en constantes, debe darse prisa una vez, tratando de terminar el proyecto a tiempo, y aparecer√° un valor arbitrario. Pero si usa objetos o enumeraciones especialmente creados, el compilador bloquea los argumentos no v√°lidos y le da una lista espec√≠fica de los v√°lidos.

Del mismo modo, si algunas combinaciones de banderas booleanas no est√°n permitidas, reempl√°celas con una sola enumeraci√≥n. Tomemos, por ejemplo, una composici√≥n que puede estar en los siguientes estados: est√° almacenada en b√ļfer, completamente cargada y reproducida. Si imagina los estados de carga y reproducci√≥n como dos banderas booleanas

 (loaded, playing) 

el compilador permitir√° la entrada de valores no v√°lidos

 (loaded: false, playing: true) 

Y si usas la enumeración

 (.buffering/.loaded/.playing) 

entonces será imposible indicar un estado no válido. En la interfaz orientada al cliente, lo predeterminado debería ser prohibir combinaciones inválidas de configuraciones. Pero cuando escribimos código dentro de la aplicación, a menudo olvidamos brindarnos la misma protección.




Las combinaciones no válidas se deshabilitan de antemano; los usuarios no necesitan pensar qué configuraciones son incompatibles. Captura de pantalla - Apple

Siguiendo las rutas de usuario consideradas, llegamos a las mismas reglas que al principio. Pero ahora tenemos un principio por el cual pueden formularse independientemente y cambiarse de acuerdo con la situación. Para hacer esto, nos preguntamos:

  • ¬ŅSer√° f√°cil para el usuario buscar el c√≥digo deseado? ¬ŅLos resultados de la b√ļsqueda estar√°n saturados de funciones no relacionadas con la consulta?
  • ¬ŅPuede un usuario, habiendo encontrado el c√≥digo necesario, verificar r√°pidamente la correcci√≥n de su comportamiento?
  • ¬ŅEl entorno de desarrollo proporciona edici√≥n segura y reutilizaci√≥n de c√≥digo?

En resumen: el código legible debería ser fácil de usar. Y, como efecto secundario, puede verse hermoso.

Nota


  1. Puede parecer que las variables booleanas son más convenientes para reutilizar, pero esta opción de reutilización implica intercambiabilidad. Tomemos, por ejemplo, los indicadores tappables y en caché , que representan conceptos ubicados en planos completamente diferentes: la capacidad de hacer clic en un elemento y el estado de almacenamiento en caché. Pero si ambas banderas son booleanas, puede intercambiarlas accidentalmente, obteniendo una expresión no trivial en una línea de código, lo que significará que el almacenamiento en caché está asociado con la capacidad de hacer clic en un elemento. Al usar enumeraciones, para formar tales relaciones, nos veremos obligados a crear una lógica explícita y verificable para la conversión de las "unidades de medida" que utilizamos.


Sobre el traductor

El artículo fue traducido por Alconost.

Alconost localiza juegos , aplicaciones y sitios en 70 idiomas. Traductores nativos, pruebas ling√ľ√≠sticas, plataforma en la nube con API, localizaci√≥n continua, gerentes de proyecto 24/7, cualquier formato de recursos de cadena.

También hacemos videos de publicidad y capacitación , para sitios que venden, imágenes, publicidad, capacitación, teasers, expliner, trailers de Google Play y App Store.

‚Üí Leer m√°s

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


All Articles