Resolviendo la kata Prime Factors
Enunciado de la kata
Nuestro objetivo será escribir un programa que descomponga un número natural en sus factores primos. Por simplicidad, no agruparemos los factores en forma de potencias. Eso lo dejaremos para un ejercicio posterior si te interesa avanzar un poco más.
Lenguaje y enfoque
Esta kata la vamos a hacer en Javascript, con el framework de testing Jest. Crearemos una función primeFactors, a la que le pasamos el número que queremos descomponer y nos devolverá un array con los factores primos, ordenados de menor a mayor.
1 var primesOf18 = primeFactors(18);
2 // -> [2, 3, 3]
Definir la función
Nuestro primer test espera que exista la función primefactors:
1 describe('Calculate prime factors', function () {
2 it ('should exist', () => {
3 expect(primefactors())
4 });
5 });
Que ya sabemos que no ha sido definida todavía:
1 ReferenceError: primefactors is not defined
La introducimos sin más. De momento en el propio archivo del test:
1 function primefactors() {
2
3 }
De momento, no nos hemos comunicado con la función en el test, así que vamos a introducir esa idea, pasando un primer ejemplo de número para descomponer y el resultado que esperamos. Lo primero que nos debería llamar la atención es que debido a las peculiaridades de la definición y distribución de los números primos entre los números naturales, se nos presenta un orden muy intuitivo para organizar los ejemplos y escribir los tests. Casi nos basta con empezar con el número uno y avanzar progresivamente.
El uno, además, es un caso particular (no tiene factores primos), así que nos viene especialmente bien como primer test.
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it ('should exist', () => {
5 expect(primefactors())
6 });
7
8 it('1 should not have factors', () => {
9 expect(primefactors(1)).toEqual([]);
10 });
11 });
Para hacer pasar el test necesitamos una implementación mínima de la función:
1 function primefactors() {
2 return [];
3 }
4
5 export default primefactors;
Fíjate que ni siquiera implementamos que la función recibe un parámetro. Vamos a hacer que sea el test quien nos lo pida. Mientras tanto, eliminamos el primer test, dado que ahora es redundante.
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7 });
Definir la signatura de la función
El segundo test nos debería ayudar a definir la signatura de la función. Para ello necesitamos que sea un caso en el que esperemos una respuesta distinta de [], lo cual podremos hacer si recibimos un parámetro que introduzca la variación necesaria. El 2 es un buen ejemplo para conseguir esto:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('2 is a prime number', () => {
9 expect(primefactors(2)).toEqual([2])
10 })
11 });
Para resolver este caso necesitamos tener en cuenta el parámetro que define la función, lo que nos obliga a introducirlo y utilizarlo. En nuestra solución atendemos al caso que plantea el test anterior y hacemos una implementación obvia para poder pasar el test que acabamos de introducir. Estamos posponiendo la implementación del algoritmo hasta tener más información:
1 function primefactors(numberToDecompose) {
2 if (numberToDecompose === 1) {
3 return [];
4 }
5
6 return [2];
7 }
8
9 export default primefactors;
Obteniendo más información sobre el problema
El siguiente caso que vamos a probar es a descomponer el número 3, que es primo como el número 2. Este test nos servirá para entender mejor cómo gestionar estos casos:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('2 is a prime number', () => {
9 expect(primefactors(2)).toEqual([2])
10 })
11
12 it ('3 is also a prime number', () => {
13 expect(primefactors(3)).toEqual([3])
14 })
15 });
Ahora que tenemos este test fallando, haremos una implementación obvia, como es devolver el propio número que pasamos. Puesto que es un número primo, es perfectamente correcto. No hay mucho más que rascar aquí.
1 function primefactors(numberToDecompose) {
2 if (numberToDecompose === 1) {
3 return [];
4 }
5
6 return [numberToDecompose];
7
8 }
9
10 export default primefactors;
Introduciendo un test que no falla
En la presentación de la kata hemos dividido los casos en categorías. Repasemos:
- Casos límite o especiales, como el 1
- Números primos, como 2, 3 o 5
- Números no primos, como 4, 6, 8
La primera categoría ya la hemos cubierto, puesto que no hay más casos límite que considerar.
La tercera categoría todavía no hemos empezado a tratarla y no hemos hecho tests con ningún ejemplo de ella.
La segunda categoría es la que hemos estado testeando hasta ahora. En este punto, podríamos seguir tomando ejemplos de esta categoría y probar nuevos casos. Pero, ¿qué pasaría? Veámoslo:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('2 is a prime number', () => {
9 expect(primefactors(2)).toEqual([2])
10 })
11
12 it ('3 is also a prime number', () => {
13 expect(primefactors(3)).toEqual([3])
14 })
15
16 it ('5 is also a prime number', () => {
17 expect(primefactors(5)).toEqual([5])
18 })
19
20 });
¡El test pasa sin implementar nada nuevo!
Era bastante obvio, ¿no? En este momento, el algoritmo, por llamarlo de algún modo, no hace nada más que considerar todos los números como primos. Por esta razón, si seguimos usando como ejemplos números primos, nada nos obligará a hacer cambios en la implementación.
Cuando añadimos un test que no falla significa que el algoritmo que estamos desarrollando ya es lo bastante general para resolver esa categoría de casos y, por tanto, es hora de pasar a otra categoría que todavía no pueda ser manejada con éxito. O, si ya hemos cubierto todas las categorías posibles, es que hemos terminado.
Empezaremos a utilizar ejemplos de la categoría de los números no primos. Pero igualmente vamos a refactorizar el test para ver estas categorías de forma más explícita:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13 });
Cuestionando nuestro algoritmo
El primer número no primo que tenemos es el 4, y es el más sencillo de todos por muchos motivos, así que hacemos un test que esta vez fallará:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17 });
Hay varias formas de plantear esta implementación. Por ejemplo, tenemos esta que es especialmente ingenua, pero eficaz:
1 function primefactors(numberToDecompose) {
2 if (numberToDecompose === 1) {
3 return [];
4 }
5
6 if (numberToDecompose === 4) {
7 return [2, 2];
8 }
9
10 return [numberToDecompose];
11 }
12
13 export default primefactors;
A pesar de lo simplona, es interesante. Nos ayuda a entender que tenemos que distinguir entre números primos y no primos para poder desarrollar el algoritmo.
Sin embargo, tiene una pinta muy deslavazada. Vamos a intentar organizarla un poco mejor:
1 function primefactors(numberToDecompose) {
2 if (numberToDecompose > 1) {
3 if (numberToDecompose === 4) {
4 return [2, 2];
5 }
6
7 return [numberToDecompose];
8 }
9
10 return [];
11 }
12
13 export default primefactors;
Básicamente, dice: si un número es mayor que uno, intentamos descomponerlo. Si es cuatro, devolvemos su factorización y, si no, devolvemos el mismo número porque será primo. Lo que es cierto para los ejemplos que tenemos ahora mismo.
Descubriendo los múltiplos de 2
El siguiente número que podemos descomponer es el 6. Una cosa buena de esta kata es que cada nuevo número no primo nos da una respuesta diferente y eso quiere decir que cada test nos aportará información. Helo aquí
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17
18 it ('6 is 2 * 3', () => {
19 expect(primefactors(6)).toEqual([2, 3])
20 })
21 });
Vamos a empezar por la implementación ingenua:
1 function primefactors(numberToDecompose) {
2 if (numberToDecompose > 1) {
3 if (numberToDecompose === 4) {
4 return [2, 2];
5 }
6
7 if (numberToDecompose === 6) {
8 return [2, 3];
9 }
10
11 return [numberToDecompose];
12 }
13
14 return [];
15 }
16
17 export default primefactors;
No hay nada de malo en hacerlo así. Al contrario, esta forma de resolver el problema nos empieza a poner de relieve regularidades. 4 y 6 son múltiplos de 2, por lo que queremos introducir ese conocimiento en forma de refactor. Y eso lo podemos hacer gracias a que tenemos tests que nos demuestran que la función ya los descompone correctamente. Así que vamos a modificar el código sin cambiar ese comportamiento que ya hemos definido con los tests.
Nuestro primer intento se basa en que el primer factor primo es 2 y es común. Es decir, podemos hacer un algoritmo que procese múltiplos de 2 y, de momento, asumimos que el número que queda como resto de la primera división por 2 es el segundo factor del compuesto, sea cual sea.
Para ello tenemos que introducir una variable de tipo array con la que entregar la respuesta, a la que le vamos añadiendo los factores que descubramos:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose > 1) {
6 if (numberToDecompose === 4) {
7 factors.push(2);
8 factors.push(2);
9
10 return factors;
11 }
12
13 if (numberToDecompose === 6) {
14 factors.push(2);
15 factors.push(3);
16
17 return factors;
18 }
19
20 factors.push(numberToDecompose);
21 }
22
23 return factors;
24 }
25
26 export default primefactors;
Este ha sido un primer paso, ahora ya nos queda más claro cómo funcionaría y lo podemos generalizar, expresándolo así:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose > 1) {
6 if (numberToDecompose % 2 === 0) {
7 factors.push(2);
8 factors.push(Math.floor(numberToDecompose/2));
9
10 return factors;
11 }
12
13 factors.push(numberToDecompose);
14 }
15
16 return factors;
17 }
18
19 export default primefactors;
Este refactor casi funciona, pero ha dejado de pasar el test del número 2. Arreglamos eso y avanzamos un paso más:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose > 1) {
6 if (numberToDecompose % 2 === 0 && numberToDecompose > 2) {
7 factors.push(2);
8 numberToDecompose = Math.floor(numberToDecompose/2);
9 }
10
11 factors.push(numberToDecompose);
12 }
13
14 return factors;
15 }
16
17 export default primefactors;
Esta nueva implementación hace pasar todos los tests y estamos listos para forzar un nuevo cambio.
Introduciendo más factores
Dentro de los números no primos podríamos considerar varias agrupaciones a la hora de seleccionar ejemplos. Tenemos casos en que los números se descomponen en el producto de dos factores primos, y casos en los que se descomponen en el producto de 3 o más factores. Esto viene a cuenta porque nuestros próximos ejemplos son 8 y 9. El 8 es 2 * 2 * 2, mientras que 9 es 3 * 3. El caso del 8 nos obliga a considerar los casos en que podemos descomponer un número en más de dos factores, y el del 9, aquellos casos en los que se introducen nuevos divisores.
En principio puede darnos igual empezar por cualquiera de los dos. Quizá la clave sea escoger el caso que te parezca más fácil de abordar. Aquí vamos a empezar por descomponer el número 8. De este modo, mantenemos el divisor 2 que, en este momento, nos parece algo más fácil de abordar.
Hagamos un test:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17
18 it ('6 is 2 * 3', () => {
19 expect(primefactors(6)).toEqual([2, 3])
20 })
21
22 it ('8 is 2 * 2 * 2', () => {
23 expect(primefactors(8)).toEqual([2, 2, 2])
24 })
25 });
Para implementar tenemos que cambiar un if por un while. Es decir, tenemos que seguir dividiendo el número por 2 hasta que ya no podamos más:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose > 1) {
6 while (numberToDecompose % 2 === 0 && numberToDecompose > 2) {
7 factors.push(2);
8 numberToDecompose = Math.floor(numberToDecompose/2);
9 }
10
11 factors.push(numberToDecompose);
12 }
13
14 return factors;
15 }
16
17 export default primefactors;
Este cambio es muy espectacular porque es muy pequeño y muy potente. Aplicándolo podemos descomponer cualquier número que sea potencia de 2, ni más ni menos. Pero no es este el objetivo final, sino que queremos descomponer cualquier número y para eso tenemos que poder introducir nuevos divisores.
Nuevos divisores
En este punto necesitamos un ejemplo que nos obligue a introducir nuevos divisores. Antes hemos dejado aparcado el 9, y ahora nos toca retomarlo. El 9 es un buen ejemplo porque es múltiplo de 3, sin serlo de 2. Vamos a hacer un test que sabemos que fallará:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17
18 it ('6 is 2 * 3', () => {
19 expect(primefactors(6)).toEqual([2, 3])
20 })
21
22 it ('8 is 2 * 2 * 2', () => {
23 expect(primefactors(8)).toEqual([2, 2, 2])
24 })
25
26 it ('9 is 3 * 3', () => {
27 expect(primefactors(9)).toEqual([3, 3])
28 })
29 });
De nuevo, empecemos con una implementación muy ingenua, pero que funciona. Lo importante es que el test pase, prueba de que hemos implementado el comportamiento.
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose > 1) {
6 while (numberToDecompose % 2 === 0 && numberToDecompose > 2) {
7 factors.push(2);
8 numberToDecompose = Math.floor(numberToDecompose/2);
9 }
10
11 while (numberToDecompose % 3 === 0 && numberToDecompose > 3) {
12 factors.push(3);
13 numberToDecompose = Math.floor(numberToDecompose / 3);
14 }
15
16 factors.push(numberToDecompose);
17 }
18
19 return factors;
20 }
21
22 export default primefactors;
Con el código anterior, todos los tests están en verde. En este punto parece claro que cada nuevo divisor que queramos introducir, como el 5, necesitará una repetición del bloque así que vamos a refactorizar a una solución general.
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 let divisor = 2;
6
7 while (numberToDecompose > 1) {
8 while (numberToDecompose % divisor === 0) {
9 factors.push(divisor);
10 numberToDecompose = Math.floor(numberToDecompose / divisor);
11 }
12 divisor++;
13 }
14
15 return factors;
16 }
17
18 export default primefactors;
Este algoritmo tiene pinta de ser bastante general. Así que probemos un par de casos:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('2 is a prime number', () => {
9 expect(primefactors(2)).toEqual([2])
10 })
11
12 it ('3 is also a prime number', () => {
13 expect(primefactors(3)).toEqual([3])
14 })
15
16 it ('4 is 2 * 2', () => {
17 expect(primefactors(4)).toEqual([2, 2])
18 })
19
20 it ('6 is 2 * 3', () => {
21 expect(primefactors(6)).toEqual([2, 3])
22 })
23
24 it ('8 is 2 * 2 * 2', () => {
25 expect(primefactors(8)).toEqual([2, 2, 2])
26 })
27
28 it ('9 is 3 * 3', () => {
29 expect(primefactors(9)).toEqual([3, 3])
30 })
31
32 it ('10 is 2 * 5', () => {
33 expect(primefactors(10)).toEqual([2, 5])
34 })
35
36 it ('12 is 2 * 2 * 3', () => {
37 expect(primefactors(12)).toEqual([2, 2, 3])
38 })
39 });
Hemos añadido dos tests que pasan. Por lo que parece, hemos resuelto el problema. Pero, ¿no te queda la sensación de haber saltado demasiado en este último paso?
El camino más corto no siempre es el más rápido
El camino de desarrollo en TDD no siempre es fácil. El siguiente test a veces es bastante evidente y otras veces tenemos varias alternativas. Escoger mal el camino nos puede llevar a un callejón sin salida o, como en este caso, a un punto en que tengamos que implementar mucho de golpe. Y como hemos visto, los cambios que añadamos al código de producción deberían ser lo más pequeños posible.
En el sexto test optamos por explorar la vía de las repeticiones del mismo factor en lugar de forzar que aparecieran otros factores primos. ¿Hubiera sido mejor seguir esta ramificación del problema? Probémoslo, rebobinamos y volvemos a la situación antes del sexto test.
Introduciendo nuevos factores, segundo intento
Esta es la versión del código de producción en la que estábamos al llegar al sexto test:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose % 2 === 0) {
6 factors.push(2);
7 numberToDecompose = Math.floor(numberToDecompose / 2);
8 }
9
10 if (numberToDecompose > 1) {
11 factors.push(numberToDecompose);
12 }
13
14 return factors;
15 }
16
17 export default primefactors;
Ahora sigamos por la otra ruta:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17
18 it ('6 is 2 * 3', () => {
19 expect(primefactors(6)).toEqual([2, 3])
20 })
21
22 it ('9 is 3 * 3', () => {
23 expect(primefactors(9)).toEqual([3, 3])
24 })
25 });
El siguiente código de producción nos permite pasar el nuevo test y todos los anteriores:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 if (numberToDecompose % 2 === 0) {
6 factors.push(2);
7 numberToDecompose = Math.floor(numberToDecompose / 2);
8 }
9
10 if (numberToDecompose % 3 === 0) {
11 factors.push(3);
12 numberToDecompose = Math.floor(numberToDecompose / 3);
13 }
14
15 if (numberToDecompose > 1) {
16 factors.push(numberToDecompose);
17 }
18
19 return factors;
20 }
21
22 export default primefactors;
Ahora podríamos refactorizar:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 let divisor = 2;
6
7 while (divisor < numberToDecompose) {
8 if (numberToDecompose % divisor === 0) {
9 factors.push(divisor);
10 numberToDecompose = Math.floor(numberToDecompose / divisor);
11 }
12 divisor++;
13 }
14
15 if (numberToDecompose > 1) {
16 factors.push(numberToDecompose);
17 }
18
19 return factors;
20 }
21
22 export default primefactors;
Más de dos factores
Para introducir más de dos factores necesitamos un test:
1 import primefactors from "../src/primefactors";
2
3 describe('Calculate prime factors', function () {
4 it('1 should not have factors', () => {
5 expect(primefactors(1)).toEqual([]);
6 });
7
8 it ('prime numbers cannot be decomposed', () => {
9 expect(primefactors(2)).toEqual([2])
10 expect(primefactors(3)).toEqual([3])
11 expect(primefactors(5)).toEqual([5])
12 })
13
14 it ('4 is 2 * 2', () => {
15 expect(primefactors(4)).toEqual([2, 2])
16 })
17
18 it ('6 is 2 * 3', () => {
19 expect(primefactors(6)).toEqual([2, 3])
20 })
21
22 it ('9 is 3 * 3', () => {
23 expect(primefactors(9)).toEqual([3, 3])
24 })
25
26 it ('8 is 2 * 2 * 2', () => {
27 expect(primefactors(8)).toEqual([2, 2, 2])
28 })
29 });
El cambio necesario es sencillo:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 let divisor = 2;
6
7 while (divisor < numberToDecompose) {
8 while (numberToDecompose % divisor === 0) {
9 factors.push(divisor);
10 numberToDecompose = Math.floor(numberToDecompose / divisor);
11 }
12 divisor++;
13 }
14
15 if (numberToDecompose > 1) {
16 factors.push(numberToDecompose);
17 }
18
19 return factors;
20 }
21
22 export default primefactors;
Y podemos librarnos del último if puesto que queda cubierto por el while que acabamos de introducir:
1 function primefactors(numberToDecompose) {
2
3 let factors = [];
4
5 let divisor = 2;
6
7 while (divisor <= numberToDecompose) {
8 while (numberToDecompose % divisor === 0) {
9 factors.push(divisor);
10 numberToDecompose = Math.floor(numberToDecompose / divisor);
11 }
12 divisor++;
13 }
14
15 return factors;
16 }
17
18 export default primefactors;
Si añadimos nuevos tests veremos que podemos factorizar cualquier número sin problemas. Es decir, con este último cambio y su refactor hemos terminado el desarrollo de la clase. ¿Ha sido mejor este camino? En parte sí. Hemos llegado a un algoritmo casi idéntico, pero diría que el recorrido ha sido más suave, los saltos de código de producción menos pronunciados y todo ha ido mejor hilado.
¿Tenemos criterios para elegir buenos ejemplos?
Desde el punto de vista de QA tradicional existen una serie de métodos para elegir los casos de test. Sin embargo, estos métodos no son necesariamente aplicables en TDD. Recuerda cómo empezábamos este libro: QA y TDD no son lo mismo pese a usar las mismas herramientas y solaparse mucho ambas disciplinas. TDD es una metodología para guiar el desarrollo de software y los tests más adecuados para hacerlo pueden ser sutilmente diferentes de los que usaríamos para verificar el funcionamiento de un software.
Por ejemplo, la categorización que hemos hecho de los números en primos y no primos puede ser más que suficiente en QA, pero en TDD los casos de números no primos podrían subdividirse:
- Potencias de un factor primo, como el 4, el 8 o el 9, que implican un único primo multiplicado varias veces por sí mismo.
- Producto de diferentes primos, como el 6, el 10, que implican varios números primos.
- Productos de n factores primos, con n mayor que dos.
Cada una de estas categorías nos fuerza a implementar distintas partes del algoritmo, lo que puede plantearnos problemas más o menos fáciles de resolver. Incluso, una mala elección podría llevarnos a un callejón sin salida.
Sin embargo, nada nos impide volver atrás si nos quedamos estancados. Cuando tengamos una duda razonable de si tomar un curso u otro en TDD, lo mejor es tomar nota de cuál es el estado del desarrollo y ese punto, marcándolo como un punto de retorno en caso de que nos metamos en algún cenagal de código. Marcha atrás y pensar de nuevo. Haberse equivocado es también información.
Qué hemos aprendido con esta kata
- Con esta kata hemos aprendido cómo a medida que añadimos tests y estos son más específicos, el algoritmo se hace más general
- También hemos visto que obtenemos mejores resultados priorizando las transformaciones (cambios en el código de producción) más sencillas