Temporary variables

Introducción: persona instruída

Supongamos que tenemos que conocer si Helmut es una persona instruida, esto ocurre cuando leyó más de 20 libros y conoce más de 3 idiomas.

Tenemos esta implementación:

object helmut {
  var librosLeidos = 0
  const idiomasQueConoce = []
  method leerLibro() { librosLeidos = librosLeidos + 1 }
  method conocerIdioma(idioma) { idiomasQueConoce.add(idioma) }
  method esInstruido() ...
}

Bad smell: temporary variable

Es un patrón frecuente la resolución del ejemplo agregando un nuevo atributo en helmut:

object helmut {
  ...
  var instruido = false
  method esInstruido() = instruido
}

Una solución similar puede ser definir instruido como una property, el efecto es el mismo. Esto tiene algunas desventajas:

Para mantener consistente el estado de Helmut, deberíamos codificar su comportamiento de la siguiente manera:

object helmut {
  var librosLeidos = 0
  const idiomasQueConoce = []
  var instruido = false
  method leerLibro() {
    librosLeidos = librosLeidos + 1
    // sincronizo esInstruido...
    instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
  }
  method conocerIdioma(idioma) {
    idiomasQueConoce.add(idioma)
    // sincronizo esInstruido...
    instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
  }
  method esInstruido() = instruido
}

Por supuesto, podemos extraer un método aparte, igualmente estaremos enviando el mismo mensaje en ambos casos:

object helmut {
  ...
  method leerLibro() {
    librosLeidos = librosLeidos + 1
    self.actualizoInstruido()
  }
  method conocerIdioma(idioma) {
    idiomasQueConoce.add(idioma)
    self.actualizoInstruido()
  }
  method actualizoInstruido() {
    instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
  }
  ...
}

Otra variante similar consiste en que el método instruido haga algo como:

method leerLibro() {
  librosLeidos = librosLeidos + 1
  self.esInstruido()
}
method conocerIdioma(idioma) {
  idiomasQueConoce.add(idioma)
  self.esInstruido()
}
method esInstruido() {
  instruido = librosLeidos > 20 && idiomasQueConoce.size() > 3
  return instruido
}

lo cual agrega más desventajas:

Una alternativa más simple

La alternativa más sencilla es descartar todos los atributos que pueden calcularse, de esa manera evitamos sincronizar el estado de Helmut:

object helmut {
  var librosLeidos = 0
  const idiomasQueConoce = []
  method leerLibro() { librosLeidos = librosLeidos + 1 }
  method conocerIdioma(idioma) { idiomasQueConoce.add(idioma) }
  method esInstruido() = librosLeidos > 20 && idiomasQueConoce.size() > 3
}

Aquí vemos cómo tenemos

Heurística para tener atributos que pudieran ser calculables

En general, intentar guardarse cosas de antemano para “optimizar” es un smell. Como dijo Donald Knuth, premature optimization is the root of all evil.

Sin embargo, sí hay ocasiones donde guardarse un resultado de antemano es aceptable:

son señales en los que tener un cálculo como atributo es justificable, algo que es improbable que ocurra en cursos iniciales de programación OO.