Malos
Hábitos que deberíamos Erradicar

Seamos incluyentes

Historia de
la vida real

¿Juguete para
Niño o Niña?

¿A un niña no le puede gustar Transformers?

¿A un niño no le puede gustar My Little Pony?

¿Qué tiene que ver con la industria?

Tenemos una
deuda muy grande en inclusión

...especialmente con las mujeres

¡Pensemos más en cómo podemos ser parte de la solución y no parte del problema!

Métodos largos

"Todos" sabemos
que no debemos
escribir métodos largos

public HttpResponse doResourceBundle(StaplerRequest request) {
  String baseName = request.getParameter("baseName");

  if (baseName == null) {
    return HttpResponses.errorJSON("Mandatory parameter 'baseName' not specified.");
  }

  String language = request.getParameter("language");
  String country = request.getParameter("country");
  String variant = request.getParameter("variant");
  if (language != null) {
    String[] languageTokens = language.split("-|_");
    language = languageTokens[0];
    if (country == null && languageTokens.length > 1) {
      country = languageTokens[1];
      if (variant == null && languageTokens.length > 2) {
        variant = languageTokens[2];
      }
    }
  }
  try {
    Locale locale = request.getLocale();
    if (language != null && country != null && variant != null) {
      locale = new Locale(language, country, variant);
    } else if (language != null && country != null) {
      locale = new Locale(language, country);
    } else if (language != null) {
      locale = new Locale(language);
    }
    return HttpResponses.okJSON(ResourceBundleUtil.getBundle(baseName, locale));
  } catch (Exception e) {
    return HttpResponses.errorJSON(e.getMessage());
  }
}

¡Ese código es de una corporación grande!

public ArrayList<T> breadthFirstSearch (T source, T destination) {
  ArrayList<T> bfsPath = new ArrayList<>();
  Set<T> visited = new HashSet<>();
  ArrayList<T> queue = new ArrayList<>();
  queue.add(source);
  bfsPath.add(source);
  visited.add(source);
  int flag = 0;
  while (! queue.isEmpty()) {
    source = queue.get(0);
    queue.remove(0);
    ArrayList<T> temp = new ArrayList<>();
    if (adj.containsKey(source) && adj.get(source).size() > 0) {
      temp.addAll(adj.get(source));
    }
    for (int i = 0; i < temp.size(); i++) {
      if (! visited.contains(temp.get(i))) {
        bfsPath.add(temp.get(i));
        if (temp.get(i).equals(destination)) {
          flag = 1;
          break;
        }
        queue.add(temp.get(i));
        visited.add(temp.get(i));
      }
    }
    if (flag == 1) {
      break;
    }
  }
  if (flag == 0) {
    return null;
  }
  return bfsPath;
}

¡Ciertamente código educativo no
debería ser así!

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;
}

¡Cómo industria, somos malísimos escribiendo métodos "cortos"!

Métodos Cortos

  • Tienen menos errores
  • Son más fáciles de probar
  • Son más fáciles de reutilizar
  • El código resultante
    es probablemente más legible

Tips sencillos

Todo lo que vaya
dentro de un condicional, podría ser una función

Todo lo que vaya
dentro de un ciclo,
podría ser una función

Todo lo que vaya
dentro de un try,
podría ser una función

¿Se puede ir
demasiado lejos?

boolean esMúltiplo(int a, int b) {
   return a % b == 0;
}

boolean esPar(int n) {
   return esMúltiplo(n, 2);
}

boolean esImpar(int n) {
   return !esPar(n);
}
boolean esBisciesto(int año) {
  return esMúltiplo(año, 4) && 
          (!esMúltiplo(año, 100) || 
            esMúltiplo(año, 400));
}

Si dentro de un método, varias líneas se usan
para inicializar una sola variable, todo este código podría ser una función

public String foo() {
   Map<String, String> properties = getProperties();
   String bar = properties.get("fizzbuzz");
   FooBar fooBar = new FooBar(bar);
   Baz baz = fooBar.getBaz();
   return baz.obtenerUnString();
}

En vez de

public String foo() {
   Baz baz = unBaz();
   return baz.obtenerUnString();
}

private Baz unBaz() {
   Map<String, String> properties = getProperties();
   String bar = properties.get("fizzbuzz");
   FooBar fooBar = new FooBar(bar);
   return fooBar.getBaz();
}

Escribir

Se incrementa exponencialmente si
hay más de una variable

Escribir métodos cortos
es importante así no los escribamos nosotros...

¡Después que el código copiado funciona,
hay que refactorizarlo!

¡Dejemos los métodos largos!

No documentar
lo suficiente

¿Esto suena familiar?

"Carlos está de vacaciones ni idea cómo funciona eso"

"¡Carlos amaneció enfermo!
Él es el que sabe
cómo se hace eso"

"Carlos hizo esa funcionalidad pero
él ya no trabaja
en la compañía"

"¡No tengo ni p*** idea de qué fue lo que yo hice"

Documentar
evita problemas; salva
productos / compañías

Diferentes Tipos

  • Código Limpio
  • Pruebas Unitarias
  • Java Docs
  • Escrita tipo Confluence
/**
 * Indica si un año es bisciesto o no.
 * 
 * Un año bisciesto es múltiplo de 4 y si
 * es múltiplo de 100 también debe serlo de 400.
 * 
 * El año 1000 es múltiplo de 4 y de 100, 
 * por lo tanto no es bisciesto.
 * El año 2000 es múltiplo de 4 y de 100 
 * pero también de 400 por lo tanto es bisciesto.
 * El año 1996 es múltiplo de 4 y no de 100, 
 * por lo tanto es bisciesto. 
 * 
 * @param año un número natural mayor que 1 
 * @return true si el año es bisciesto, 
 * false en caso contrario.
 */
public static boolean esBisciesto(int año) {
  if (año < 1) {
    throw new IllegalArgumentException("Año no puede < 1");
  }
  return esMúltiplo(año, 4) && 
         (!esMúltiplo(año, 100) || 
           esMúltiplo(año, 400));
}
@Test
public void elAño1996PorSerMúltiploDe4YNoDe100EsBisciesto() {
  assertThat(Fechas.esBisciesto(1996), is(true));
}

@Test
public void elAñoMilPorSerMúltiploDe4YDe100NoEsBisciesto() {
  assertThat(Fechas.esBisciesto(1000), is(false));
}

@Test
public void elAñoDosMilPorSerMúltiplosDe4yDe400SiEsBisciestos() {
  assertThat(Fechas.esBisciesto(2000), is(true));
}

En una Wiki podemos dar una vista de alto vuelo de las decisiones y consideraciones de una funcionalidad

Documentar es un poco "mamón", no es "código"
y toma tiempo

Pero el daño de no
hacerlo es muchísimo
más grave que cualquiera de esas excusas

No todo tipo de funcionalidad requiere
el mismo nivel de documentación

¡Empecemos a documentar, más!

Mantente en lo
alto de tu juego

¿Java tiene
pass-by-reference?

void duplicar(int n) {
   n *= 2;
}
int n = 1;
duplicar(n);
System.out.println(n); // ???
class N {
   int valor = 1;
}

void duplicar(N x) {
   n.x *= 2;
}

N x = new N();
System.out.println(x);
N x = new N();
System.out.println(x.valor); // ???
void duplicar(N x) {
   x.valor *= 2;
}

Ni Java, ni JS tienen
pass-by-reference

...pero usan Referencias para manejar variables no-primitivas

¿Qué tanto sabes sobre la JVM?

¿Qué tanto sabes de Java 8, 9, 10 y 11?

¿Cuánto lees?

...libros, blogs, "conferencias", etc.

¿Qué responsabilidad tenemos con nuestro proyecto?

¿Qué responsabilidad tenemos con
nuestra empresa?

¿Qué responsabilidad tenemos con
nuestra industria?

¿Qué responsabilidad tenemos con
nuestros mismos?

No olvidemos el escándalo de VW

¡Vamos a ser la
mejor versión de nosotros mismos!

NULL

¿Qué es NULL?

NULL es el valor de
una referencia, que no refiere aún a un valor

Hoy en día estamos de acuerdo que uno no debe declarar una variable si no sabemos qué valor darle

int x; // mejor espera a 
       // poderle dar un valor

No hay ninguna buena razón para inicializar una variable en NULL

String s = null;
if (/* una condición */) {
  s = /* algo */;
} else {
  // mucho código
  s = /* algo más */;
}

Excusa #1

String s = getS();

* * *

private String getS() {
  if (/* una condición */) {
    return /* algo */;
  } else {
    // mucho código
    return /* algo más */;
  }
}

Solución

String s = null;
for (int veces = 1; veces <= total: ++veces) {
   s = cosas[i];
   // código que usa s
}

Excusa #2

for (int veces = 1; veces <= total: ++veces) {
   String s = cosas[i];
   // código que usa s
}

Solución

El compilador optimiza el código

¡No necesitamos
inicializar a NULL!

¿Para qué más

usamos NULL?

Retornamos NULL
para indicar la ausencia
de un valor

Pero NULL nunca fue pensado para ser usado así

¿Qué tan malo puede
ser retornar NULL?

Es muy fácil tratar
una referencia NULL
como si no lo fuera

"Soluciones"

  • Operador Elvis
  • Null Coalescing
  • Anotaciones como @NonNull

esas "soluciones"
distraen de lo esencial...

¡La mejor manera de
evitar problemas con
NULL es nunca usarlo!

"Mi experiencia es que
si sigues una regla
estricta de nunca retornar null, todo tu código se vuelve más limpio y seguro para los usuarios"
-- Stephen Colebourne

¿Qué retornar?

  • Optional
  • Colecciones vacías
  • Null-Objects

Sólo si no podemos cumplir ninguna de esas opciones, entonces retornamos null pero limitamos su alcance

¡Sabemos que NPE es el error del billón de dólares ¿vamos a seguir usándolo?!

No hacer
parámetros configurables

¿Suena familiar?

"Tenemos que hacer todo el software de nuevo porque las tarifas del cobro de parqueadero cambiaron"

"Esta funcionalidad no la podemos probar por que el endpoint está quemado"

Lo único constante
en el desarrollo de
software es el cambio

Vale la pena preguntarnos
¿qué costo tendría hacer
un cambio? ¿qué tan probable es que cambie?

¡Archivos al rescate!

¡Todos los cambios
hechos en un archivo
no necesitan recompilar!

¡Los archivos pueden ser modificados sin necesidad de saber programar!

Usa i18n así no
"esperes" usarla

¡Siempre escribe código pensando
en el cambio!

Clases
mutables
sin razón

Date date = new Date();
foo(date);
System.out.println(date);

¿Qué fecha se imprime?

Sin conocer la implementación de foo()
es imposible saberlo

¡A veces la implementación no la podemos conocer!
¡A veces no la
podemos cambiar!

La cantidad de errores
en la industria por culpa
de mutabilidad no
deseada debe ser
cercana al de los NPE's

Así como los miembros
de una función deben
ser lo menos visible
que sea posible

Las clases deben ser lo menos mutables posibles

En Java hay 2 niveles

  • Inmutabilidad Débil
  • Inmutabilidad Fuerte

Inmutabilidad Débil

  • Atributos Final
  • Ningún método "mutator"
  • Copia de los parámetros
    mutables en el constructor
  • Copia de los atributos
    mutables en "accesors"

Inmutabilidad Fuerte

  • Inmutabilidad Débil
  • Imposibilidad de crear subtipos:
    Clase Final o Todos los Constructores Private

¿Y qué tal JS?

¿Por qué no lo hacemos?

La mutabilidad
es por defecto;
mi lenguaje es
muy "verboso"

Java en una futura versión agregará "value types"

Mientras tanto...

¡Incrementemos
el uso de clases inmutables!

No hacer
Code Review

Code Review: pedir a otros miembros del equipo que revisen nuestro código

Ventajas

  • Asegura que todo el
    código tenga el mismo estilo
  • Incrementa la
    calidad del código
  • Evita bugs
  • Incrementa el conocimiento
    del código en el equipo
  • Es una manera de aprendizaje

"Ese no es mi trabajo", "¿Cómo voy a criticar a alguien?", etc.

En un equipo maduro, la calidad del código es responsabilidad de todos

Así su equipo no utilice formalmente Code Reviews no significa que usted
no pueda pedirlos

Hacerlo a la ligera es
peor que no hacerlo

¡Saquémosle el máximo provecho a los Code Review!

No pedir
ayuda a tiempo

Un caso de la vida real

Alguien perdió todo
un día porque no
entendía por que código similar a este no compilaba

HttpServlet miServlet = new HttpServlet();
error: HttpServlet is abstract; cannot be instantiated

¿Por qué tenemos miedo a preguntar? ¿Tenemos miedo de "exponernos"?

No saber algo es aceptable, pero es inaceptable no hacer nada para cambiarlo

Un buen líder sabe
que el tiempo haciendo "mentoring" es tiempo
bien invertido

¡Dejemos de
pensar en el "yo"
y pensemos más
en el "nosotros"!

KISS

Keep It Simple
& Straightforward

"Lo bueno, si breve
¡dos veces bueno!"

La codificación
siempre debería ser
un proceso iterativo

¡TDD ayuda,
pero TDD no
es necesario!

Siempre empecemos
por hacer lo más
sencillo posible

Agregamos nueva funcionalidad, poco a poco

Escribir Código

  • Haz que funcione
  • Haz que sea limpio
  • Haz que sea rápido

YAGNI: You Aren't
Gonna Need It 

¡Siempre codificar iterativamente!

No usar más
de un return

Los puristas de la programación abogan
una función debe
tener solo un return

En teoría eso está muy bien, pero si hacemos código más frágil
entonces no está bien

public T foo() {
   T t = null;
   if (/* algo */) {
      t = ...; // El caso trivial
   } else {
      // mucho código
      t = ...;
   }
   return t;
}

Ese código está bien, siempre y cuando el
caso trivial con
el tiempo no mute

public T foo() {
   T t = null;
   if (/* algo */) {
      t = ...; // El caso trivial
      // Alguien pone código aquí
      // modifica el valor de T,
      // introduciendo bugs
   } else {
      // mucho código
      t = ...;
   }
   return t;
}

¡return al rescate!

public T foo() {
   if (/* !algo */) {
      return ...;
   } else {
      // varias líneas
      // de código
      // para calcular
      // un valor de retorno
      return ...;
   }
}

La intención del if es mucho más clara ¿no?

"Say what you mean,
mean what you say!"

El primo hermano del código culposo...

public T foo() {
   T t = null;
   try {
      // código
      t = ...;
   } catch (FiFaiFuException e) {
      t = ...; // Valor por defecto
   }
   return t;
}

¡Este es aún más

fácil de romper!

public T foo() {
   try {
      // código
      t = ...;
      return t;
   } catch (FiFaiFuException e) {
      return ...; // Valor por defecto
   }
}

Algunas herramientas de estilo se quejan cuando
hay "muchos" return

Métodos Cortos
+ Muchos Return
= Buen Código

¡Que lo puristas
no nos quite
lo prácticos!

Código Spaghetti
en Try / Catch

Si un código se ve "raro",
es muy, muy probable que tenga bugs, sea difícil de modificar y de probar

El código Spaghetti usualmente tiene flujos
de ejecución que
no son obvios

¡El código Spaghetti en
un Try / Catch es
mil veces peor
por que maneja errores!

"Quis custodiet
ipsos custodes?"

¿Quién maneja
los errores del código
que maneja los errores?

Código Limpio

  • No debe haber nada antes del try
  • No debe haber nada
    después del último catch
  • Las sentencias del try deben
    ser triviales o invocar a un método
public T foo(/* parámetros */) {
  try {
    return doFoo(/* (¿otros?) parámetros */);
  } catch(UnaExcepción e | OtraExcepción e1) {
    // manejo de ambas excepciones
  } catch(AlgunaExcepción e) {
    // manejo de esta excepción
  }
}

private T doFoo(/* some argmuments */) throws AnException, 
      OtherException, SomeException {
  // Clean Code!
  Bar bar = foo.do(); // lanza UnaExcepción
  bar.baz(); // lanza OtraException
  if (bar.isBlitz()) {
    bar.platz(); // lanza AlgunaException
  }
  return bar.doo();
}

¿Qué pasa si tengo que usar una variable
dentro del catch?

public void foo() {
   T t = ...;
   try {
      // lógica del método
      // puede lanzar una excepción
   } catch (MiExcepción e) {
      // t sólo es visible si 
      // está por fuera del try
      LOGGER.info("Algo fallo con {}", t);
   }
}

Podemos crear un método donde t sea un parámetro

public void foo() {
   T t = ...;
   algoPeligroso(t);
}

private void algoPeligroso(T t) {
   try {
      // uso t, posiblemente lanza
   } catch (MiExcepción e) {
      LOGGER.info("Algo fallo con {}", t);
   }
}

¡El código más limpio debe ser el que maneja errores!

Lanzar Exception Atrapar Exception

En Java hay 2 tipos
de excepciones:
checked y unchecked

Las unchecked evidencian errores de código.
No debería ser atrapadas. ¡Nunca!

¡Las solución a una excepción unchecked es cambiar el código!

Excepciones Unchecked

  • NullPointerException
  • IndexOutOfBoundsException
  • IllegalArgumentException
  • etc.

Pero todas las excpeciones, checked o unchecked, comparten un ancestro común: Exception

Si yo atrapo Exception, atrapo todo incluso lo que no debería ser atrapado

Estoy seguro que el 95%
de los errores en producción con NPE se deben a un catch Exception

Si yo lanzo Exception, alguien tiene que
atrapar Exception

Usualmente la gente
lanza y atrapa Exception por pereza

¡Lanza solo que puedes lanzar
y atrapa solo
lo que lanzas!

No darle la importancia que merecen los errores

¡Causa #1 de terminaciones inesperadas de software!

Hacemos mal manejo
de errores porque
no probamos los
flujos de errores

¿De quién es
la responsabilidad
de hacer software
de la mejor calidad?

¡TODOS!

Usamos pruebas
unitarias, usamos
pruebas manuales, hablamos con
los testers, etc.

A veces cuando un error inesperado sucede, preferimos "silenciarlo"

Tratar de manejar un error que "nunca debió suceder" silencia el error

¿Por qué
silenciamos errores?

Por miedo a qué
lleguen a producción

Los errores silenciados, simplemente se manifiestan de otra manera

Manejo de Errores

  • Validar Parámetros (IllegalArgument, IllegalState)
  • Usar AssertionError

¿Si una variable es
null, qué significa?

public String getDescripción(Map<String, String> data, 
       String id) {
   if (data == null) {
      return "";
   }
   String descripción = data.get(id);
   if (descripción == null) {
      return "";
   }
   return descripción;
}

Un usuario que no
puede completar su compra, seguramente
irá a comprar a otro lugar

public String getDescripción(Map<String, String> data, 
       String id) { 
   // Puede lanzar NPE
   String descripción = data.get(id);
   if (StringUtil.isEmpty(descripción)) {
      throw new 
        AssertionError("Descripción vacía para id " + id);
   }
   return descripción;
}

El desarrollador, el tester,
el PM, el PO, el cliente... ¡alguien lo va a ver antes que un usuario!

Si el usuario lo ve
primero, va a ser muy sencillo encontrar el error

¡Y ya que probamos nuestros errores, escojamos los
mensajes de error!

También en
las pruebas unitarias

¡Hagamos
software resilient!

Usar i para todas las variables de ciclos

Hay 2 tipos de ciclos

  • Repetimos un código n veces
  • Generamos una secuencia

Hay un caso especial del segundo caso: generar
los índices válidos
de un arreglo

¡Para todo lo demás, hay mejores nombres!

for (int veces = 1; veces <= total; ++veces) {
   // para ciclos que repiten instrucciones
}
for (int n = 2; n <= total; n += 2) {
   // para ciclos que repiten instrucciones
}

Para secuencias pueden usar n, x o algo
más apropiado del
dominio como par

¡Cuiden el nombre de todas
sus variables!

No aprender inglés

No hay ningún problema que sea
de alguien más

¡Quejarse!

¡Empecemos a producir cambios!

Gracias

@gaijinco

Q&A

Malos hábitos que deberíamos erradicar

By Carlos Obregón

Malos hábitos que deberíamos erradicar

  • 1,591