Código Limpio

¿Quién es el
mejor programador?

Programador Sobresaliente

  • ¿Aquel que conoce de memoria
    el API de un lenguaje?
  • ¿Aquel que conoce comportamientos
    raros de un lenguaje?
  • ¿Aquel que conoce muchísimos
    frameworks o lenguajes?
  • ¿Aquel que escribe el
    código muy rápido?
  • ¡¿Aquel que escribe código
    que nadie más entiende?!

Mi respuesta

"Alguien que se preocupa por el código que escribe"

Y no hay preocupación
más importante
que hacerlo limpio

¿Qué es
código limpio?

Código Limpio

Código que se puede entender sin tener
a la persona que lo escribió al lado
(puedo ser yo hace unas semanas)

Código Limpio

  • Fácil de entender
  • Menos propenso a errores
  • Fácil de probar
  • Fácil de extender
  • Fácil de corregir

¡Una empresa puede acabarse, por no
escribir código limpio!

Nombres

Usa nombres expresivos

int d; // días de la semana
int díasDeLaSemana;

NO escribas

Mejor escribe

void copiar(char[] a1, char[] a2)
void copiar(char[] fuente, char[] destino)

NO escribas

Mejor escribe

No uses abreviaturas

int cntrl;
int control;
int central;

NO escribas

Mejor escribe

Usa nombres fáciles
de pronunciar / buscar

Usa constantes en vez
de números mágicos

for (int i = 0; i < 34; ++i) {
  s += (t[i] * 4) / 5;
}
final int NÚMERO_TAREAS = 34;
final int NÚMERO_REAL_DÍAS = 4;
final int DÍAS_LABORALES = 5;
for (int i = 0; i < NÚMERO_TAREAS; ++i) {
  suma += (tarifa[i] * NÚMERO_REAL_DÍAS) / DÍAS_LABORALES;
}

NO escribas

Mejor escribe

Comentarios

Antes nos parecía
muy importante
escribir comentarios

Ahora sabemos que es
muy frecuente que el código y el comentario
no estén de acuerdo

// calculamos el mínimo común múltiplo de a y b
int mcm = a * b;

¡El código es la
mejor documentación!

Criba de Eratóstenes

  • Supongamos una lista infinita
    de números naturales
  • Tachamos los números
    que obviamente no son primos
  • Nos paramos en el primer número
    no recorrido que no haya sido tachado
    y lo encerramos en un círculo
    indicando que son primos
  • Luego tachamos todos los
    múltiplos de ese número.
  • Repetimos los 2 últimos pasos

Criba de Eratóstenes

public class CribaDeEratóstenes {

  private static final int MAX_PRIMO = 1_000_000;
  private boolean[] primo = new boolean[MAX_PRIMO + 1];

  public CribaDeEratóstenes() {
    Arrays.fill(primo, true);
    primo[0] = false;
    primo[1] = false;
    for (int i = 2; i * i <= MAX_PRIMO; ++i) {
      if (primo[i]) {
        for (int j = i * i; j <= MAX_PRIMO; j += i) {
          primo[j] = false;
        }
      }
    }
  }

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

¿Podemos hacerlo
más legible?

public class CribaDeEratóstenes {

  private static final int MAX_PRIMO = 1_000_000;
  private boolean[] primo = new boolean[MAX_PRIMO + 1];

  public CribaDeEratóstenes() {
    destacharTodosLosNúmeros();
    tacharNúmerosObvios();
    tacharMúltiplosDePrimosConocidos();
  }

  public boolean esPrimo(int n) {
    return noEstáTachado(n);
  }

  // más código
}
private void tacharNúmerosObvios() {
  tachar(0);
  tachar(1);
}

private void tacharMúltiplosDePrimosConocidos() {
  for (int n = 2; 
        menorMúltiploPosiblementeNoTachado(n) <= MAX_PRIMO; ++n) {
    if (esPrimo(n)) {
      tacharMúltiplosDe(n);
    }
  }
}

private void tacharMúltiplosDe(int n) {
  for (int x = menorMúltiploPosiblementeNoTachado(n); 
      x <= MAX_PRIMO; x = siguienteMúltiplo(x, n)) {
    tachar(x);
  }
}
private static int menorMúltiploPosiblementeNoTachado(int n) {
  return n * n;
}

private static int siguienteMúltiplo(int x, int n) {
  return x + n;
}

Hasta aquí, todos
los métodos están
escritos como prosa

Y por último los métodos de más baja abstracción
los que revelan detalles
de implementación

private void destacharTodosLosNúmeros() {
  Arrays.fill(primo, true);
}

private void tachar(int n) {
  primo[n] = false;
}

private boolean noEstáTachado(int n) {
  return primo[n];
}

Indirección

Introducimos métodos,
con buenos nombres,
para agregar un grado
de indirección

Un método permite ponerle un nombre a un conjunto de instrucciones. Que además ayude a que sean reutilizadas es incidental

Métodos

Métodos

  • Cortos
  • Hacer solo una cosa
  • No tener repetición

Métodos Cortos

  • Entre 5 y 10 líneas
  • Más que líneas piensa en responsabilidades
  • Recuerda los grados de indirección

¡Esta es rara vez la manera en que codificamos!

public static boolean solucionar(int [][]tablero) {
  int x=0,y=0;
  boolean encontrado = false;
  for(x = 0;x < 9; x ++) {
    for(y = 0;y < 9; y++) {
      if(tablero[x][y] == 0) {
        encontrado = true;
        break;
      }
    }
    if( encontrado ) break;
  }
  if(!encontrado) return true;
		
  boolean digitos[] = new boolean[11];
  for(int i = 0; i < 9; i++) {
    digitos[tablero[x][i]] = true;
    digitos[tablero[i][y]] = true;
  }
  int bx = 3 * (x/3), by = 3 * (y/3);
  for(int i =0;i<3;i++)
    for(int j = 0; j < 3; j++)
      digitos[tablero[bx+i][by+j]] = true;
		
  for(int i = 1 ; i <= 9; i++) {
    if(!digitos[i] ) {
      tablero[x][y] = i;
      if(solucionar(tablero))
        return true;
      tablero[x][y] = 0;
    }
  }
  return false;
}

Tomado de Internet

Saquemos responsabilidades puntuales a nuevos métodos

Pero primero introduzcamos
Tipos de Datos

public class Sudoku {

  private int[][] tablero;

  public Sudoku(int[][] tablero) {
    this.tablero = tablero;
  }

  private class Coordenada {
    int hilera;
    int columna;
    boolean vacía;
    private Coordenada(int hilera, int columna, boolean vacía) {
      this.hilera = hilera;
      this.columna = columna;
      this.vacía = vacía;
    }
    Coordenada(int hilera, int columna) {
      this(hilera, columna, false);
    }
    Coordenada() {
      this(-1, -1, true);
    }
  }
  // más código
}

Ahora saquemos responsabilidades a
nuevos métodos

int x=0,y=0;
boolean encontrado = false;
for(x = 0;x < 9; x ++) {
  for(y = 0;y < 9; y++) {
    if(tablero[x][y] == 0) {
      encontrado = true;
      break;
    }
  }
  if( encontrado ) break;
}
if(!encontrado) return true;

Antes

private Coordenada buscarSiguienteCoordenada() {
  for (int i = 0; i < 9; ++i) {
    for (int j = 0; j < 9; ++j) {
      if (tablero[i][j] == 0) {
        return new Coordenada(i, j);
      }
    }
  }
  return new Coordenada();
}

Después

int x = celda.fila;
int y = celda.columna;
boolean digitos[] = new boolean[11];
for(int i = 0; i < 9; i++) {
  digitos[tablero[x][i]] = true;
  digitos[tablero[i][y]] = true;
}
int bx = 3 * (x/3), by = 3 * (y/3);
for(int i =0;i<3;i++)
  for(int j = 0; j < 3; j++)
    digitos[tablero[bx+i][by+j]] = true;

Antes

private List<Integer> digitosPosibles(Coordenada coordenada) {
  Set<Integer> usados = new HashSet<>();
  usados.addAll(usadosHorizontalmente(coordenada));
  usados.addAll(usadosVerticalmente(coordenada));
  usados.addAll(usadosEnCuadrante(coordenada));
  List<Integer> dígitos = 
    new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
  dígitos.removeAll(usados);
  return dígitos;
}

Después

private Set<Integer> usadosHorizontalmente(Coordenada coordenada) {
  Set<Integer> usados = new HashSet<>();
  for (int i = 0; i < tablero.length; ++i) {
    usados.add(tablero[coordenada.hilera][i]);
  }
  return usados;
}

private Set<Integer> usadosVerticalmente(Coordenada coordenada) {
  Set<Integer> usados = new HashSet<>();
  for (int i = 0; i < tablero.length; ++i) {
    usados.add(tablero[i][coordenada.columna]);
  }
  return usados;
}

private Set<Integer> usadosEnCuadrante(Coordenada celda) {
  Set<Integer> usados = new HashSet<>();
  int x = 3 * (celda.hilera / 3);
  int y = 3 * (celda.columna / 3);
  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 3; ++j) {
      usados.add(tablero[x + i][y + i]);
    }
  }
  return usados;
}

Después

private boolean backtrack() {
  Coordenada siguiente = buscarSiguienteCoordenada();
  if (siguiente.vacía) {
    return true;
  }

  List<Integer> dígitos = dígitosPosibles(siguiente);
  for (int n : dígitos) {
    tablero[siguiente.hilera][siguiente.columna] = n;
    if (backtrack()) {
      return true;
    }
    tablero[siguiente.hilera][siguiente.columna] = 0;
  }
  return false;
}

Tipos de Datos

  • Hacen el código más legible
  • Ayudan a reducir la
    lista de parámetros
  • Hacen que ciertos
    errores sean imposibles
public Circle(double x, double y, double radius)
public Circle(Punto centro, double radius)

En vez de 

Mejor

public void execute(String query)
public void execute(Query query)

En vez de 

Mejor

La mejor forma de hacer el código más legible, es escribir más métodos

Críticas al
Código Limpio

Críticas

  • "Es escriben más métodos,
    lo que impacta el desempeño"
  • "Los métodos de una sola
    línea son un exabruptos"
  • "¿Para entender más el código,
    tengo que leer más código?"

Es cierto que escribir código limpio tiene un costo, pero no es tan alto como se piensa

Legibilidad es mucho
más importante
que Desempeño

¿Cómo escribir código limpio?

Escribir Código

  1. Haz que el código funcione
  2. Haz que el código sea limpio
  3. Haz que el código sea rápido

¡Gracias!

@gaijinco

https://slides.com/gaijinco/codigo-limpio-itc

Q&A

Código Limpio

By Carlos Obregón

Código Limpio

  • 1,529