Capítulo 4. Separación de intereses
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
"Separación de preocupaciones"... es lo que quiero decir con "centrar la atención en algún aspecto": no significa ignorar los demás aspectos, sólo es hacer justicia al hecho de que, desde el punto de vista de este aspecto, el otro es irrelevante. Es tener una mentalidad única y múltiple simultáneamente.
Edsger Dijkstra, "Sobre el papel del pensamiento científico"
Nuestro código fuente ha crecido. Dependiendo del lenguaje, hay entre 50 y 75 líneas en un archivo fuente. Eso es más que una pantalla llena en muchos monitores de visualización, y desde luego más que una página impresa en este libro.
Antes de pasar a la siguiente función, dedicaremos algún tiempo a refactorizar nuestro código. Ese es el tema de este capítulo y de los tres siguientes.
Código de prueba y producción
Hasta ahora, hemos escrito dos tipos de código diferentes en .
-
Código que resuelve nuestro problema de Dinero. Esto incluye
Money
yPortfolio
y todo su comportamiento. A esto lo llamamos código de producción. -
Código que verifica que el problema se ha resuelto correctamente. Esto incluye todas las pruebas y el código necesario para soportar dichas pruebas. A esto lo llamamos código de prueba.
Hay similitudes entre los dos tipos de código: están en el mismo lenguaje, los escribimos en rápida sucesión (a través del ya familiar ciclo rojo-verde-refactor), y confirmamos ambos en nuestro repositorio de código. Sin embargo, hay algunas diferencias clave entre los dos tipos de código.
Dependencia unidireccional
El código de prueba tiene que depender del código de producción, al menos en las partes del código de producción que prueba. Sin embargo, no debe haber dependencias en la otra dirección.
Actualmente, todo nuestro código para cada idioma está en un solo archivo, como se muestra en la Figura 4-1. Así que no es fácil garantizar que no haya dependencias accidentales del código de producción al código de prueba. Existe una dependencia implícita del código de prueba al código de producción. Esto tiene un par de implicaciones:
-
Al escribir código, tenemos que tener cuidado de no utilizar accidentalmente ningún código de prueba en nuestro código de producción.
-
Al leer código, tenemos que reconocer los patrones de uso y también fijarnos en los patrones ausentes, es decir, en el hecho de que el código de producción no puede llamar a ningún código de prueba.
Importante
El código de prueba depende del código de producción; sin embargo, no debe haber dependencia en el otro sentido.
Si el código de producción depende del código de prueba, ¿cuáles son los posibles malos resultados? En casos especialmente malos, puede llevarnos a un camino en el que la ruta del código que se prueba es "prístina", mientras que las rutas que no se prueban están plagadas de errores. La Figura 4-2 muestra una parte del pseudocódigo de la unidad de control del motor de un coche. El código funciona de forma diferente si el motor se está probando para cumplir la normativa de emisiones que cuando el motor se utiliza "en el mundo real".
Si eres escéptico de que un caso tan flagrante de "compórtate lo mejor que puedas para los exámenes" pueda ocurrir en la realidad, te animamos a que leas sobre el escándalo de las emisiones de Volkswagen, del que se ha extraído el pseudocódigo de la Figura 4-2.1
Tener una dependencia unidireccional -en la que el código de producción no dependa en modo alguno del código de prueba y, por tanto, no sea susceptible de comportarse de forma diferente cuando se somete a prueba- es vital para garantizar que no se cuelen defectos de esta naturaleza (ya sean accidentales o malintencionados).
Inyección de dependencia
La inyección de dependencia es una práctica para separar la creación de un objeto de su uso. Aumenta la cohesión del código y reduce su acoplamiento.2 La inyección de dependencias requiere que distintas unidades de código (clases y métodos) sean independientes entre sí. Separar el código de prueba del de producción es un requisito previo importante para facilitar la inyección de dependencias.
Tendremos más que decir sobre la inyección de dependencias en el Capítulo 11, donde la utilizaremos para mejorar el diseño de nuestro código.
Empaquetado e Implementación
Cuando el código de la aplicación se empaqueta para su implementación, el código de prueba casi siempre se empaqueta por separado del código de producción. Esto permite desplegar el código de producción y el de prueba de forma independiente. A menudo, sólo se despliega el código de producción en determinados entornos "superiores", como el de producción. Esto se muestra en la Figura 4-3.
Describiremos la implementación con más detalle en el capítulo 13, cuando construyamos una canalización de integración continua para nuestro código.
Modularización
Lo primero que haremos es separar el código de prueba del código de producción. Para ello tendremos que resolver el problema de incluir, importar o requerir el código de producción en el código de prueba. Es fundamental que ésta sea siempre una dependencia unidireccional, como se muestra en la Figura 4-4.
En la práctica, esto significa que el código debe modularse siguiendo estas líneas:
-
El código de prueba y el de producción deben estar en archivos fuente separados. Esto nos permite leer, editar y centrarnos en el código de prueba o de producción de forma independiente.
-
El código debe utilizar espacios de nombres para identificar claramente qué entidades van juntas. Un espacio de nombres puede denominarse "módulo" o "paquete", según el lenguaje.
-
En la medida de lo posible, debe haber una directiva de código explícita -
import
,require
, o similar, según el lenguaje - para indicar que un módulo depende de otro. Esto garantiza que podamos especificar explícitamente la dependencia que se muestra en la Figura 4-1.
También buscaremos oportunidades para hacer que el código sea más autodescriptivo. Esto incluiría renombrar y reordenar entidades, métodos y variables para reflejar mejor su intención.
Eliminar redundancia
Lo segundo que haremos será eliminar la redundancia de nuestras pruebas.
Hace tiempo que tenemos dos pruebas de multiplicación: una para euros y otra para dólares. Prueban la misma funcionalidad. En cambio, sólo tenemos una prueba para la división. ¿Deberíamos mantener las dos pruebas de multiplicación?
Rara vez hay una respuesta irrefutable de "sí" o "no" a esto. Podríamos argumentar que las dos pruebas nos protegen de la codificación inadvertida de la moneda en el código que realiza la multiplicación, aunque ese argumento se vería debilitado por el hecho de que tenemos una prueba para la división y ahí podría surgir un error similar de codificación de moneda.
Para que nuestra toma de decisiones sea más objetiva, he aquí una lista de control:
-
¿Tendríamos la misma cobertura de código si suprimiéramos una prueba? La cobertura de líneas es una medida del número de líneas de código que se ejecutan al ejecutar una prueba. En nuestro caso, no habría pérdida de cobertura si suprimiéramos alguna de las pruebas de multiplicación.
-
¿Una de las pruebas verifica un caso de perímetro significativo? Si, por ejemplo, estuviéramos multiplicando un número realmente grande en una de nuestras pruebas y nuestro objetivo fuera asegurarnos de que no se produce desbordamiento/desbordamiento en diferentes CPU y sistemas operativos, podríamos justificar el mantenimiento de ambas pruebas. Sin embargo, tampoco es el caso de nuestras dos pruebas de multiplicación.
-
¿Aportan las distintas pruebas un valor único como documentación viva? Por ejemplo, si utilizáramos símbolos monetarios de más allá del conjunto de caracteres alfanuméricos ($, €, ₩), podríamos decir que mostrar estos símbolos monetarios dispares aporta un valor adicional como documentación. Sin embargo, actualmente utilizamos letras extraídas de las mismas 26 letras del alfabeto inglés (USD, EUR, KRW) para nuestras monedas, por lo que la variación entre monedas aporta un valor mínimo como documentación.
Consejo
La coberturade líneas (o sentencias), la cobertura de ramas y la cobertura de bucles son tres métricas diferentes que miden qué parte de un determinado cuerpo de código se ha probado.
Dónde estamos
En este capítulo, hemos revisado la importancia de la separación de intereses y la eliminación de la redundancia. Éstos son los dos objetivos que centrarán nuestra atención en los tres capítulos siguientes.
Actualicemos nuestra lista de funciones para reflejarlo:
5 USD × 2 = 10 USD |
10 EUR × 2 = 20 EUR |
4002 KRW / 4 = 1000,5 KRW |
5 USD + 10 USD = 15 USD |
Separar el código de prueba del código de producción |
Eliminar pruebas redundantes |
5 USD + 10 EUR = 17 USD |
1 USD + 1100 KRW = 2200 KRW |
Nuestros objetivos están claros. Los pasos para alcanzarlos -especialmente el primer objetivo de separación de preocupaciones- variarán significativamente de un lenguaje a otro. Por ello, la puesta en práctica se ha separado en los tres capítulos siguientes:
Lee estos capítulos en el orden que tenga más sentido para ti. Consulta "Cómo leer este libro" para orientarte.
1 Sobre el escándalo "dieselgate" de Volkswagen, Felix Domke ha trabajado mucho. Es coautor de un libro blanco. También pronunció un discurso en la conferencia del Chaos Computer Club.
2 La cohesión y el acoplamiento se describen con más detalle en el capítulo 14.
Get Aprender el desarrollo basado en pruebas now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.