16 KiB
Los errores a lo largo del camino
¡Vaya!
Tiene que pasar: algo que pensabas que era una buena idea no funcionó de la manera que lo habías planeado, y ahora te das cuenta de que has cometido un terrible error. A veces es algo que se podría haber evitado fácilmente (realizar un commit de un código destinado a la depuración, por ejemplo). A veces es una cascada de errores, cada uno de los cuales se basa en los esfuerzos del error anterior. Está el error de ignorar los efectos secundarios de un módulo cuando se usa de una manera que no estaba prevista, o darse cuenta de que has diseñado un módulo pequeño y estrechamente acoplado y después descubrir que tu módulo será parte de una pieza de software más grande y tu código no está diseñado para hacer una transición sin problemas. ¡Vaya!
Sin embargo, los errores que realmente me asustan son los que no esperaba, aquellos en los que las consecuencias no deseadas corren desenfrenadas por todo el sistema como una reacción en cadena. Esos errores me quitan el sueño por la noche.
Los programadores cometen errores. La naturaleza de nuestros trabajos requiere que seamos conscientes de lo que sucede en múltiples secciones de código. Perdemos la noción del estado de nuestro programa y del código que ya hemos realizado. Intentamos salpicar nuestro código con comentarios y recordatorios de lo que sucede en una sección del código, pero los comentarios se vuelven obsoletos y aumentan nuestra distracción. Nos apresuramos y confiamos que el músculo de nuestra memoria tome el relevo. Nos negamos áreas en las que podemos probar el código adecuadamente porque sentimos que debemos apresurarnos para hacer las cosas rápidamente.
Entramos en pánico, y cuando entramos en pánico cometemos errores.
Evitar los errores
Vamos a hablar con claridad: no hay manera de evitar o eliminar los errores. El software es demasiado complejo para estar completamente libre de errores. Sin embargo, lo que podemos hacer es crear lugares en los que podamos detectar tantos errores del código como sea posible antes de mostrarlo a los demás. Podemos comprender mejor nuestro código y lo que está haciendo cuando tenemos la capacidad de depurar y probar nuestro código en un entorno seguro. Podemos ver cómo se comportará bajo ciertas circunstancias. La creación de un modelo similar al del sistema de destino nos permite probar nuestro código con versiones más pequeñas de la realidad del sistema del destino y ver cómo se comporta en esas condiciones.
Ponemos mucho énfasis en evitar errores, tanto en la programación como en la cultura de la programación. Hay historias de terror de cómo pequeños errores en un programa causaron un enorme dolor de cabeza a las personas involucradas. La moraleja de estas historias es que los errores simples pueden ser costosos y debemos ser doblemente cuidadosos para evitar errores. Estas anécdotas se cuentan con la esperanza de asustar a los desarrolladores para que sean más cautelosos, pero pueden tener el efecto contrario. Pueden hacer que los programadores se vuelvan paranoicos acerca de poder cometer cualquier error, y cuando operamos en un modo basado en el miedo, comenzamos a entrar en pánico. Decirle a los programadores que no cometan errores es similar a decirle a alguien que no tenga miedo: tienen más miedo de tener miedo.
La mejor (y quizás la única) forma en que aprendemos es cometiendo errores. Aprender cometiendo errores es una forma efectiva de permitirnos ser curiosos y ver qué causó que el programa fallara. Cuando nos privamos de la libertad de cometer errores, nos privamos de las oportunidades de aprendizaje al cometer esos errores. Eso no significa que tengamos que cometer todos los errores que otros desarrolladores han cometido antes que nosotros (eso sería un montón de errores). Tampoco significa que debamos introducir el caos en nuestro proceso de desarrollo para aprender mejor. Lo que significa es que debemos cometer nuestros propios errores a nuestra manera para seguir aprendiendo y descubrir dónde existen las lagunas en nuestra comprensión.
Hacer un modelo
Necesitamos entornos donde los programadores puedan aprender de una manera segura de sus propios errores. Necesitamos espacios donde los programadores puedan sentirse bien y seguros a la hora de probar cosas nuevas. Necesitamos lugares donde los desarrolladores puedan probar sus ideas y que esos cambios no se extiendan a otros sistemas no relacionados. Esta es la mejor forma en que los desarrolladores pueden aprender y ser valientes en su proceso de aprendizaje.
Estos entornos deben replicar los sistemas de destino y deben estar lo más cerca posible de esos sistemas de destino. Eso no significa que haya que hacer copias exactas de entornos de producción costosos, pero sí se necesita crear modelos de entornos de producción que prueben la mayoría de las piezas con las que su código entrará en contacto. Tener modelos o réplicas que reflejen los sistemas de producción significa que cuando muevas tu código a producción, introducirá menos cambios con consecuencias no deseadas. Tus cambios ya habrán existido en un entorno de producción. Puedes estar tranquilo sabiendo que los cambios que provoquen en estos modelos serán los mismos cambios que aparecerán en el sistema de destino.
En la situación más ideal, necesitarás tener un entorno como este en una máquina que controles. Esto significa que no estás compitiendo con otros programadores de tu organización que también están siendo valientes con sus cambios. También querrás asegurarte de que tu entorno se mantenga actualizado con sus cambios (y cualquier cambio de producción) para que tu modelo de desarrollo coincida con lo que está en el sistema de destino y lo que estará en el sistema de destino. Un buen modelo es aquel que se mantiene actualizado con lo que se está modelando. Es lo mismo que un mapa de una ciudad: es mejor cuando coincide el área su modelo y se mantiene actualizado con los cambios que ocurren en esa ciudad. Un buen mapa de la ciudad podría informarte sobre las obras recientes que se están realizando en tu ruta. Un mapa inútil es aquel que ni siquiera muestra tu ruta porque no no se había construido esa ruta cuando se creó el mapa. Si nuestro modelo de producción se está quedando atrás constantemente con respecto a lo que está en producción, dedicaremos más tiempo a rectificar los cambios que estamos haciendo con los cambios entre nuestro modelo y la producción.
Esto también significa que deberíamos tener un entorno que se pueda reconstruir rápidamente y replicar según sea necesario. Tener un modelo que se convierte en su propia realidad separada se convierte en un sistema más para mantener. Este modelo debe ser algo que se pueda eliminar y reconstruir a voluntad para eliminar cualquier experimento anterior. Es mejor pensar en él como una copia efímera de su entorno de destino que tiene un uso limitado y puede desecharse cuando ya no sea necesario. Este entorno de pruebas debe ser rápido a la hora de volverlo a replicar para que haya poca fricción a la hora de crear nuevos entornos para probar. Eso quizás puede significar crear secuencias de comandos para el proceso de construcción de estos entornos. La forma en que decidas hacer esto depende de ti, pero ten en cuenta que quieres algo que sea lo más simple posible y que requiera la menor cantidad posible de nuestra atención para replicarlo.
De nuevo, no tiene que ser perfecto, sólo es un modelo, pero necesita ser lo bastante similar al original para que tu código se comporte de una manera similar entre el modelo creado y el entorno real.
Máquinas del tiempo
Hay un buen número de personas que te hablarán sobre los beneficios de un sistema de control de revisiones (y muchas de esas personas te mostrarán los pasos exactos para configurar un sistema de control de revisiones). Los sistemas de control de revisiones como git
, svn
, o cvs
y similares han ayudado a los programadores a coordinar lanzamientos de publicaciones y mantener un registro de qué trabajos se han añadido a sus proyectos. Tener un buen sistema de control de revisiones te permite crear áreas donde puedas probar código nuevo sin tener que añadir estas pruebas a código ya en producción. Un buen control de revisiones te permite crear un espacio (o también llamados ramas o branch
por su nombre en inglés cuando estamos hablando de git
) basado en un código ya existente que puedes utilizar para experimentar y desarrollar. También te permite realizar commits en ese espacio y divagar tanto como necesites para poder explorar a fondo los cambios que estás realizando. Sin embargo, lo que es más importante es que un buen sistema de control de revisiones también te permitirá abandonar ese espacio si lo necesitas, no estas forzado a añadir esos cambios de nuevo a tu código en producción. Esto te permite ver si algo podría funcionar o abandonar esos cambios si no lo hace. Un buen control de revisiones brinda a los programadores la capacidad de ramificarse desde cualquier punto en el tiempo y explorar lo que sucedió en el código base. En cierto sentido, son máquinas del tiempo y universos infinitos, lo que te permite jugar con distintos escenarios "¿y si?" con su código y avanzar y retroceder en el tiempo en tu código. Esto es vital para tu aprendizaje porque puedes sentirte seguro probando y probando cosas y rebobinando esos cambios (o eliminándolos por completo) sin afectar el trabajo de otras personas.
Aprender cómo funciona tu sistema de control de revisiones te dará libertad para cometer errores. Muchos de estos sistemas pueden parecer complejos al principio, pero con la práctica continua y la paciencia comprenderás lo que hace el sistema de control de revisiones y cuáles son sus capacidades. Podrás juzgar cuántos riesgos puedes tomar con tu código y tener más confianza con los riesgos que tomas.
El control de revisiones también puede desempeñar un papel importante al poder ver el desarrollo del código de otras personas. Puedes obtener una ventana a su proceso de desarrollo y ver cómo se ven ciertas características a medida que se añaden. Esto puede ayudarte a aprender sobre una base de código desconocida y mostrarte la dirección que tomaron para hacer el código de la manera que es. Puede brindarte una ventana a la historia de un proyecto y lo que se hizo para que sucediera. El control de revisiones puede ser una máquina del tiempo en la historia de un proyecto y puede ayudarte a comprender que la programación es un proceso. No todos los proyectos vienen completamente ya formados de la mente de los programadores.
Learning from failure
Sometimes we fail. Sometimes the code we write isn't up to the realities of the system it's implemented on. We push code that does something unexpected and systems break as a result. We can lose track of where we are in our code and make changes that conflict with other changes which then causes us to spend time undoing those changes. All of these cases cause discomfort, whether to us, the folks we support, or the folks we work with.
I'm not going to lie: failure sucks. It makes us feel like we're less of a person because we failed. We feel inadequate and wonder how others think of us. Do they think less of us? Have we damaged our relationship with those who use whatever we've programmed? Have we let our team down? All of these questions stem from two desires: the desire to do our best and the desire to do no harm to others. We want others to think well of us and our skills. Failure runs counter to those desires and amplifies whatever feelings of inadequacy we might have. Those feelings can include wondering if we should be programming at all or wondering if our talents should be used elsewhere. We wonder if we should just give up.
We don't usually think of failure as part of the learning process. Failure is often seen as the end-point of the journey. In school a failing grade is viewed as a condemnation. We don't view it as "I need to practice this some more"; instead we feel that we have caused shame and discomfort to ourselves and our loved ones. We do ourselves a grave disservice if we don't realize that failure is a natural part of the learning process and that it's OK to fail. Not everything we do will be perfect. Mistakes will creep into the best code we write. We will slip up and deploy to the wrong system. Our mistakes will cause discomfort to others. Accepting this gives us the freedom to realize that despite our best efforts we will not be perfect. Instead of viewing failure as a limitation we can use it as part of our growth process.
When we realize we are going to make mistakes we can change our approach in how and where we make them. I mentioned before about creating models of our environments. What better way to allow us to make mistakes than in an environment where those mistakes can be contained and rolled back? Creating models allows us to practice and test our assumptions in environments that nobody else has to see. It's akin to a practice space for musicians where they can run through their material without the need to perform it right the first time. They can work out the troublesome parts and make mistakes until they are confident in their performance.
Mistakes are how we learn what works and what doesn't work. They are an integral part of our learning process. We tend to remember the lessons of what didn't work better than the ones that did work. Mistakes help us shore up where we lack knowledge and help us understand the gaps we've yet to close.
Mistakes also act as a reminder to pause for a moment and not get too wound up in the urgency of things. My own mistakes tend to crop up when I'm rushing to meet a deadline (whether real or self-imposed). My worst mistakes happen when I'm tired and rushed, when I'm practically flailing at the keyboard trying to get something (anything!) working. When I allow myself to pause for a moment, reflect on what I'm trying to do, and feel the uncertainty in the moment I can take steps to recalibrate and refocus in the moment. I give myself the freedom to course-correct and understand that I'm not doing my best and need to do something different. It might be something small like giving my brain a bit of rest or something large like revisiting the assumptions I made about what I'm doing. Taking the pause lets me determine if I want to continue doing what I'm doing and understand if that's the best path.
Journaling our mistakes
There's value in not making the same mistakes twice, but when we do repeat the same mistake can still be useful. Knowing that we've repeated the same failure is useful because it gives us a pattern we can understand. Those patterns show us that doing this particular thing leads to a repeatable failing result. We can then determine what caused the mistake and plan for how to mitigate it. This is part of the learning process, as long as we don't fall into a spiral of self-recrimination when we realize that we've made the same mistake again.
One trick that I should use more often is journaling. Keeping a journal of what happened and how we fixed it is one way to explain to someone else (often ourselves) about what happened. Explaining what happened allows us to become a teacher to ourselves and others. It reinforces our learning process. Writing down what happened in a way that others can understand allows us to arrange the thoughts in our head in a way that is clear and understandable. When we articulate our own thoughts about what happened and codify them, we start to understand our own thoughts and can shake loose other ideas about how to fix this and other problems. We give ourselves the pause we need to fully understand what happened and how best to move forward. We become our own sounding-board for ideas on how best to proceed.
This isn't about keeping a record for posterity so we can look back at a list of failures and beat ourselves up about the past (if you're anything like me that happens automatically). It's a way to teach ourselves and maximize the learning process. It's about giving ourselves the freedom to be the instructor to our future selves so we can be more aware when a mistake is about to happen and understand how to correct for it. This allows us to focus on the moment just long enough to understand what happened, what we did to correct it, and how we can best proceed from here. It also helps us to locate where our gaps are and the "next actions" that we'll need to take in order to fill in those gaps.
We'll talk more about journaling in later chapters but I fully recommend a journal habit if for no other reason than it gives you a willing apprentice to teach, even if that apprentice is only yourself.