Mi primera Code Kata

1 febrero, 2011 por Ana Buigues Dejar una respuesta »

Ayer hice junto a Héctor Rodes mi primera Code Kata, gracias a la iniciativa de 12meses12katas, donde cada mes se propone una nueva kata. Abierta a todo el mundo y a todos los lenguajes de programación, excelente para poder practicar y aprender.

Y…¿qué es una Code Kata? pues una Code Kata hace referencia a un ejercicio de programación en el cual se resuelve un problema más o menos complejo donde el objetivo es mejorar las cualidades de un programador mediante la práctica y resolución repetitiva de problemas.

Lo que obtenemos de todo esto es obligarnos a encontrar una solución a un problema y además nos permite contrastar nuestra solución con la de otras personas, de esta forma podemos descubrir nuevas formas de resolver el mismo problema, y si la programas en parejas es todavía más divertido.

Para Enero el ejercicio de la Code Kata era el String Calculator, la idea es hacer un programa que sume  los números de una cadena separados por diferentes delimitadores, por ejemplo: “8*5%3″ . Si queréis ver el código de la gente que ha realizado la Code Kata podéis visitar el github del proyecto. El nuestro está en el directorio de animalaes.

Nuestra solución

La hemos realizado en Java, con la ayuda de la clase Scanner de Java para parsear la cadena mediante expresiones regulares. Se admiten sugerencias sobre mejoras o lo que sea.

package com.anabuigues;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;
/**
 * Implementación sencilla de la calculadora para la kata de enero.<br />
 * <br />
 * La implementación está realizada usando java 6 estándar apoyándose en la
 * clase Scanner que permite trocear al vuelo un imput a partir de un o n
 * delimitadores. Para ello extrae los delimitadores y construye una expresión
 * reguar válida para el scanner al que se le solicitan los diferentes enteros
 * para ir sumándolos.
 *
 *
 * @author A. Buigues (@animalaes), H. Rodes (@hector_rodes)
 *
 */
public class StringCalculator {
	public int add(String numbers) throws Exception {
		int returnValue;
		if (numbers == null || numbers.isEmpty()) {
			returnValue = 0;
		} else {
			ByteArrayInputStream bais = new ByteArrayInputStream(
					numbers.getBytes());
			Scanner sc = new Scanner(bais);
			String delimiters = null;
			// Delimiters
			if (numbers.startsWith("//")) {
				sc.useDelimiter("//|\n");
				if (sc.hasNext()) {
					delimiters = sc.next();
				}
			}
			delimiters = getScannerFormattedDelimiters(delimiters);
			// Process number lines with numbers to be added
			sc.useDelimiter(delimiters);
			int sum = 0;
			int currentNumber;
			List<Integer> negativeValues = new ArrayList<Integer>();
			while (sc.hasNext()) {
				currentNumber = sc.nextInt();
				if (currentNumber < 0) {
					negativeValues.add(currentNumber);
				} else if (currentNumber <= 1000) {
					sum += currentNumber;
				}
			}
			checkNegativeValues(negativeValues);
			returnValue = sum;
		}
		return returnValue;
	}
	/**
	 * Método auxiliar que comprueba si hay números negativos y forma el mensaje
	 *
	 * @param negativeValues
	 *            Lista de números negativos
	 * @throws Exception
	 *             Si la lista de números negativos contiene algún número
	 */
	private void checkNegativeValues(List<Integer> negativeValues)
			throws Exception {
		if (negativeValues != null && negativeValues.size() > 0) {
			StringBuilder sb = new StringBuilder("negatives not allowed");
			for (int negative : negativeValues) {
				sb.append(" ").append(negative);
			}
			throw new Exception(sb.toString());
		}
	}
	/**
	 * Genera la cadena de delimitadores necesaria para ser usada en el scanner
	 * y así poder procesar los números.<br />
	 * Siempre introduce el \n como un delimitador válido.<br />
	 * En caso de que los delimiters sean vacÌos o nulos usa la , y el \n como
	 * delimitadores por defecto
	 *
	 * @param delimiters
	 *            Los delimitadores introducidos
	 * @return Cadena con los delimitadores en el formato que espera el Scanner
	 */
	private String getScannerFormattedDelimiters(String delimiters) {
		String delimitersExpression;
		if (delimiters == null || delimiters.isEmpty()) {
			delimitersExpression = ",|\n";
		} else {
			String currentDelimiter;
			StringBuilder sb = new StringBuilder();
			Scanner scDelimiters = new Scanner(delimiters).useDelimiter(Pattern
					.quote("]") + "|" + Pattern.quote("["));
			while (scDelimiters.hasNext()) {
				currentDelimiter = scDelimiters.next();
				if (!currentDelimiter.isEmpty()) {
					sb.append(Pattern.quote(currentDelimiter)).append("|");
				}
			}
			sb.append("\n");
			delimitersExpression = sb.toString();
		}
		return delimitersExpression;
	}
}

Y su test

package com.anabuigues;
import junit.framework.Assert;
import org.junit.Test;
/**
 *
 * @author A. Buigues (@animalaes), H. Rodes (@hector_rodes)
 *
 */
public class CalculatorTest {
	private StringCalculator calculator = new StringCalculator();
	@Test
	public void basicCalculator() throws Exception {
		Assert.assertEquals(0, calculator.add(""));
		Assert.assertEquals(1, calculator.add("1"));
		Assert.assertEquals(10, calculator.add("7,3"));
		Assert.assertEquals(10, calculator.add("7\n2,1"));
		Assert.assertEquals(27, calculator.add("5\n5\n8,2,4\n2,1"));
	}
	@Test
	public void simpleDelimiterCalculator() throws Exception {
		Assert.assertEquals(16, calculator.add("//[*]\n5*5*6"));
		Assert.assertEquals(20, calculator.add("//[*]\n5*5*6\n4"));
		Assert.assertEquals(20, calculator.add("//[*][;][,]\n5,5;6\n4"));
		Assert.assertEquals(20,
				calculator.add("//[*][;][pollofrito]\n5pollofrito5;6\n4"));
	}
	@Test
	public void bigNumbersCalculator() throws Exception {
		Assert.assertEquals(1005, calculator.add("//[*]\n5*1000*1001"));
	}
	@Test
	public void negativeNumbersCalculator() throws Exception {
		try {
			Assert.assertEquals(1005, calculator.add("//[*]\n5*-23*45*-34"));
		} catch (Exception e) {
			Assert.assertTrue("-23 must be in error message ", e.getMessage()
					.indexOf("-23") != -1);
			Assert.assertTrue("-34 must be in error message ", e.getMessage()
					.indexOf("-34") != -1);
		}
	}
	@Test
	public void notDefinedDelimiterCalculator() throws Exception {
		try {
			Assert.assertEquals(8, calculator.add("//[*]\n5;3"));
			Assert.fail("Delimiter ; is not allowed but it has been used");
		} catch (Exception e) {
		}
	}
}

Ahora toca ponerse con la de febrero!!

4 comentarios

  1. miquelsi dice:

    Molt interessant! A vore si m’anime i puc apuntar-me :-)

  2. Angel Armenta dice:

    Hola Ana,
    El otro día leyendo algún artículo de Robert C. Martin (uncle bob) me picó la curiosidad con esto de las Katas. Buscando Katas para hacer descubrí tu blog y el proyecto 12 meses 12 katas. He preparado el fork de esta kata y cuando tenga un ratito de tranquilidad la intentaré hacer y compartiré comentarios contigo.
    Yo también soy Javero y además soy de Alicante y estudié en la UA, soy de la promoción del 97. Me ha hecho ilusión descubrir a una persona tan cercana como tu que esté interesada en mejorar sus aptitudes como programador con este tipo de ejercicios, además leo que eres Agilista e interesada en la calidad del código y también son parte de mis intereses. Un saludo.

    • Ana Buigues dice:

      Hola Angel,
      Pues la verdad es que ahora mismo tengo el proyecto de 12meses12katas un poco abandonado, me has dado una buena excusa para retomarlo y así podemos comentarla!!
      Sobre el tema ágil, iba a recomendarte que te unieses al grupo de AgileAlicante pero he visto que ya formas parte del grupo!! así que en la próxima reunión nos veremos.

      Un saludo, Ana

  3. Sergio dice:

    Hola Ana

    Hace poco que empecé con esto de las Katas y me ha parecido genial, tengo algunas recomendaciones que me parece deberían aplicare a la Kata que has publicado. Esta semana sacare algo de tiempo para aplicarlas y publicaré el código para ver que opinas tú.

    1. En lugar de hacer validaciones en los if tales como
    if (numbers == null || numbers.isEmpty())

    Da más sentido al código emplear métodos que autodocumenten lo que se pretende validar algo como:
    if (isEmpty(numbers))

    Y crear el método isEmpty
    private static bolean isEmpty() {
    return numbers == null || numbers.isEmpty();
    }

    2. Reducir la cantidad de estructuras anidadas if, else if, while, etc y la cantidad de líneas de código en un solo método, esto permite mejorar la legibilidad del código y evita que el método haga mas cosas de las que debe hacer y por tanto facilita el mantenimiento del código.

Deja un comentario