Escribir código que sea fácil de eliminar y depurar



El código fácil de depurar es un código que no te engaña. Es más difícil depurar código con un comportamiento oculto, con un manejo deficiente de los errores, con incertidumbres, estructurada de manera insuficiente o excesiva, o en proceso de cambio. En proyectos lo suficientemente grandes, terminas con un código que no puedes entender.

Si el proyecto es relativamente antiguo, es posible que se encuentre con un código que olvidó en absoluto, y si no fuera por el registro de confirmación, juraría que estas líneas no fueron escritas por usted. A medida que el proyecto crece, se hace más difícil recordar lo que hacen los diferentes fragmentos de código. Y la situación se agrava si el código no hace lo que parece estar haciendo. Y cuando necesite cambiar el código que no entiende, debe resolverlo con dificultad: depurar.

La capacidad de escribir código que es fácil de depurar comienza con el entendimiento de que no recuerda nada escrito anteriormente.

Regla 0: Un buen código contiene errores obvios.


Los proveedores de tecnología ampliamente utilizada afirman que "escribir código claro" significa "escribir código limpio". El problema es que el grado de "pureza" es altamente sensible al contexto. El código puro se puede codificar en el sistema y, a veces, se escribe algún truco sucio para que sea fácil deshabilitarlo. A veces, el código se considera limpio, porque toda la suciedad se empuja a alguna parte. Un buen código no es necesariamente limpio.

La limpieza caracteriza el grado de orgullo (o vergüenza) experimentado por el desarrollador en relación con este código, en lugar de la facilidad de mantenimiento o cambio. Es mejor darnos un código aburrido en lugar de uno limpio, cuyos cambios son obvios: descubrí que las personas están más dispuestas a refinar la base del código si la fruta es lo suficientemente baja y fácil de recoger. El mejor código puede ser el que acaba de ver e inmediatamente entendió cómo funciona.

  • Código que no intenta crear un problema feo para verse bien, o un problema aburrido para parecer interesante.
  • Código en el que los errores son obvios y el comportamiento es claro, a diferencia del código sin errores obvios y con un comportamiento poco claro.
  • El código en el que está documentado en el que no es ideal, en contraste con el código que busca la perfección.
  • Código con un comportamiento tan obvio que cualquier desarrollador puede encontrar una infinidad de formas diferentes de modificar este código.

A veces, el código es tan desagradable que cualquier intento de hacerlo más limpio solo agrava la situación. Escribir código sin comprender las consecuencias de sus acciones también puede considerarse como un ritual de invocación de código convenientemente mantenido.

No quiero decir que el código limpio es malo, pero a veces el deseo de limpieza se parece más a barrer la basura debajo de una alfombra. El código de depuración no es necesariamente limpio; y el código repleto de cheques o manejo de errores rara vez es legible.

Regla 1: siempre hay problemas en la computadora


La computadora tiene problemas y el programa se bloqueó durante la última ejecución.

La aplicación primero debe asegurarse de que se inicia desde un estado conocido, bueno y seguro antes de intentar hacer algo. A veces simplemente no hay una copia del estado, porque el usuario la eliminó o actualizó la computadora. El programa se bloqueó durante la última ejecución y, paradójicamente, también durante la primera ejecución.

Por ejemplo, al leer o escribir el estado en un archivo, pueden ocurrir los siguientes problemas:

  • Falta el archivo
  • El archivo esta corrupto.
  • Archivo de una versión anterior o más nueva.
  • El último cambio en el archivo no se ha completado.
  • El sistema de archivos te está mintiendo.

Estos problemas no son nuevos; las bases de datos los han encontrado desde la antigüedad (1970-01-01). El uso de algo como SQLite ayudará a hacer frente a muchos problemas similares, pero si el programa se bloqueó en la última ejecución, entonces el código puede funcionar con datos erróneos y / o de manera errónea.

Por ejemplo, con programas programados, sucederá algo de esta lista:

  • El programa comenzará dos veces en una hora debido al horario de verano.
  • El programa se iniciará dos veces porque el operador ha olvidado que ya se está ejecutando.
  • El programa comenzará tarde debido a la falta de espacio libre en disco o misteriosos problemas en la nube o en la red.
  • El programa se ejecutará durante más de una hora, lo que puede provocar un retraso en las llamadas posteriores al programa.
  • El programa comenzará a la hora incorrecta del día.
  • Inevitablemente, el programa se ejecutará poco antes de la hora límite, por ejemplo, medianoche, fin de mes o año, y fallará debido a errores de cálculo.

La creación de software sostenible comienza con la escritura de software que cree que ha caído la vez anterior y se bloquea si no sabe qué hacer. Lo mejor de lanzar una excepción y dejar un comentario al estilo de "esto no debería suceder" es que cuando esto sucede inevitablemente, tendrá una ventaja inicial para depurar su código.

Un programa ni siquiera está obligado a recuperarse de una falla; es suficiente para dejarlo rendirse y no empeorar la situación. Las pequeñas comprobaciones que generan excepciones pueden ahorrar semanas en la indagación, y un simple archivo de bloqueo puede ahorrar horas en la recuperación de las copias de seguridad.

El código que es fácil de depurar es:

  • un código que verifica si todo está bien antes de hacer lo que le piden;
  • código que facilita volver a un estado conocido e intentar nuevamente;
  • así como código con niveles de seguridad que provocan errores lo antes posible.

Regla 2: tu programa lucha consigo mismo


El mayor ataque DoS en la historia de Google vino de nosotros mismos (porque nuestros sistemas son muy grandes). Aunque de vez en cuando alguien intenta ponernos a prueba en busca de fuerza, aún podemos dañarnos más que a los demás.

Esto se aplica a todos nuestros sistemas.

Astrid Atkinson, ingeniera de juegos largos

Los programas siempre fallan durante la última ejecución; siempre no hay suficiente procesador, memoria o espacio en disco. Todos los trabajadores están trabajando en una cola vacía, todos intentan repetir una solicitud fallida y desactualizada, y todos los servidores al mismo tiempo hacen una pausa durante la recolección de basura. El sistema no solo se rompe, sino que constantemente intenta romperse.

Una gran dificultad puede causar incluso verificar el sistema.

Implementar una verificación de operación del servidor puede ser fácil, pero solo si no procesa las solicitudes. Si no verifica la duración del tiempo de actividad continuo, entonces es posible que el programa se encuentre entre las verificaciones. Los errores también pueden ser activados por controles de salud: tuve que escribir controles que condujeron al colapso del sistema, que tuvieron que proteger. Dos veces, con una diferencia de tres meses.

El código de manejo de errores inevitablemente conducirá al descubrimiento de aún más errores que deben procesarse, muchos de los cuales surgen del manejo de errores en sí. Del mismo modo, las optimizaciones de rendimiento a menudo son la causa de los cuellos de botella en el sistema. Una aplicación que es agradable de usar en una pestaña se convierte en un problema, que se inicia en 20 copias.

Otro ejemplo: un trabajador en una tubería se ejecuta demasiado rápido y consume memoria disponible antes de que la siguiente parte de la tubería acceda a ella. Esto se puede comparar con los atascos de tráfico: surgen debido a un aumento en la velocidad y, como resultado, la congestión del tráfico crece en la dirección opuesta. Entonces, las optimizaciones pueden generar sistemas que caen bajo cargas altas o pesadas, a menudo de maneras misteriosas.
En otras palabras: cuanto más rápido sea el sistema, más fuerte será la presión sobre él, y si no permite que el sistema contrarreste un poco, entonces no se sorprenda si se agrieta.

La acción contraria es una de las formas de retroalimentación del sistema. El programa, que es fácil de depurar, involucra al usuario en el ciclo de retroalimentación, le permite ver todos los comportamientos dentro del sistema, aleatorios, intencionales, deseados y no deseados. Puede inspeccionar fácilmente dicho código, ver y comprender los cambios que ocurren con él.

Regla 3: si dejas algo ambiguo ahora, tendrás que depurarlo más tarde


En otras palabras, debería ser fácil para usted rastrear las variables en el programa y comprender lo que está sucediendo. Tome cualquier rutina con álgebra lineal de pesadilla, debe esforzarse por presentar el estado del programa lo más obvio posible. Esto significa que en medio de un programa no puede cambiar el propósito de una variable, ya que usar una variable para dos propósitos diferentes es un pecado mortal.

Esto también significa que debe evitar con cuidado el problema de los semi-predicados, nunca usar un solo valor ( count ) para representar un par de valores ( boolean , count ). Es necesario evitar devolver un número positivo para el resultado y al mismo tiempo devolver -1 si nada coincide. El hecho es que puede encontrarse fácilmente en una situación en la que necesita algo como " 0, but true " (y esto es exactamente lo que es una característica en Perl 5); o cuando crea código que es difícil de combinar con otras partes del sistema ( -1 para la siguiente parte del programa puede no ser un error, sino un valor de entrada válido).

Además de usar una variable para dos propósitos, no se recomienda usar dos variables para el mismo propósito, especialmente si es booleana. No quiero decir que sea malo usar dos números para almacenar un rango, pero usar números booleanos para indicar el estado de un programa es a menudo una máquina de estado enmascarada.

Cuando un estado no pasa de arriba a abajo, es decir, en el caso de un ciclo episódico, es mejor proporcionar al estado su propia variable y borrar la lógica. Si tiene un conjunto de booleanos dentro del objeto, reemplácelos con una variable llamada state y use enum (o una cadena si es necesario en alguna parte). if expresiones se verán como if state == name , no if bad_name && !alternate_option .

Incluso si crea una máquina de estado explícita, existe la posibilidad de confundir: a veces el código puede tener dos máquinas de estado ocultas en su interior. Una vez que fui torturado para escribir proxies HTTP, hasta que hice explícita cada máquina, rastreé el estado de la conexión y la analicé por separado. Cuando combina dos máquinas de estado en una, puede ser difícil agregar un nuevo estado o comprender exactamente qué estado debe tener algo.

Se trata más de crear código que no tiene que ser depurado que de una depuración fácil. Si desarrolla una lista de estados correctos, será mucho más fácil descartar los incorrectos sin perder accidentalmente uno o dos.

Regla 4: El comportamiento aleatorio es el comportamiento esperado.


Cuando no comprende lo que hace la estructura de datos, los usuarios llenan estos vacíos de conocimiento: cualquier comportamiento del código, intencional o accidental, dependerá en última instancia de algo. Muchos lenguajes de programación populares admiten tablas hash que se pueden iterar y que, en la mayoría de los casos, mantienen el orden después de la inserción.

En algunos idiomas, el comportamiento de la tabla hash cumple con las expectativas de la mayoría de los usuarios, iterando sobre las claves en el orden en que se agregaron. En otros idiomas, la tabla hash en cada iteración devuelve las claves en un orden diferente. En este caso, algunos usuarios se quejan de que el comportamiento no es lo suficientemente aleatorio.

Desafortunadamente, cualquier fuente de aleatoriedad en su programa eventualmente se utilizará para simulación estadística, o peor aún: criptografía; y cualquier fuente de clasificación se utilizará para la clasificación.

En las bases de datos, algunos identificadores contienen un poco más de información que otros. Al crear una tabla, el desarrollador puede elegir entre diferentes tipos de clave primaria. La elección correcta es el UUID, o algo que no se puede distinguir de él. La desventaja de otras opciones es que pueden revelar información de pedidos e identificación. Es decir, no solo a == b , sino a <= b , y otras opciones significan teclas de incremento automático.

Cuando se usa una clave de incremento automático, la base de datos asigna un número a cada fila de la tabla, agregando 1 al insertar una nueva fila. Y la clasificación es vaga: las personas no saben qué parte de los datos es canónica. En otras palabras, ¿ordena por clave o marca de tiempo? Al igual que con una tabla hash, las personas mismas elegirán la respuesta correcta. Y otro problema es que los usuarios pueden predecir fácilmente los registros vecinos con otras claves.

Pero cualquier intento de burlar al UUID fracasará: ya hemos tratado de usar códigos postales, números de teléfono y direcciones IP, y cada vez fallamos miserablemente. Es posible que un UUID no facilite la depuración de su código, pero un comportamiento menos aleatorio significa menos problemas.

De las teclas puede extraer información no solo sobre pedidos. Si en la base de datos crea claves basadas en otros campos, las personas descartarán los datos y los restaurarán de la clave. Y surgirán dos problemas: cuando el estado del programa se almacena en varios lugares, será muy fácil que las copias no estén de acuerdo entre sí; y sincronizarlos será más difícil si no está seguro de cuál necesita cambiar o cuál ha cambiado.

Lo que sea que permita que hagan sus usuarios, lo harán. Escribir código que sea fácil de depurar significa pensar en formas de mal uso, así como en cómo las personas pueden interactuar con él en general.

Regla 5: La depuración es una tarea social, en primer lugar, técnica.


Cuando un proyecto se divide en componentes y sistemas, puede ser mucho más difícil encontrar errores. Al comprender cómo surge el problema, puede coordinar los cambios en diferentes partes para corregir el comportamiento. Corregir errores en proyectos grandes requiere no tanto encontrarlos como convencer a la gente de la existencia de estos errores o de la posibilidad misma de la existencia.
Hay errores en el software, porque nadie está completamente seguro de quién es responsable de qué. Es decir, es más difícil depurar el código cuando no se escribe nada, hay que preguntar sobre todo en Slack y nadie responde hasta que llega un experto.

Esto se puede solucionar con planificación, herramientas, procesos y documentación.

La planificación es una forma de deshacerse del estrés de mantenerse en contacto, la estructura de gestión de incidentes. Los planes le permiten informar a los compradores, liberar a las personas que han estado en contacto durante demasiado tiempo y también hacer un seguimiento de los problemas y realizar cambios para reducir los riesgos futuros. Herramientas: una forma de reducir los requisitos para realizar algún trabajo, de modo que sea más accesible para otros desarrolladores. Un proceso es una forma de eliminar funciones de gestión de participantes individuales y pasarlas a un equipo.

Las personas y las formas de interacción cambiarán, pero los procesos y las herramientas permanecerán a medida que el equipo cambie. No es que uno sea más importante que el otro, sino que uno está diseñado para soportar cambios en el otro. El proceso también se puede utilizar para eliminar funciones de control del equipo. Esto no siempre es bueno o malo, pero siempre hay algún tipo de proceso, incluso si no se detalla. Y el acto de documentarlo es el primer paso para permitir que otras personas cambien este proceso.

La documentación es más que archivos de texto. Esta es una forma de transferir responsabilidad, cómo lleva a las personas al trabajo, cómo informa los cambios a los afectados por estos cambios. Escribir documentación requiere más empatía que cuando se escribe código, y más habilidades: no hay indicadores de compilación simples o controles de tipo, y puede escribir fácilmente muchas palabras sin documentar nada.

Sin documentación, uno no puede esperar que otros tomen decisiones informadas, o incluso estar de acuerdo con las consecuencias del uso del software. Sin documentación, herramientas o procesos, es imposible compartir la carga del mantenimiento o al menos reemplazar a las personas que ahora están resolviendo el problema.

El deseo de facilitar la depuración es aplicable no solo al código en sí, sino también a los procesos relacionados con el código, esto ayuda a comprender en qué aspecto necesita ingresar para corregir el código.

El código fácil de depurar es fácil de explicar.


Existe la opinión de que si explica un problema a alguien durante la depuración, usted mismo lo comprende. Para esto, ni siquiera necesita a otra persona, lo principal es obligarse a explicar la situación desde cero, a explicar el orden de reproducción. Y a menudo esto es suficiente para tomar la decisión correcta.

Si solo A veces, cuando pedimos ayuda, no preguntamos qué se necesita. Esto es tan común que se llama El problema XY: “ ¿Cómo obtengo las últimas tres letras de un nombre de archivo? ¿Eh? No, quise decir expansión .

Hablamos de un problema en términos de una solución que entendemos, y hablamos de una solución en términos de las consecuencias que tememos. La depuración es una comprensión difícil de las consecuencias inesperadas y soluciones alternativas; requiere lo más difícil para un programador: admitir que entendió algo mal.

Resulta que esto no fue un error del compilador.

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


All Articles