Tinselcity

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 cosa1). 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.

{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.


1)
Sí, como un ornitorrinco

Discusión

Escribe el comentario. Se permite la sintaxis wiki: