Hemos pensado mucho y nos hemos esforzado en desarrollar las prácticas de ingeniería que utilizamos cada día. Estas prácticas se basan en principios que hemos llegado a valorar y esos principios conducen a las prácticas. Hemos documentado esos principios y prácticas en nuestro documento de Ingeniería. En el lado izquierdo de cada página hay un principio que valoramos, y en el lado derecho hay una lista de elementos que Hacemos , Fomentamos , y Evitamos .Este post se va a centrar en los dos principios: Continuamente verificamos la corrección de nuestro código , y Mantenemos una base de código limpia, segura y de alta calidad .
Continuamente verificamos la corrección de nuestro código
Lo que hacemos
Para apoyar el principio «Verificamos continuamente la corrección de nuestro código», hay una práctica que pedimos a todos los ingenieros que adopten:
Mantenemos un conjunto de buenas pruebas de unidad para todo el código de producción. Todo el código de producción debe ser probado. Esto no significa que nos volvamos locos por cosas como las métricas de cobertura del código, pero es nuestra filosofía. Queremos asegurarnos de que todo sea probado por unidad mediante una prueba rápida, fiable y automatizada, de modo que no tengamos que depender de probar todo manualmente, un proceso que está lleno de errores humanos. En lugar de probar las cosas manualmente, cada vez que confirmamos y presionamos nuestro código, tenemos una construcción automatizada que arranca, compila el código si es necesario, y ejecuta todas nuestras pruebas. Por supuesto, lo ideal es que estemos ejecutando estas pruebas localmente antes de que incluso presionemos para obtener un bucle de retroalimentación aún más rápido y para evitar romper nuestra construcción que puede impedir el despliegue en caso de que tengamos que responder rápidamente y desplegar algo.
No entraré en muchos detalles sobre cómo hacer las pruebas, ya que nuestro blog ya tiene varias entradas que lo cubren. Aquí hay un par de ellas que puedes comprobar, pero hay más, así que siéntete libre de navegar por nuestro blog:
Diferentes tipos de pruebas de unidad
¿Qué tipo de prueba?
Lo que alentamos
También hay otra práctica que fomentamos para ayudarnos a verificar continuamente la corrección de nuestro código:
Alentamos (aceptación) el desarrollo impulsado por pruebas. Llegaremos a la parte de (aceptación) en un minuto. Primero, se preguntarán, «¿Cuál es la diferencia entre pruebas unitarias y desarrollo dirigido por pruebas?» Las pruebas unitarias son simplemente escribir pequeñas pruebas para alguna unidad de código. El desarrollo dirigido por pruebas (TDD), por otro lado, requiere que escribas tus pruebas primero, luego tu implementación, y después el refactorio si es necesario. Si no lo has hecho antes, puede ser un poco alucinante.
Podrías preguntarte: «¿Cómo puedo escribir una prueba si ni siquiera sé cómo es la implementación?» Es una gran pregunta y es un concepto que está en el corazón de la TDD. La respuesta es que no tienes que conocer la implementación antes de empezar a escribir pruebas. De hecho, cuando hacemos TDD, evitamos intencionadamente tratar de crear toda la implementación de una vez. En cambio, empezamos con la más pequeña pieza de funcionalidad y escribimos una prueba para eso. Luego la implementamos. Luego escribimos la siguiente prueba.
Por ejemplo, digamos que quieres escribir una aplicación de cambio de monedas y estás trabajando en la función que toma una cantidad y devuelve las monedas apropiadas como una matriz. Tu primera prueba no sería, «Prueba de hacer el cambio por $25.99», en su lugar empezaríamos con el trozo de código más simple posible. Quizás, «Prueba de que la función devuelve una matriz». Y una vez escrita la prueba y fallando, escribirías la implementación y todo lo que haría es devolver un array. Entonces podrías empezar con «Prueba de hacer el cambio por $0.00». Y luego sigues escribiendo pruebas e implementando pequeñas piezas. Una gran ventaja es que lleva a un diseño más simple. Es menos probable que se sobrearquitectura una solución cuando se trabaja de manera iterativa como esta. También es fácil asegurarse de que cada pedazo de lógica es probado porque seguimos la regla, «no implemente nada para lo que no haya escrito una prueba».
Lo que acabo de describir son pruebas de unidad; están probando una pequeña y aislada unidad de código. A veces, sin embargo, quieres probar una sección más grande de tu código para asegurarte de que todas las piezas funcionan bien juntas. Aquí es donde entran las pruebas de aceptación. Las pruebas unitarias tienden a aislar otras partes del código con cosas como las burlas. Con las pruebas de aceptación, normalmente se prueba todo el código desde justo debajo de la capa de interfaz de usuario hasta las diferentes capas.
Cuando se combinan las pruebas de aceptación con TDD se obtiene lo que llamamos pruebas de doble circuito. Así que en el ejemplo del cambiador de monedas, digamos que estás creando una API que será llamada desde alguna interfaz de usuario. Es probable que tengas una capa de API, una capa de negocios y tal vez una capa de datos si estás haciendo un seguimiento del dinero que entra y el cambio que sale. Con el desarrollo impulsado por la prueba de aceptación, empezarías escribiendo una prueba de aceptación que llame a la API y espere un determinado resultado. Esa prueba fallaría porque el punto final de la API no existe todavía. Luego comienzas a escribir pruebas unitarias para cada una de las diferentes piezas que deben implementarse: La capa de la API, la lógica del cambiador de monedas, etc. Una vez que hayas escrito todas esas pruebas y las apruebes, deberías haber terminado y en ese momento esperarías que tu prueba de aceptación se apruebe.
De esta manera, estamos usando TDD para asegurar no sólo que todas las piezas individuales funcionen, sino también que todas funcionen juntas. Y luego, por supuesto, todas estas pruebas se ejecutan automáticamente cuando empujamos a nuestro repositorio de código.
Hay mucho más en TDD, pero esto no es una entrada de blog en TDD, así que me detendré aquí. Aquí hay una rápida introducción de nuestro blog sobre cómo empezar con TDD:
Fundamentos del desarrollo impulsado por pruebas
Lo que evitamos
También hay una práctica que evitamos en lo que respecta a la calidad del código:
No dependemos de un equipo de control de calidad separado para las pruebas. A primera vista, esto podría parecer contrario a nuestro objetivo de calidad de código. ¿Por qué no querríamos que un equipo de control de calidad nos ayudara a asegurar nuestra calidad? Es sobre todo una cuestión de mentalidad, pero también ayuda con nuestro flujo de valor a nuestros clientes. Realmente se deriva de nuestras raíces de Artesanía de Software.Queremos que nuestros ingenieros se sientan responsables y apasionados por la calidad y parte de ello es saber que depende de ellos asegurarla.No queremos dar una puñalada y luego tirarla por encima de una pared para que alguien más pruebe la calidad, sino que queremos asegurarnos de que la estamos construyendo a medida que avanzamos.las pruebas de unidad, TDD, y saber que depende completamente de usted entregar la calidad crea una poderosa mentalidad en torno a la calidad.para citar a un local de hamburguesas, queremos «calidad y mucha».
Esto también es importante para nuestra metodología de desarrollo «lean». No trabajamos en sprints. Cuando un par o un grupo de desarrolladores terminan un pequeño trabajo, lo comprometen y lo empujan. Se realizan todas las pruebas y luego lo comprueban en nuestro entorno de puesta en escena. Cuando están seguros de que todo está bien, ¡lo envían! No hay que esperar a que termine el sprint o a una ventana de mantenimiento planificado, simplemente lo enviamos en ese momento… en medio del día… sin tiempo de inactividad. Si tuviéramos un equipo de control de calidad, no sólo se reduciría la responsabilidad de los ingenieros de construir la calidad desde el principio, sino que también tendría un tremendo impacto en esa capacidad de desplegar rápidamente el valor a nuestros clientes.
Mantenemos un código base limpio, seguro y de alta calidad
Lo que hacemos
Aquí hay un par de prácticas que usamos para ayudarnos a mantener una base de código limpia, segura y de alta calidad:
Nos tomamos el tiempo de escribir el código de calidad para mantener la velocidad de la entrega. Escribir un código bueno y limpio es difícil. Escribir un código que lea cómo debe funcionar es difícil. Nombrar es difícil. Refactorizar también puede ser difícil. A veces pasamos una cantidad de tiempo inusualmente significativa hablando de alguna sección del código porque es demasiado complejo y estamos tratando de averiguar cómo simplificarlo o porque es difícil hacer que el código transmita a un lector lo que realmente hace. Evitar un código sobre-arquitecturado requiere esfuerzo. Y todo vale la pena. Como Bob Martin dijo en su libro Código Limpio: «La proporción de tiempo de lectura (código) frente a la escritura es más de 10 a 1 … (por lo tanto) hacerla fácil de leer hace que sea más fácil de escribir. «Y por lo tanto nos tomamos el tiempo para hacerlo bien la primera vez. Ok, eso no es cierto, lo hacemos mal muchas veces, pero es ciertamente nuestro objetivo pasar el tiempo para escribir código de calidad que nos ayuda a mantener la velocidad de entrega en el futuro.
Dedicamos al menos el 20% de nuestro tiempo a la reducción técnica de la deuda. Como dije, tratamos de hacerlo bien la primera vez, pero nos equivocamos con mucha frecuencia. La arquitectura evoluciona. Las necesidades del cliente evolucionan. Nuestro pensamiento evoluciona y ganamos más claridad sobre lo que estamos construyendo a medida que lo construimos. Todas estas cosas significan que nuestro código necesita evolucionar junto con él y eso requiere una constante e intencionada refactorización. La deuda técnica, o la necesidad de volver y limpiar o cambiar el código después de que está escrito, sucede por una amplia variedad de razones – pero siempre sucede, constantemente. Hemos descubierto que si no dedicamos de manera constante e intencionada alrededor del 20% de nuestro tiempo a limpiar la deuda técnica, nuestras bases de código comienzan a decaer y eso afecta a nuestra velocidad sostenida de entrega. Es difícil hacer que ese 20% suceda, pero es algo a lo que nos comprometemos y de lo que hablamos con frecuencia porque sabemos que, al final, acelera nuestra capacidad de entrega. ¿Qué pasa si rompes algo?» Bueno, ahí es donde nuestra amplia gama de pruebas entran en juego. Estamos más seguros de nuestra capacidad de refactorizar y limpiar el código porque sabemos que tenemos un conjunto de pruebas que probablemente nos dirán si rompemos algo! Sin mencionar que una base de código desordenada es mucho más arriesgada y condusiva para los bichos que una base de código limpia.
Lo que alentamos
Hay un principio más que también fomentamos para ayudarnos a mantener un código limpio, seguro y de alta calidad:
Fomentamos la refactorización regular. Esto está relacionado con nuestros esfuerzos de reducción de la deuda técnica y se fomenta por las mismas razones. La principal diferencia entre esto y la deuda técnica es que es constante y continua en el momento en lugar de después del hecho. Animamos a los ingenieros a estar constantemente refactorizando a medida que avanzan.
Con nuestros esfuerzos de desarrollo dirigido por pruebas, hay tres fases: Rojo – Escribir una prueba que falla (y se vuelve roja); Verde – Hacerla pasar por inminente; y Refactor – Una vez que tu prueba es verde, echa un vistazo a tu código y a tu prueba y mira si puedes limpiarlos. Es un esfuerzo constante para estar siempre reflexionando sobre tu código y considerando si tendría sentido para los que vienen detrás de ti.
Conclusión
Todo esto se combina para ayudarnos a crear un código y sistemas limpios y fáciles de mantener. El código limpio nos ayuda a entender mejor el código para que tengamos más confianza y crear menos errores para que nuestra prueba encuentre o no encuentre si nuestras pruebas no son perfectas (no lo son) y todo esto nos ayuda a enviar valor a nuestros clientes más rápidamente.