Diseñando el dominio de datos
Hemos empezado. Esto es bueno, porque generalmente en todas las cosas que implican pensar y razonar, hay que mantener un cierto ritmo. Debemos avanzar con calma y seguridad, pero debemos avanzar realmente. Pero como decíamos no hemos hecho más que empezar. Apenas hemos escrito unas funciones de una de las partes de la solución y poco más.
A partir de aquí hay toda una serie de preocupaciones a las que debemos prestar atención. Pero primero vamos a ver otra forma o parte por la que empezar.
Diseñando el dominio de datos
El ejemplo del emisor Morse es muy simple. Nos permite ver algunas ideas sencillas, pero no da mucho más de sí para poder apreciar otras que también son importantes. En particular, la falta de complejidad en las operaciones y datos implica que, aunque siga siendo aplicable la idea de escribir un código estructurado y organizado, no necesitemos plantear otras necesidades.
Una de las principales características del ejemplo del traductor de
Morse es la ausencia de preocupación por los tipos de datos. De hecho,
sólo existe un tipo de datos. Tomamos una cierta cadena de texto y la
transformamos en otra cierta cadena de texto. Es cierto que podríamos,
forzando un poco las cosas, pensar en definir un tipo SimboloMorse
que
represente los símbolos de punto (.
) y guión (--
). Podríamos
hacerlo. Pero realmente estamos forzando un poco las cosas. Si
recordamos, ni siquiera nos interesaba la representación intermedia en
puntos y guiones. Podríamos eliminarla por completo (y más adelante,
para un objetivo concreto, lo haremos).
En un caso más general, cuando diseñamos nuestra solución lo habitual es que nos encontremos que en nuestro dominio (o en el dominio de una de esas partes) manejamos una serie de conceptos o definiciones de diferentes tipos de datos. Es algo a lo que conviene prestar atención porque diseñar nuestros tipos y estructuras de datos de un modo o de otro puede cambiar radicalmente la complejidad de las operaciones y la lógica que escribamos para manejarlos. Así que otra forma alternativa de comenzar nuestra primera aproximación al código es definiendo esos tipos de datos.
Tomemos el ejemplo de Mastermind. Hemos identificado que existen varios elementos que manejaremos en nuestra solución: Fichas de colores, combinaciones de dichas fichas, fichas de pista, combinaciones de estas otras pistas, el concepto partida con sus 12 turnos. Poco más en el dominio del problema. En la interfaz seguramente tendremos alguna cosa más, como el tablero, quizá una clasificación de jugadores con el número de partidas o lo que queramos añadir. Por ahora nos centramos en el dominio del juego en sí mismo.
Nota: Como sabemos en JavaScript el sistema de tipos de datos es bastante... digamos que no es gran cosa. Voy a poner una especie de definición genérica, en ningún lenguaje en particular. Dependiendo de las características de nuestro lenguaje real, adaptaremos esto con mayor o menor fortuna.</wrap>
{ficha es un dato que solo puede tomar uno de estos valores}
type ficha = ( red, green, blue, cyan, magenta, yellow );
{una combinación es un array de 4 fichas}
type combinacion = array[4] of ficha;
{una pista es una pareja de números, el número de "aciertos en posición"
y el de "aciertos no posicionados". Ambos pueden vales de 0 a 4}
type pista = record
posicionados: 0..4,
sinposicion: 0..4
end;
{ ... }
Este es un diseño básico. Hemos identificado en general la información que lleva cada entidad y su forma más fundamental.
Por ejemplo, para la combinacion
hemos elegido representarla como un
array
o un vector. La forma específica que tenga en tu lenguaje puede
variar, pero por array
me refiero a una lista ordenada de una cantidad
determinada de elementos a los que podemos acceder con un índice -en la
mayoría de lenguajes existe un tipo de datos array
o vector
de este
estilo-. Podríamos haber elegido otras estructuras. Quizá una lista
genérica, una lista simple o doblemente enlazada, un registro con cada
una de las posiciones con su propio nombre, o, a lo mejor, podríamos
usar algún tipo de tabla
sin orden particular. Este caso es sencillo
y parece claro, pero conviene pensar por qué lo hemos hecho así. La
propia mecánica del juego nos dice que una combinación está determinada
por 4 fichas cada una en una posición concreta. Es decir la combinación
la forma la selección de colores particular y sus posiciones. Las
operaciones que luego haremos sabemos que comprobarán las posiciones. Y
además sabemos que las posiciones son exactamente 4, nunca más... ni
menos. Así elegimos algo que parece expresar bien todas estas
restricciones y necesidades, un vector de 4 posiciones.
Para la pista
podríamos hacer algo más. De forma básica el número de
posicionados
y el de sinposicion
son valores de 0
a 4
. Pero
además, hay otra restricción adicional que podemos imponer: la suma
de ambos también debe estar en el rango de 0
a 4
. Así, podemos
pensar en definir un tipo de dato algo más sofisticado. Si el lenguaje
lo permite podríamos hacer algo como...
type pista = record
posicionados: 0..4,
sinposicion: 0..4
end
where posicionados + sinposicion: 0..4;
Pero como es probable que no, entonces podríamos pensar por ejemplo en definir una clase propia que implemente esta restricción. Por ejemplo, algo del estilo de:
class Pista {
posicionados: 0..4;
sinposicion: 0..4;
constructor(posicionados, sinposicion) {
if (posicionados + sinposicion > 4) {
throw DataRestrictionException("Una Pista no puede sumar más de 4");
}
// ...resto del código...
this.posicionados = posicionados;
this.sinposicion = sinposicion;
// etc
}
}
Hacer un buen diseño de datos es cuestión de práctica y experiencia. Para quien no tiene aún esa experiencia, lo recomendable es por un lado practicar y por otro intentar mantener las soluciones lo más simples posible. Como siempre por otro lado. Además, también como en otras ocasiones, siempre es más a fácil añadir un detalle que no hayamos incluido inicialmente, que quitar algo de lo que -posiblemente- ya dependan otras cosas.
Un buen diseño de datos, como indicaba más arriba, es importante porque
tener los datos estructurados de una forma o de otra puede cambiar
enormemente la dificultad de implementar luego operaciones sobre ellos.
También es importante porque si tenemos las cosas bien estructuradas y
organizadas, luego siempre resulta más fácil razonar sobre ellas, y así,
cuando tenemos claro qué es para nosotros, por ejemplo, una
combinacion
, seguramente veremos más claro qué operaciones se pueden
hacer con ella y cómo.