Lo que me hubiera gustado saber
antes de trabajar
como programador
en la industria
del desarrollo
de software

1. Ser un muy buen programador no es cuestión de genios

Practicar,
Practicar,
Practicar

20 Horas para pasar
la "molestia" inicial,
10.000 Horas
para ser "Experto"

"No soy un muy
buen programador.
Soy simplemente un
buen programador con muy buenos hábitos"
-- Kent Beck

"No soy un muy
buen programador.
Simplemente leo mucho"
-- Yuji Kiriki

¿Qué leer?

¡Aprende un
nuevo lenguaje!

2. Aprende la
cultura del
lenguaje que usas

Para Hacktoberfest decidí colaborar en: iiitv/algos

Problema de las N-Reinas

private static boolean solveNQueen(int[][] board, int column) {
  int boardLength = board[0].length;
  if (column >= boardLength)
    return true;

  for (int i = 0; i < boardLength; ++i) {
    if (isQueenSafe(board, i, column)) {
      board[i][column] = 1;
      if (solveNQueen(board, column + 1))
        return true;
      else
        board[i][column] = 0;
      }
    }
    return false;
}

Lo que había antes

private static boolean isQueenSafe(int[][] board, int row, int column) {
  int i;
  int j;
  int boardLength = board[0].length;
  for (i = 0; i < column; ++i)
    if (board[row][i] == 1)
      return false;

  for (i = row, j = column; i >= 0 && j >= 0; --i, --j)
    if (board[i][j] == 1)
      return false;

  for (i = row, j = column; j >= 0 && i < boardLength; ++i, --j)
    if (board[i][j]==1)
      return false;

  return true;
}

Lo que había antes

public static void main(String[] args) {
  int boardLength = 8;
  int[][] board = new int[boardLength][boardLength];
  if (!solveNQueen(board, 0)) {
    System.out.println("No solution");
  } else {
    for (int i = 0; i < boardLength; ++i) {
      System.out.println(Arrays.toString(board[i]));
    }
  }
}

Lo que había antes

¿En qué lenguaje estaba escrito ese código?

"Reconozco al león por su pisada" -- Isaac Newton

La mayoría podía estar
escrito en C o cualquiera
de sus derivados

Cuando Java no es Java

  • No hay tipos de datos
  • No hay uso de toString()
  • No hay uso de interfaces
    bases como Iterable
public static void main(String[] args) {
  NQueenProblem queens = new NQueenProblem(5);
  for (Solution s : queens) {
    System.out.println(s);
  }
}
public static class Solution {

  private final List<Integer> board;
  private static char QUEEN = 'Q';
  private static char EMPTY_SQUARE = '.';
  private static String NEW_LINE = String.format("%n");

  private Solution(final List<Integer> board) {
    this.board = board;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < board.size(); ++i) {
      for (int j = 0; j < board.size(); ++j) {
        if (board.get(i) == j) {
          builder.append(QUEEN);
        } else {
          builder.append(EMPTY_SQUARE);
        }
      }
      builder.append(NEW_LINE);
    }
    return builder.toString();
  }
}
private class NQueenIterator implements Iterator<Solution> {
  // más código
  @Override
  public boolean hasNext() {
    while (!queue.isEmpty()) {
      List<Integer> current = queue.removeLast();
      if (current.size() == size) {
        this.next = new Solution(current);
        return true;
      }
      int column = current.size();
      for (int row = 0; row < size; ++row) {
        if (isValid(current, column, row)) {
          List<Integer> next = new ArrayList<>(current);
          next.add(row);
          queue.addLast(next);
        }
      }
    }
    return false;
  }

  @Override
  public Solution next() {
    return next;
  }
}
public class NQueenProblem 
      implements Iterable<NQueenProblem.Solution> {

    private final int size;

    public NQueenProblem(final int n) {
        this.size = n;
    }

    @Override
    public Iterator<Solution> iterator() {
        return new NQueenIterator();
    }

    public static class Solution {
      // implementación
    }

    private class NQueenIterator 
        implements Iterator<Solution> {
      // implementación
    }
}

No solo se trata de escribir código que funcione,
es bueno entender lo
que busca el lenguaje 

¡En Internet hay muchísimo código que no es Java!

No porque esté en
Internet, o en un curso
(pago o gratuito) significa que sea buen código

Libros

  • Effective Java
  • Clean Code
  • Head First Design Patterns

¡Parte de la cultura
de un lenguaje son
sus últimas versiones!

Trata de aprenderlas y estar pendiente de las discusiones sobre ellas

¡Esto aplica también
a las herramientas, FW
o librerías que sean
parte de tus proyectos!

3. Tipos de Datos

¡La programación
debería tratar de hacer nuevos tipos de datos!

Tipos de Datos

  • Son más expresivos
  • Tienden a tener menos errores
  • Hacen que ciertos errores sean imposibles
  • Hace el código más fácil de probar
public void execute(String query)
public void execute(Query query)

En vez de

usar

Una vez que la idea
de Tipos de Datos
es clara, todos los demás conceptos son más sencillos de entender

p.j. Clases Internas

Solution y NQueenIterator
son clases
internas de NQueen

Clases Internas

  • Tienen acceso al estado de la
    clase externa (p.j. Builder)
  • Se puede tener control para
    que solo la clase externa cree
    objetos de la clase interna (p.j. Solution)

Aprende todos
las características
del lenguaje que usas

Cuando vayas a hacer un diseño, necesitas conocer la herramienta idónea

"Si la única herramienta que tienes es un martillo,
es tentador tratar todo como una puntilla."
-- Abraham Maslow

4. Aprendiendo
a Aprender

¿Cómo pasar un
exámen difícil?

  • Hago un esfuerzo todos los días
    leyendo el material escrito
  • Me trasnocho el día anterior
    para tener todo más freso
  • Estudio varios días,
    sin transnocharme

"Modos" del cerebro

  • Focused
  • Difussed

Focused

  • Como su nombre implica,
    estamos concentrados aprendiendo
  • El cerebro está muy activo,
    así que los pensamientos tienen
    a relacionarse con conceptos familiares
  • Es muy difícil tener patrones innovadores

Diffused

  • Típicamente sucede cuando
    no estamos pensando
    activamente en un problema
  • Hay "más espacio" para que los
    conceptos se asocien con nuevos conceptos,
    lo que facilita aprender nuevas cosas
  • Es una manera de ver "la gran imagen"

El cerebro solo puede
estar en un modo

Para aprender,
la mejor manera es
ir de un modo a otro

Sin descansar, el cerebro
no puede estar en modo diffused. ¡La repetición espacial es la clave!

La primera vez que
trato de aprender sobre
un tema complejo,
usualmente,
no entiendo nada.

Ni la segunda,
ni la tercera,
ni la cuarta...

Y usualmente un día sin estar pensando en eso, todo hace "clic"

"No soy un muy buen programador. Solo un
buen programador con muy buenos hábitos"

20 horas para pasar
la molestia inicial
del aprendizaje

Encuentras difícil concentrarte para
hacer una tarea
¿Cómo lo manejas?

  • Me obligo a iniciar la tarea, con la esperanza de que después de arrancar, el tedio se vaya
  • Simplemente te paras y vuelves en una hora para tener una perspectiva nueva
  • Simplemente te paras y vuelves cuando te sientas listo para continuar

Cuando haces algo
que no quieres, el cerebro lo asocia con "dolor"

Pero cuando arrancas
a hacerlo, el cerebro
se siente intrigado por
la tarea y la sensación
de desagrado se va

Para vencer la procrastinación una opción es la técnica Pomodoro

Para crear hábitos
de aprendizaje

  • Duerme bien
  • Haz ejercicio
  • Únete a un grupo de estudio

Para aprender, es muy importante usar el modo diffused del cerebro

Para aprender, es
muy importante tener
una buena oxigenación
en el cuerpo

Para aprender, es
muy importante activar para zonas del cerebro

Para aprender mejor: duerme bien, haz ejercicio y estudia con otros

¡También toma notas
a mano y dibuja!

5. NULL

¿La mejor forma de evitar NullPointerException?
Solo usa NULL como un detalle de implementación

"Soluciones" para NULL

  • @NonNull y @Nullable
  • Nullable Type: T?, T!, etc.
  • Elvis operator ?:

¡Todas son curitas para controlar una hemorragia!

No existe ningún código que dependa de usar NULL

Siempre hay una alternativa mejor que usar NULL

"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

Nunca uses NULL

6. Inmutabilidad

Las clases que
diseñes deberían ser inmutables por defecto

Entender un proceso
que usa objetos
inmutables es
mucho más sencillo

Tener objetos inmutables elimina un montón de errores en los programas

Escribir una clase inmutable es un "poco" más intricado, pero no tanto

7. ¿De quién es
la responsabilidad
de XXX?

¡TODOS!

Nada en mi proyecto es el problema de alguien más 

Nada en mi compañía es el problema de alguien más 

¡No te dejes etiquetar, ve más allá de los roles!

Empowerment

  • Cuando hay un problema, ayuda
  • Cuando hay código que mejorar, propon
  • Cuando hay un proceso que no funciona, compártelo

"¡No me traigas problemas, tráeme soluciones!" -- Frances Frei

8. Código que
mi abuela
pueda entender

Clean Code es un mantra

¿Se puede escribir código que entienda alguien
que no programa?

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

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

Criba de Eratóstenes

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 void heapify() {
  int index = size;
  while (hasParent(index) 
      && greater(parentIndex(index)), index) {
    swap(parentIndex(index), index);
      index = parentIndex(index);
    }
  }
}

Min Heap

private void makeHeap() {
  swap(0, size);
  int index = 0;
  while (hasLeftChild(index)) {
    int minChildIndex = getMinChildIndex(index);
    if (less(parentIndex(index), minChildIndex) {
      return;
    }
    swap(parentIndex(index), minChildIndex);
    index = minChildIndex;
  }
}

Procura que los
métodos públicos
se lean como prosa

Si los métodos se leen como prosa, seguramente son métodos cortos

Métodos Cortos

  • Más fáciles de entender
  • Menos errores
  • Más fáciles de probar

Escribir código limpio
es un proceso iterativo

Escribir código

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

La ley del Boy Scout:
"Trata de dejar este
mundo un poco mejor
de lo que lo encontraste."
-- Robert Stephenson 

La ley del Boy Scout:
"Has push de código más limpio de como estaba cuando hiciste pull." 

9. Sacar la
cabeza de la arena

Requerimiento #1:
Si un texto tiene más
de 210 caracteres, se debe cortar y agregar "..."

public static String ellipsify(String s) {
  if (s.length() < 210) {
    return s;
  }
  return s.subtring(0, 210) + "...";
}

¿No deberíamos ser más amigables y no cortar en medio de una palabra?

Requerimiento #2:
El método ellipsify coloca los puntos suspensivos después del primer espacio después del caracter 210

public static String ellipsify(String s) {
  if (s.length() < 210) {
    return s;
  }
  int cut = s.indexOf(' ', 210);
  if (cut == -1 || cut == s.length() - 1) {
    return s;
  }
  return s.subtring(0, cut) + "...";
}

Requerimiento #3:
El argumento de ellipsify puede incluir hipertexto que no debe ser contado como caractetes

Requerimiento #4:
El argumento de ellipsify puede incluir caracteres que no debe ser contado como caractetes

¡Pensar en índices
puede llevar el código
a volverse empalagoso!

Pero los programadores usualmente piensan así.
¡Hay que sacar la
cabeza de la arena!

¡La manera funcional
de programar!

Pensemos en partir
el requerimiento en más
de una función de uso específico y componerlas para crear nuestra funcionalidad

public final class WordSplitter {

  private static final 
      Predicate<Character> IS_WHITESPACE = Character::isWhitespace;
  private static final 
      Predicate<Character> IS_NOT_WHITESPACE = isWhitespace.negate();

  private final String text;
  private Predicate<Character> predicate;
  private int i = 0;

  public WordSplitter(final String text) {
    this.text = java.util.Objects.requireNonNull(text);
    this.predicate = isNotWhitespace.test(text.charAt(0)) 
        ? isNotWhitespace 
        : isWhitespace;
  }

  // más código
}
public final class WordSplitter {
  // más código
  public boolean hasNext() {
    return i < text.length();
  }

  public String next() {
    int end = next(i);
    String chunck = text.substring(i, end);
    i = end;
    predicate = (predicate == IS_WHITESPACE
        ? IS_NOT_WHITESPACE 
        : IS_WHITESPACE);
    return chunck;
  }

  private int next(int start) {
    while (start < text.length() 
        && predicate.test(text.charAt(start))) {
      ++start;
    }
    return start;
  }
}

No siempre podemos escapar de los índices, pero nos aseguramos de poner un grado de indirección detrás de una abstracción

Algunas funciones auxiliares

public static int count(String s, char c) {
  return count(s, Character.toString(c));
}

public static int count(String s, String sub) {
  int total = 0;
  int idx = s.indexOf(sub);
  while (idx != -1) {
    ++total;
    idx = s.indexOf(sub, idx + 1);
  }
  return total;
}
private static int discountMarks(int marks) {
  return marks == 0 
      ? 0 
      : marks * 2;
}

private static int discountSpecialCharacters(int chars) {
  return chars == 0 
      ? 0 
      : chars / 2;
}

private int countChars(String s) {
  return next.length() 
      - discountMarks(count(next, "(C)")) 
      - discountMarks(count(next, "(R)")) 
      - discountSpecialCharacters(count(next, '^')) 
      - discountSpecialCharacters(count(next, '~'));
}

Juntando todo

public static String ellipsify(String text, int maxChars) {
  if (text.length() <= maxChars) {
    return text;
  }
  WordSplitter splitter = new WordSplitter(text);
  StringBuilder builder = new StringBuilder();
  int charCount = 0;
  while (charCount < maxChars && splitter.hasNext()) {
    String next = splitter.next();
    if (charCount + next.length() > maxChars) {
      break;
    }
    charCount += countChars(next);
    builder.append(next);
  }
  return builder.append(" ...").toString();
}

¿Lo mejor? Las funciones auxiliares fueron después reutilizadas en otras funcionalidades

Y el código resultante resultó más limpio

Siempre vale la pena tomarse un momento
y pensar en una
forma alterna de desarrollar una historia

10. Parametrizar, parametrizar, parametrizar

Requerimiento #1: En una DB jerárquica necesito buscar el primer nodo que sea de un tipo específico

Requerimiento #2:
Buscar el primer nodo que sea de un tipo específico
que contengan una propiedad específica

Requerimiento #3:
Buscar el primer nodo
que tengan un
nombre específico

¡Requerimiento #n!
Lo único constante
en el desarrollo de
software es el cambio

Una manera simple
de estar preparado al cambio es parametrizar nuestras funciones

¡El API de los lenguajes
lo usan todo el tiempo!

public static void sort(List<T> list, Comparator<T> c)
public Thread(Runnable target)
public void addActionListener(ActionListener l)

Y no es un consejo
nuevo, el patrón de
diseño Component
busca esa ventaja

public static Optional<Resource> getFirstChild(Resource resource, 
      Predicate<Resource> keep) {
  for (Resource child : resource.getChildren()) {
    if (keep.test(child)) {
      return Optional.of(child);
    }
  }
  return Optional.empty();
}

¡La solución, parametrizada!

Con una sola función abarcamos infinitos
casos de uso, pero
solo necesitaríamos
una prueba unitaria

Utiliza las ventajas de parametrizar los métodos

11. Las pruebas unitarias son documentación

Las pruebas unitarias
son "first-class citizens"
en nuestro código

Todas las buenas prácticas que aplicamos a nuestro código, deben aplicarse a las pruebas unitarias

¡Pero las pruebas unitarias también tienen sus propias buenas prácticas!

Regla cardinal: el código de las pruebas unitarias debe ser trivial de entender

En Construcción
de Software se enseña sobre el uso de las especificaciones

En Java usamos Javadocs

Pero Javadocs como cualquier comentario
es susceptible de desinformación

assertThat(isPalindrome(""), is(true));
assertThat(isPalindrome("a"), is(true));
assertThat(isPalindrome("ab"), is(false));
assertThat(isPalindrome("radar"), is(true));
assertThat(isPalindrome("reader"), is(false));
assertThat(isPalindrome("Radar"), is(true));
assertThat(isPalindrome("A man a plan a canal Panama"), is(true));
assertThat(isPalindrome("A man, a plan, a canal - Panama"), is(true));

Pruebas Unitarias
como especificaciones

Recuerda: el código
debe ser trivial

Implementemos el
tablero del Juego del 15

public final class Tablero {

  private final int[][] tablero;
  private final int filas;
  private final int columnas;
  private Coordenada hueco;

  public Tablero() {
    this.filas = 4;
    this.columnas = 4;
    this.tablero = new int[filas][columnas];
    ordenarTablero();
    this.hueco = buscar(0);
    desordenar(); // utiliza Random
  }
  // más código
}
public final class Tablero {
  // más código

  public Tablero mover(int n) {
    if (n < 0 || n >= filas * columnas) {
      throw new IllegalArgumentException();
    }
    Tablero copia = new Tablero(this);
    Coordenada número = copia.buscar(n);
    copia.mover(número, n);
    return copia;
  }
}

¿Cómo probamos que podamos mover la ficha debajo del hueco?

Opción 1

  • Creamos un tablero
  • Llamamos al método toString
  • Hacemos "parsing" al String
    para encontrar el hueco
  • Hacemos "parsing" para hallar
    el número debajo del hueco
  • Llamamos a mover()
  • Modificamos el String de toString
    para producir el tablero resultante
    y lo usamos en el assert

¡Yo escribía código así!

Si tenemos ciclos / condicionales, entonces las pruebas unitarias no son triviales de entender

¿Otra opción?

Tablero(int[][] tablero) {
  this.tablero = tablero;
  this.columnas = tablero.length;
  this.filas = tablero[0].length;
  this.hueco = buscar(0);
}

@Override
public boolean equals(Object obj) {
  if (this == obj)
    return true;
  if (obj == null)
    return false;
  if (getClass() != obj.getClass())
    return false;
  Tablero other = (Tablero) obj;
  if (!Arrays.deepEquals(tablero, other.tablero))
    return false;
  return true;
}
@Test
public void puedoMoverLaFichaAbajoDelHueco() {
  Tablero inicial = new Tablero(new int[][] {{1, 2, 3},
                                             {4, 0, 5},
                                             {6, 7, 8}});

  Tablero siguiente = new Tablero(new int[][] {{1, 2, 3},
                                               {4, 7, 5},
                                               {6, 0, 8}});

  assertThat(inicial.mover(7), equalTo(siguiente));
}

Hacer el código fácil de probar es más importante que otras consideraciones

¡Código como Documentación!

12. KISS

KISS
Keep It Simple
and Straightforward

A la hora de programar, siempre es mejor la solución más simple

La perfección se alcanza, no cuando no hay más que agregar sino cuando no hay nada más que quitar
-- Antoine de Saint-Exupéry

YAGNI
You Aren't Gonna Need It

Resiste la tentación
de agregar cosas que
no se necesitan

Incrementa la complejidad natural del código,
de manera iterativa

Usa TDD o escribe el
código de una manera TDD

13. Vencer el
miedo a decir "no"

En general:
¡Vencer el miedo
a dar una opinión!

En muchísimas ocasiones decir "no", no tiene
ningún impacto negativo en nuestro proyecto

Es mejor decir "no"
desde el principio,
en vez de quedar mal

¡No significa que a todo tengamos que decir "no"!

14. Entender
el negocio

Entender el negocio
de nuestro cliente,
pero también
de nuestra empresa

Este entendimiento le da contexto a las decisiones de ellos y a las nuestras

Tómate el tiempo de conocer a las personas claves de las empresas

15. Vencer el síndrome del
super héroe

¿Qué es más productivo:
no escribir código por
n horas o preguntar?

¡Todos tenemos que preguntar alguna vez!

¡No tiene nada que
ver con la experiencia!

¡A veces solo el hecho
de expresar nuestra duda, es suficiente para
hallar la respuesta!

Preguntar

  • Dar un paso atrás
  • Buscar compañero de proyecto
  • Buscar compañero de trabajo
  • Buscar conocido
  • Internet

Si no tienen un proceso
de Code Review, pídanlo

Por otro lado,
un proyecto no lo hace
una sola persona:
hay que trabajar en equipo

Team Building

  • Comparte tiempo con tu equipo
  • Preocúpate por tu equipo
  • Tómate el tiempo de solucionar
    roces con tu equipo
  • Tómate el tiempo de
    conocer a tu equipo

16. El tiempo
es sagrado

¡No hay un valor más preciado para la mayoría que el tiempo!

Tiempo

  • Llega a tiempo a trabajar
  • Llega a tiempo a las reuniones
  • Organízate para no perder tiempo

En mi experiencia
hay muchas cosas para
las que "no tenía tiempo", que pude hacer
solo organizándome

La gente valora
muchísimo a las personas que son puntuales

17. Ser el peor
de tu equipo

"Siempre sé el peor integrante en cada
banda en la que estás"
-- Pat Metheny

Si tu eres el peor de
un equipo, puedes aprender algo de todos

Cuando ya no tengas nada que aprender de los demás, es probablemente hora
de cambiar de proyecto

Aprovecha la gente con la que trabajas al máximo y trata de aprender de ellos todo lo que puedas

¿Y a ustedes qué
les hubiera
gustado saber?

¡Gracias!

@gaijinco

Lo que me hubiera gustado saber antes de trabajar en la industria del desarrollo de software

By Carlos Obregón

Lo que me hubiera gustado saber antes de trabajar en la industria del desarrollo de software

  • 2,347