NORMAS DE DISEÑO PARA MINË
--------------------------

- Es pérdida de tiempo escribir código para que haga cosas en seguida, y luego
  borrarlo y volverlo a escribir para que haga cosas bien. Esto sólo es válido
  para hacer prototipos de alguna técnica nueva que aún no se conoce.

- Para el resto del código, hay que diseñar primero el interfaz de las clases
  y luego el código. El interfaz consiste en las declaraciones de clase y de
  métodos, incluyendo los "doc comments".

- Dicho de otra manera, hay que escribir primero el "contrato" de la clase y
  luego la implementación del contrato (para saber qué significa esto, seguir
  leyendo). El contrato es la parte más "creativa" de escribir, si está bien
  hecho, la implementación es casi automática.



DISEÑO POR CONTRATO
-------------------

1) Tipos Abstractos de Datos
   -------------------------

  Según esta técnica, cada clase representa un Tipo Abstracto de Datos, en el
sentido matemático: es decir, una pieza de información, con propiedades (o
atributos), y operaciones que realizan cálculos sobre esas propiedades o cambian
el valor de las mismas. El interfaz de la clase debe coincidir con el T.A.D.,
y el resto de la clase debe ser oculto (privado).

     > Por ejemplo, una Sala es una habitación o lugar del juego. Sus propiedades
     > son el id, autor, nombre, volumen, personajes que contiene, etc. Sus
     > operaciones son crear una sala nueva, entrar y salir un personaje, etc.


  En Python, el mapa entre T.A.D. y clase no está perfectamente definido (tengo
el proyecto de investigar sobre esto), pero a grosso modo, las propiedades
corresponden a los atributos públicos, y las operaciones a las funciones
miembro, públicas también. La parte privada de la clase no tiene reflejo en el
T.A.D., y no debe ser conocida por ningún otro trozo de código.

     > Por ejemplo, Sala.id, Sala.autor, etc. son atributos, y Sala.__init__(),
     > Sala.entrar_personaje(), etc. son funciones miembro.


2) Requisitos de las propiedades: dominios e invariantes
   -----------------------------------------------------

  Las propiedades deben cumplir una serie de requisitos. Por un lado, el
DOMINIO o conjunto de valores que puede tomar (es decir, el tipo: string, int,
etc.).

     > El dominio del id de una Sala son los números enteros (int), el del
     > autor son las cadenas de caracteres (string), etc.
 

  En Python, los tipos existen pero no son declarados (es un lenguaje dinámicamente
tipado), e incluso una variable puede adoptar valores de distintos tipos. Esto
puede ser cómodo en proyectos pequeños, pero en los medianos o grandes, hay que
indicar los tipos en el interfaz, y para ello se pueden usar los strings de
documentación. Estoy buscando una forma más o menos estándar de hacerlo, pero
de momento lo haremos así (si un atributo puede tener distintos tipos, se
comenta también, pero no es algo tan frecuente):

      > class Sala:
      >     """<descripción de la clase, una línea>
      >
      >     <descripción más larga>
      >
      >     Contiene los siguientes atributos:
      >      - id (string): <descripción>
      >      - luz (int): <descripción>
      >     [...]
      >     """

  Por otro, las propiedades tienen restricciones o condiciones que deben cumplirse
siempre. A estas condiciones se les denomina INVARIANTES. Una instancia cuyas
propiedades cumplen todas las invariantes es una instancia válida. NO SE ADMITEN
INSTANCIAS NO VÁLIDAS EN NINGÚN MOMENTO, excepto durante la creación de la
instancia o la ejecución de una única de sus funciones miembro públicas. A la
salida de cualquier función miembro pública, el código debe asegurar que ninguna
invariante se ha roto, en ningún caso (esto no es necesario en las privadas).

     > Por ejemplo, son invariantes Sala.area != None, aura in range(0,100),
     > n_jugadores >= 0, n_jugadores <= len(personajes), etc.
     > El__init__() debe dar valores a todos los atributos que satisfagan
     > todas las invariantes. No se admite, por  ejemplo, crear una sala con
     > area == None, y luego darle un valor más tarde. Sí se admite que el
     > __init__ la inicialice a None y más tarde llame a una función que lee
     > el área de un fichero y actualice el valor, siempre y cuando todo ello
     > ocurra dentro de la ejecución de __init__.


  En Python no hay ningún mecanismo para expresar invariantes, así que lo haremos
también en la documentación. También estoy buscando una forma estándar. De
momento será como sigue. Durante la depuración, si se quiere verificar algunas
invariantes, se puede usar la instrucción assert justo antes del return de
todas las funciones (no hacerlo sistemáticamente con todas, porque puede 
cargar mucho).

      > class Sala:
      >     """[...]
      >     [...]
      >     Invariantes:
      >       id != None
      >       aura in range(0,100)
      >       n_jugadores <= len(personajes)
      >     [...]
      >     """
      >
      >     def entrar_personaje(self, personaje):
      >         [...]
      >         assert self.n_jugadores <= len(self.personajes)
      >
      >     def salir_personaje(self, personaje):
      >         [...]
      >         assert self.n_jugadores <= len(self.personajes)
      

3) Requisitos de las operaciones: declaración, precondiciones y postcondiciones
   ----------------------------------------------------------------------------

  Las operaciones también tienen requisitos. Por un lado, está la DECLARACIÓN
(el nombre y tipo de los parámetros, y el tipo de valor devuelto).

      > Por ejemplo, la operación "entrar personaje en una sala" tiene un
      > parámetro que es la sala y otro que es el personaje que entra; sus
      > tipos son las clases Sala y Personaje. No devuelve ningún valor.


  En Python los parámetros vienen explícitos, y los tipos se pueden poner en
el comentario, si no son obvios. Pueden ser obvios en los siguientes casos: si
el nombre coincide con el de una clase, el tipo es esa clase; si coincide con
el de un atributo de la clase actual, el tipo es el mismo que el del atributo;
si el tipo es int o string, en muchos casos queda claro con sólo el nombre. Lo
mismo con el valor de retorno.

      > def entrar_personaje(self, personaje):
      >   """[...]
      >   self(Sala) -- obvio, el self siempre está claro
      >   personaje(Personaje) -- obvio, mismo nombre que clase
      >   """
      >
      > def mensaje_filtrado(self, mensaje, idioma, oyente):
      >   """[...]
      >   mensaje(string) -- puede ser obvio, no hay clase mensaje así que
      >                      será un string
      >   idioma(Idioma) -- obvio, nombre de clase
      >   oyente(Personaje) -- en principio no es obvio, pero se sabe
      >                        por la descripción del método
      >  
      >   Devuelve string -- puede ser obvio también por la descripción
      >   """


  Las PRECONDICIONES son requisitos que hay que cumplir antes de llamar a una
operación. Pueden referirse a los parámetros de la operación o a las propiedades
de la clase. Su comprobación es responsabilidad del código llamante (lo puede
hacer bien explícitamente, con algún if, o bien apoyandose en una invariante o
postcondición). La función debe asumir que las precondiciones se cumplen, y
no debe volverlas a chequear. Así se asegura que cada chequeo se realiza sólo
una vez, y se evitan redundancias.

      > Por ejemplo, la sala tiene una invariante que dice que el aura es
      > mayor que 0. Si el constructor lee los datos de la sala de un fichero,
      > al leer el aura debe comprobar que cumple la invariante, y si no, dar un
      > error y no crear la sala. A partir de ahí, todo el resto del código
      > asume que el aura es mayor que 0, y se puede pasar como parámetro a una
      > función que tenga esto como precondición.


  En Python las precondiciones las colocaremos en el texto del comentario, de la
siguiente manera. Durante la depuración, también se puede usar assert para
comprobar algunas precondiciones, justo al principio de la función:

      > def salir_personaje(self, personaje):
      >     """[...]
      >     Requiere:
      >       personaje != None
      >       personaje in self.personajes.values()
      >     [...]
      >     """
      >     assert personaje in self.personajes.values()


  Las POSTCONDICIONES son requisitos que una operación se compromete a
asegurar que se cumplen una vez que la misma haya terminado con éxito. Pueden
referirse a las propiedades de la clase o al valor de retorno, si lo hay. El
código llamante no debe volverlos a chequear. En cierta forma, las
postcondiciones representan la información semántica de la operación (su
utilidad prevista), frente a la información sintáctica de la declaración.

      > Por ejemplo, la operación salir personaje de una sala asegura que si
      > se completa con éxito, el personaje ya no está dentro de la sala.


  En Python, se colocan de forma similar a las precondiciones. Si se usa el
assert, se puede poner justo antes de volver, o en el código llamante justo
después de hacer la llamada:

      > def salir_personaje(self, personaje):
      >     """[...]
      >     Asegura:
      >       personaje not in self.personajes.values()
      >     """


4) El contrato
   -----------

  El conjunto de requisitos de propiedades y operaciones se conoce como el
CONTRATO del T.A.D. Se llama así porque se asemeja a un contrato real: existe
un proveedor (la clase), clientes (cualquiera que lea sus propiedades o use
sus operaciones) y cláusulas (los requisitos).

  Igual que en un contrato real, cada parte se compromete a cumplir su
obligación, y hay un sistema para tratar los incumplimientos.

  El cliente se compromete a asegurar las precondiciones. Si no se puede
cumplir alguna de ellas, no debe llamar a la operación en ningún caso. Una
función no debe añadir código para protegerse contra precondiciones no
cumplidas (puede permitirse incluso dejar el ordenador colgado o matar el
programa si quiere), ya que nunca se va a dar el caso cuando el programa esté
depurado (durante la depuración se puede usar assert).

  El constructor de la clase debe asegurar todas las invariantes. Si no es
posible cumplir alguna de ellas, debe lanzar una EXCEPCIÓN y abortar la
creación de la instancia. En ese caso el cliente debe tirar la instancia a
medias sin usarla.

  Finalmente, las operaciones de la clase se comprometen a verificar tanto las
invariantes como las postcondiciones. Si no pueden cumplir alguna, se debe
lanzar una EXCEPCIÓN. En ese caso, puede que algunas postcondiciones estén
cumplidas y otras no (no se puede asegurar), pero al menos la instancia tiene
que seguir siendo válida y la operación tiene que restaurar todas las
invariantes que puedan estar temporalmente deshabilitadas.

  Las excepciones no son un mecanismo de control de flujo. Sólo deberían
ocurrir en caso de incumplimiento de invariantes o postcondiciones, y en casos
excepcionales (tipo disco duro lleno, datos corruptos, versiones
incompatibles, etc.). Para situaciones que puedan ocurrir en una ejecución
normal del programa (por ejemplo, el usuario introduce un nombre que no
existe, se ha llegado al final de una lista, etc.) es mejor usar condiciones
explícitas (con if, while, etc.).

  Del mismo modo, las excepciones sólo se deben capturar en dos casos: cuando
haya que restaurar alguna invariante, y luego seguir pasando la excepción
hacia arriba, o cuando se llegue a un punto en el que se pueda volver a
intentar la operación. En particular está prohibido capturar una excepción y
luego hacer return normal, devolviendo un valor falso o que no cumple las
postcondiciones.

