Clean Code

Clean Code

Código que es fácilmente entendible, aún sin tener el autor al lado (puedo ser yo de hace unos meses)

Clean Code permite que el software sea menos propenso a errores, más fácil de testear y más fácil de extender o corregir 

La proporción de tiempo entre leer código y ejecutarlo es 10:1 

"Cualquier idiota puede escribir software que una máquina entienda.
Buenos programadores escriben software que
otras personas entiendan"
- Martin Fowler

Nombres

Usa nombres que
indiquen intención

// DON'T
int d; // elapsed time in days

// DO
int elapsedTimeInDays;

Evite Desinformación

// DON'T
Set<Account> accountList;

// DO something like
Set<Account> accounts;

Usa distinciones significativas

// DON'T
copyChars(char[] a1, char[] a2)

// DO
copyChars(char[] source, char[] copy)

// DON'T
getActiveAccount();
// how is this different?
getActiveAccountInfo();

Nunca uses I como
prefijo para Interfaces

// DON'T
public interface IPerson;

// DO
public interface Person;

Nunca uses Impl
como sufijo para implementaciones

// DON'T
public class ListImpl implements List;

// DO
public class ArrayList implements List;
public class LinkedList implements List;
public class CopyOnWriteArrayList implements List;

Usa nombres que
sean pronunciables

// DON'T
private Date genymdhms;

// DO
private Date generationTimeStamp;

// DON'T
// Is it Control or Central?
private String cntrl;

Comentarios

El código debe ser
la primera línea
de documentacióm

Los comentarios se vuelven obsoletos rápidamente, usualmente no
son actualizados

Comentarios benignos

  • Javadoc-style
  • Notificaciones Legales
  • TODO

Los comentarios deben
ser reemplazados por funciones con un buen nombre y usar comentarios Javadoc para explicar sutilezas que en código
son más difíciles

Comentar código y no borrarlo es confuso,
los programas de control de versiones hacen
fácil resucitarlos 

Funciones

Funciones

  • Pequeñas - Un nivel de abstracción
  • Hacer una sola cosa
  • No tener repetición

Es fácil ponerse de
acuerdo en el qué
pero no en el cómo

public class PrimeChecker {
  private static final int MAX_PRIME = 1_000_000;
  private boolean[] sieve;

  public PrimeChecker() {
    this.sieve = new boolean[MAX_PRIME];
    Arrays.fill(sieve, true);
    sieve[0] = false;
    sieve[1] = false;
    for (int i = 2; i * i < MAX_PRIME; ++i) {
      if (sieve[i]) {
        for (int j = i * i; j < MAX_PRIME; j += i) {
          sieve[j] = false;
        }
      }
    }
  }

  public boolean isPrime(int n) {
    return sieve[n];
  }
}

¿Las funciones son pequeñas? ¿Sus instrucciones están
al mismo nivel
de abstracción? 

this.sieve = new boolean[MAX_PRIME];
inicializarCriba(MAX_PRIME);

* * *

private void initSieve(int MAX_PRIME) {
   this.sieve = new boolean[MAX_PRIME];
}

En vez de

Usemos

Arrays.fill(criba, true);
uncrossAllNumbers();

* * *

private void uncrossAllNumbers() {
  Arrays.fill(sieve, true);
}

En vez de

Usemos

sieve[0] = false;
sieve[1] = false;
crossObviousNumbers();

* * *

private void cross(int n) {
  sieve[n] = false;
}

private void crossObviousNumbers(int n) {
  cross(0);
  cross(1);
}

En vez de

Usemos

for (int i = 2; i * i < MAX_PRIME; ++i)
crossMultiplesOfKnownPrimes();

* * *

private void crossMultiplesOfKnownPrimes() {
  for (int i = 2; i * i < MAX_PRIME; ++i) {
    if (sieve[i]) {
      for (int j = i * i; j < MAX_PRIME; j += i) {
        sieve[j] = false;
      } 
    }
  }
}

En vez de

Usemos

public PrimeChecker() {
  this.sieve = new boolean[MAX_PRIME];
  Arrays.fill(sieve, true);
  sieve[0] = false;
  sieve[1] = false;
  for (int i = 2; i * i < MAX_PRIME; ++i) {
    if (sieve[i]) {
      for (int j = i * i; j < MAX_PRIME; j += i) {
        sieve[j] = false;
      }
    }
  }
}
public PrimeChecker() {
  initSieve();
  crossObviousNumbers();
  crossMultiplesOfKnownPrimes();
}

Pasamos de

A esto

for (int i = 2; i * i < MAX_PRIME; ++i)
for (int n = 2; hasMutipleNotCrossed(n); ++n)

* * *

private boolean hasMutipleNotCrossed(int n) {
  return n * n < MAX_PRIME;
}

En vez de

Usemos

if (sieve[i])
if (isPrime(n))

En vez de

Usemos

for (int j = i * i; j < MAX_PRIME; j += i) {
  sieve[j] = false;
}
crossMutiplesOf(n);

* * *

private void crossMultiplesOf(int n) {
  for (int j = n * n; j < MAX_PRIME; j += n) {
    sieve[j] = false;
  }
}

En vez de

Usemos

for (int j = i * i; j < MAX_PRIME; j += i)
for (int x = firstPossiblyUncrossMultiple(n); 
    x < MAX_PRIME; x = nextMultiple(x, n))

* * *

public int firstPossiblyUncrossMultiple(int n) {
  return n * n;
}

public int nextMultiple(int x, int n) {
  return x + n;
}

En vez de

Usemos

sieve[j] = false;
cross(x);

En vez de

Usemos

La versión limpia

public PrimeChecker() {
  initSieve();
  crossObviousNumbers();
  crossMultiplesOfKnownPrimes();
}

private void crossObviousNumbers() {
  cross(0);
  cross(1);
}

private void crossMultiplesOfKnownPrimes() {
  for (int n = 2; hasOnePossibleUncrossMultiple(n); ++n) {
    if (isPrime(n)) {
      crossMultiplesOf(n);  
    }
  }
}
private void crossMultiplesOf(int n) {
  for (int x = firstPossiblyUncrossMultiple(n); 
      x < MAX_PRIME; x = nextMultiple(x, n)) {
    cross(x);
  }
}

public boolean hasOnePossibleUncrossMultiple(int n) {
  return n * n < MAX_PRIME;
}

public int firstPossiblyUncrossMultiple(int n) {
  return n * n;
}

public int nextMultiple(int x, int n) {
  return x + n;
}
private void initSieve(int MAX_PRIME) {
  this.sieve = new boolean[MAX_PRIME];
}

private void uncrossAllNumbers() {
  Arrays.fill(sieve, true);
}

private void cross(int n) {
  sieve[n] = false;
}

public boolean isPrime(int n) {
  return sieve[n];
}

Para hacer el código más legible, escribir más código

Escribir métodos pequeños ya no tiene una
penalidad significativa
en el desempeño
de un programa

¿Y qué tan fácil es detectar la repetición en el código?

Vamos a hacer funciones para imprimir figuras así:

***
***
***


  *
 ***
*****
public static String square(int size) {
   StringBuilder builder = new StringBuilder();
   for (int row = 1; row <= size; ++row) {
      for (int column = 1; column <= size; ++column) {
         builder.append('*');
      }
      builder.append(String.format("%n"));
   }
   return builder.toString();
}

public static String pyramid(int size) {
  int totalSpaces = size - 1;
  int totalChars = 1;
  for (int row = 1; row <= size; ++row) {
    for (int space = 1; space <= totalSpaces; ++space) {
      builder.append(' ');
    }
    for (int character = 1; character <= totalChars; ++character) {
      builder.append('*');
    }
    builder.append(String.format("%n"));
    --totalSpaces;
    totalCharacters += 2;
  }
}

¿Hay repetición?

private static final String CHARACTER = '*';
private static final String SPACE = ' ';

Primero eliminemos
los números mágicos

private static String makeString(char c, int size) {
  StringBuilder builder = new StringBuilder();
  for (int n = 1; n < size; ++n) {
    builder.append(c);
  }
  return builder.toString();
}

Extraemos la funcionalidad
de hacer un String usando
un caracter varias veces

public static String square(int size) {
   StringBuilder builder = new StringBuilder();
   for (int row = 1; row <= size; ++row) {
      builder.append(makeString('*'), size)
             .append(String.format("%n"));
   }
   return builder.toString();
}

public static String pyramid(int size) {
  int totalSpaces = size - 1;
  int totalChars = 1;
  for (int row = 1; row <= size; ++row) {
    builder.append(makeString(' '), totalSpaces)
           .append(makeString('*'), totalChars)
           .append(String.format("%n"));
    --totalSpaces;
    totalCharacters += 2;
  }
}

En general para imprimir una figura, por cada hilera imprimimos un número de espacios y luego un número de caracteres

El número de espacios y
el número de caracteres cambia cada hilera

private static String makeFigure(int size, int spaces, 
    int characters, int modifierSpaces, int modifierCharacters) {
  StringBuilder builder = new StringBuilder();
  for (int row= 1; row <= size; ++row) {
    builder.append(makeString(spaces, SPACE))
	   .append(makeString(characters, CHARACTER))
	   .append(String.format("%n"));
    spaces += modifierSpaces;
    characters += modifierCharacters;
  }
  return builder.toString();
}

¿Y ahora qué es un cuadrado y una pirámide?

public static String square(int size) {
   return makeFigure(size, 0, size, 0, 0);
}

public static String pyramid(int size) {
   return makeFigure(size, size - 1, 1, -1, 2);
}

Parámetros

  • Evitar más de 3
  • Agruparlos en Objetos
  • Crear Nuevos Tipos de Datos
// DON'T
public Circle(double x, double y, double radius)
// DO
public Circle(Point center, double radius)
// DON'T
public void execute(String query)
// DO
public void execute(Query query)

Error Handling

  • Prefer Exceptions to Error Codes
  • Extract Try Blocks
  • Extract Handling is One Thing
public void delete(Page page) {
  try {
    doDelete(page);
  } catch (AppException e) {
    logger.log(e);
  }
}

private void doDelete(Page page) throws AppException {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name);
}

Límites

Al usar librerías externas,
es importante blindarnos
si después
decidimos cambiarlas

Podemos hacer
un wrapper que además nos ayuda a mejorar la expresividad del código

Si en algún momento decidimos cambiar la librería externa solo hay 1 lugar en nuestro código para cambiar: el wrapper

Pruebas Unitarias

Pruebas Unitarias

  • Pruebas Unitarias son código Fist-Citizen 
  • Si es posible que sean one-liners
  • Evita condicionales, solo usa ciclos simples
  • Un assert por prueba

¿Cómo escribir
Clean Code?

Nadie escribe Clean Code en la primera versión

Código

  • Make it work
  • Make it right
  • Make it fast

La regla del Boy Scout:
"Has push the código que esté más limpio de cómo estaba cuando hiciste pull"

Evita las "ventanas rotas"

Q&A

Clean Code

By Carlos Obregón

Clean Code

Un manual de la Artesanía de Software Ágil

  • 1,609