Desde finales de 2015 comencé a interesarme mucho por escribir código más limpio y comprensible, esto me llevó a leer diferente bibliografía al respecto, libros, artículos, opiniones. Durante este proceso descubrí que existen infinidad de conceptos y reglas que ayudan a los desarrolladores a conseguir que su código sea bello.

Aunque todos los conceptos que he ido descubriendo se basan en el sentido común, la realidad es que poco se aplican en el mundo real, lo que me lleva a pensar que el sentido común es el menos común de los sentidos.

Hoy os hablaré de la Calistenia de objetos (Object Calisthenics), si, suena raro, ¿qué es la calistenia?.

Según wikipedia:

La calistenia es un sistema de ejercicios físicos con tu propio peso corporal en el cual el interés está en los movimientos de grupos musculares más que en la potencia y el esfuerzo. La palabra proviene del griego kallos (belleza) y sthenos (fortaleza). El objetivo es la adquisición de gracia y belleza en el ejercicio. Es la belleza que tiene el cuerpo en movimiento.

La Calistenia de objetos (Object Calisthenics) son ejercicios para el programador que se formalizan en nueve reglas. Éstas reglas tienen como objetivo escribir código mantenible, legible, testeable y comprensible.

Las 9 reglas de la Calistenia de objetos.

  1. Un solo nivel de indentación por método.
  2. No uses la palabra Else.
  3. Envuelve los tipos primitivos y Strings.
  4. Usa clases para encapsular las colecciones.
  5. Solo un punto por linea.
  6. No abrevies.
  7. Entidades y Clases pequeñas.
  8. Evita las clases con más de dos variables, campos o atributos.
  9. No uses getters, setters o propiedades.

1. Un solo nivel de indentación por método.

Tener varios niveles de indentación complica la lectura y comprensión del código, en muchas ocasiones no sabes que hace hasta que lo compilas y debugas.

class Board {
   public String board() {
      StringBuilder buf = new StringBuilder();

      // 0
      for (int i = 0; i < 10; i++) {
         // 1
         for (int j = 0; j < 10; j++) {
            // 2
            buf.append(data[i][j]);
         }
         buf.append("\n");
      }

      return buf.toString();
   }
}

Apreciamos que la clase Board anida diferentes bucles y tiene varias responsabilidades, esto dificulta la lectura y comprensión, además de complicar los test.

Podríamos conseguir el mismo objetivo extrayendo el primer y segundo nivel de indentación de la siguiente forma:

class Board {
   public String board() {
      StringBuilder buf = new StringBuilder();

      collectRows(buf);

      return buf.toString();
   }

   private void collectRows(StringBuilder buf) {
      for (int i = 0; i < 10; i++) {
         collectRow(buf, i);
      }
   }

   private void collectRow(StringBuilder buf, int row) {
      for (int i = 0; i < 10; i++) {
         buf.append(data[row][i]);
      }
      buf.append("\n");
   }
}

Extraer métodos no hará que tengas menos líneas de código, al contrario es probable que incorpore alguna líneas más, pero no olvidemos que el principal objetivo es que el código sea claro y legible.

2. No uses la palabra Else.

¿Cuantas veces hemos utilizado las estructuras if/else?, muchas. En serio, aunque no lo creas, no la necesitas. Añade código innecesario que se puede evitar y hace que los test deban tenerlo en cuenta.

public void login(String username, String password) {
   if (userRepository.isValid(username, password)) {
      redirect("homepage");
   } else {
      addFlash("error", "Bad credentials");
      redirect("login");
   }
}

Si el objetivo principal de este método es redirigir al login, deberíamos centrarnos en eso, y modificar la redirección solo si necesitamos enviarlo a otro lugar evitando utilizar Else. Podemos hacerlo de la siguiente forma:

public void login(String username, String password) {
   String redirectRoute = "homepage";
   if (!userRepository.isValid(username, password)) {
      addFlash("error", "Bad credentials");
      redirectRoute = "login";
   }

   redirect(redirectRoute);
}

3. Envuelve los tipos primitivos y Strings.

Reconozcámoslo, estamos obsesionados con los tipos primitivos, quizás pensemos que es más rápido al momento de escribir código, pero en realidad no lo es. Encapsular todos los tipos primitivos por objetos provoca que el código sea mucho mas claro. Utilizar un buen nombre de objeto y un tipo adecuado expondrá el comportamiento de tus objetos de una forma muy clara.

   void launchMissile(boolean isNuclear, boolean isTest, Coordinates target);

   // run a non-nuclear missile
   launchMissile(false, false, target);
   // run a nuclear missile test:
   launchMissile(true, true, target);

En el ejemplo desconocemos para que sirven los parámetros del método launchMissile, si no utilizamos tipos primarios ofreceremos más claridad, podríamos escribirlo así:

   void launchMissile(MissileType missileType, TestStatus testStatus, Coordinates target);

   launchMissile(MissileType.NON_NUCLEAR, TestStatus.IS_TEST, target);

Ahora sabemos que haremos una prueba de lanzamiento de un misil no nuclear.

4. Usa clases para encapsular las colecciones.

Cualquier clase que contenga un conjunto de elementos  y quieras manipular debe tener una clase especifica para ello. De esta forma cada colección y todo su comportamiento estarán encapsuladas en su propia clase.

public void createTeam() {
   List<Person> Team = new Arraylist<Person>();
   Person person = new Person("Andrew", "Smith");
   team.add(person);
}

Un uso muy estandarizado, quien no ha utilizado colecciones dentro de un método. El cambio en este caso es muy sutil, creamos una nueva clase para el equipo (team) y creamos un método para añadir nuevos integrantes al equipo, con los cambios aplicados quedaría así:

public void createTeam() {
   Team team = new Team();
   Person person = new Person("Andrew", "Smith");
   team.newMember(person);
}

5. Solo un punto por linea.

Nunca hables con extraños. Esta frase resume a la perfección este punto, que también está muy referido en el principio “Ley de Demeter“. En realidad la idea es no concatenar llamadas en una línea.

class Location {
   public Piece current;
}

class Piece {
   public String representation;
} 

class Board {
   public String boardRepresentation() {
      StringBuilder buf = new StringBuilder();

      for (Location loc : squares()) {
         buf.append(loc.current.representation.substring(0, 1));
      }

      return buf.toString();
   }
}

La concatenación “loc.current.representation.substring(0, 1)” confunde, que ocurre si “loc” cambia y no necesita referencia a “current”, o si “current” no necesitara referencia a “representation”. Esta forma de trabajar complica mucho la implementación y no es clara.

Una mejor forma de implementarlo sería:

class Location {
   private Piece current;

   public void addTo(StringBuilder buf) {
      current.addTo(buf);
   }
}

class Piece {
   private String representation;

   public String character() {
      return representation.substring(0, 1);
   }

   public void addTo(StringBuilder buf) {
      buf.append(character());
   }
}

class Board {
   public String boardRepresentation() {
      StringBuilder buf = new StringBuilder();

      for (Location location : squares()) {
         location.addTo(buf);
      }

      return buf.toString();
   }
}

6. No abrevies.

¿Por qué quieres abreviar?. Tal vez tu clase tenga responsabilidades múltiples, si es así debes saber que violas el principio de responsabilidad única.

Si no puedes encontrar un nombre decente para una clase o un método, probablemente algo has hecho mal.

No olvides que tu código debe ser entendido por otros, si abrevias seguramente serás el único que entienda lo que has escrito, y seguramente, en el futuro ni siquiera tú entenderás que hace tu propio código.

class trnslr {
}

foreach ($x in $ppl) {
   $x->name;
}

class URepo {
   public function fetch($bId){
   }
}

class Ordr {
   public function shpOrdr(){}
}
$ordr->shpOrdr()

Dios, no se entiende nada… Hagamos algunos cambios para que todo cobre sentido:

class translator {
}

foreach ($person in $people){
   $person->name;
}

class UserRepository {
   public function fetchByBillingId($billingId){
   }
}

class Order {
   public function shipOrder(){
   }
}
$order->shipOrder();

7. Entidades y Clases pequeñas.

Los archivos largos son más difíciles de leer, entender y mantener. Según Object Calisthenics, una clase no debe contener más de 50 líneas de código ni paquetes com más de 10 archivos. Bueno, no seamos más papistas que el papa, apliquemos un poco el sentido común. Además, si cumplimos el principio de responsabilidad única, nuestras clases y métodos no serán muy grandes.

8. Evita las clases con más de dos variables, campos o atributos.

Uno de los principios de clean code. Cuantas más variables campos o atributos tengamos en una clase, más complicaciones tendremos en nuestras implementaciones y test.

Este punto es en el que más desarrolladores fallamos (me incluyo), no es fácil. Para cumplirlo es indispensable conocer muy bien el dominio y trabajar con una buena arquitectura que nos permita un cohesión alta y una buena encapsulación de nuestras clases.arquitectura

9. No uses getters, setters o propiedades.

Tell, don’t ask” nos recuerda que no debemos usar los objetos para pedirles cosas y tomar decisiones después, lo que debemos hacer es decirles a los objetos que hagan cosas para que éstos internamente tomen sus propias decisiones según su estado.

private int score;

public void setScore(int score) {
   this.score = score;
}

public int getScore() {
   return score;
}

game.setScore(game.getScore() + ENEMY_DESTROYED_SCORE);

Cuando necesitamos saber el estado de un objeto para después tomar una decisión y realizar una acción es recomendable que sea el propio objeto quien tenga la responsabilidad de realizar la acción, de esta forma todo queda encapsulado y mantenemos la cohesión de nuestros objetos.

Para cumplir con esta regla en el ejemplo de arriba deberíamos implementarlo de la siguiente forma:

public void addScore(int delta) {
   score += delta;
}

game.addScore(ENEMY_DESTROYED_SCORE);

Si, son muchas reglas, pero si comenzamos a trabajar con ellas día tras día, llegará un momento en el que será como el andar o el respirar, las aplicaremos de forma automática. Además otros compañeros comprenderán nuestro código y desarrollaremos productos mucho mas estables y escalables.