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,670