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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | |
} | |
} | |
} |
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:
- Sobre el método Uncle Bob – The transformation priority premise
- Ven al siguiente Software Craftmanship Madrid en Meetup