Kata UpperCounter con Software Craftsmanship Madrid

El pasado martes 5 de agosto tuve la oportunidad de acudir a mi primer meetup de Software Craftsmanship Madrid, en el que se celebaba un coding dojo facilitado por Carlos Ble @carlosble. El objetivo de la sesión era hacer uso de un patrón diseñado por Robert «Uncle Bob» Martin llamado «Transformation Priority Premise» que nos lleva a una programación más genérica y funcional.

Tras sentarnos por parejas y escoger el entorno y el lenguaje de programación (En mi caso, C# con Visual Studio 2013 y XUnit como motor de pruebas) se desveló el objetivo de la kata:

Dada una cadena, devolver, en forma de array, las posiciones de dicha cadena cuyos caracteres sean la letra mayúscula

Ejemplos:
– A: {0}
– bA: {1}
– aBcdE: {1, 4}

Primera iteración, sin restricciones

Para esta primera iteración no teníamos ninguna limitación más allá de intentar resolver la kata. El resultado es el que se muestra en el vídeo:

El código completo, tanto del test como de la implementación se puede ver a continuación:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sample
{
public class UppercaseSearcher
{
internal int[] Search(string source)
{
var result = new List<int>();
if (source.Length > 0)
{
for (int i = 0; i < source.Length; i++)
{
var current = source[i];
if (char.IsUpper(source, i))
{
result.Add(i);
}
}
}
return result.ToArray();
}
private bool IsUpperCase(char current)
{
return current.ToString().ToUpper()
== current.ToString();
}
}
}

view raw

Source.cs

hosted with ❤ by GitHub


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Xunit.Extensions;
namespace Sample
{
public class Test
{
[Fact]
public void ShouldReturnEmptyArrayIfEmptyString()
{
//Arrange
var source = "";
var searcher = new UppercaseSearcher();
//Act
var result = searcher.Search(source);
//Assert
Assert.Empty(result);
}
[Fact]
public void ShouldReturnValidUppercaseLocation()
{
//Arrange
var source = "A";
var searcher = new UppercaseSearcher();
//Act
var result = searcher.Search(source);
//Assert
Assert.Equal(1, result.Length);
Assert.Equal(0, result[0]);
}
[Fact]
public void ShouldReturnValidUppercaseInSecondPlace()
{
//Arrange
var source = "bA";
var searcher = new UppercaseSearcher();
var expected = new int[] { 1 };
//Act
var result = searcher.Search(source);
//Assert
Assert.Equal(expected, result);
}
[Theory]
[InlineData("A", new int[] { 0 })]
[InlineData("bA", new int[] { 1 })]
[InlineData("bbAab", new int[] { 2 })]
[InlineData("babC", new int[] { 3 })]
//Multiple uppercases
[InlineData("bCbC", new int[] {1,3})]
//No uppercase
[InlineData("qwerty", new int[] { })]
public void ShouldBeValidInDifferentSituations(string source, int[] expected)
{
//Arrange
var searcher = new UppercaseSearcher();
//Act
var result = searcher.Search(source);
//Assert
Assert.Equal(expected, result);
}
}
}

view raw

Test.cs

hosted with ❤ by GitHub

El enfoque es iterativo, utilizando un bucle para recorrer los caracteres de la cadena y la comprobación es bastante artesanal (a mejorar en la segunda iteración) y además, como teníamos tiempo, pudimos probar las Theories de XUnit, que nos permiten utilizar, en el mismo test, diferentes conjuntos de entrada y esperar diferentes resultados.

Segunda iteración, con restricciones

En esta segunda iteración teníamos una limitación importante: No podíamos asignar variables, ni modificar valores existentes. Esto nos deja sin la posibilidad de usar bucles (ya que vamos actualizando la posición del iterador) y forzando nuestro código a que sea más funcional. El resultado es el que se muestra:

El código completo es el siguiente, con el que intentamos seguir a rajatabla la indicación de no asignar o modificar los valores de las variables. En este caso el tiempo no permitió pasar de unos pocos test, pero se aprecia una diferencia notable por una parte en el test y por otra en el código de implementación:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Sample.WithLimits
{
class UppercaseSearcher
{
internal static int[] Search(string source)
{
return Search(source, 0);
}
internal static int[] Search(string source, int index)
{
if (IsOutOfBounds(source, index))
{
return new int[0];
}
if (char.IsUpper(source, index))
{
return new int[] { index }
.Concat(Search(source, index + 1))
.ToArray();
}
else
{
return Search(source, index + 1)
.ToArray();
}
}
private static bool IsOutOfBounds(string source, int index)
{
return source.Length == 0 || index >= source.Length;
}
}
}

view raw

Searcher.cs

hosted with ❤ by GitHub


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Sample.WithLimits
{
public class TestClass
{
[Fact]
public void ShouldReturnEmptyArray()
{
//Arrange, act, assert
Assert.Equal(new int[0], UppercaseSearcher.Search(""));
}
[Fact]
public void ShouldReturn0IfOneLetterIsUppercase()
{
//Arrange, act, assert
Assert.Equal(new int[] {0}, UppercaseSearcher.Search("A"));
}
[Fact]
public void ShouldReturn1IfSecondLetterIsUppercase()
{
//Arrange, act, assert
Assert.Equal(new int[] { 1 }, UppercaseSearcher.Search("bA"));
}
}
}

view raw

Test.cs

hosted with ❤ by GitHub

Como nota adicional, gracias a @DanielRoz0 que me ha estado ayudando con la edición del vídeo, se pudo simplificar la comparación de una letra con su correspondiente mayúscula mediante el uso de la función char.IsUpper(source, index).

Información y enlaces: