Aprendizajes de una coding interview

Un problema que resolver, un editor, tus coding skills y un tiempo determinado para resolverlo ⌚.

Coding

Consistía en consumir un endpoint con una petición http:// obtener de la respuesta la cantidad de edades mayores o iguales a 50 e imprimirlo en consola.

Parece que no es muy complejo no? 🤔 véamos:

Los datos de entrada 📩

El formato de la respuesta http era como el siguiente:

{"data":"key=IAsdpK, age=58, key=WdNVdi, age=64, key=jsd9zt, age=47,  
key=0Sr4C, age=68, key=CGEqo, age=76, key=IxKVQ, age=79, key=eD221, age=29,  
key=XZbHV, age=32, key=k1SN5, age=88,key=4SCsU,  age=65, key=q3saG6, age=33,  
key=MGdpf, age=13, key=Kjd6xW, age=14, key=tg2VM, age=30, key=WSnCU, age=24"}

Análisis del algoritmo 📐

Ahora que conocemos el detalle de la respuesta, el algoritmo sería algo así:

  • primero debemos hacer la petición Http y obtener la respuesta.

  • de la respuesta, separar el value del atributo data.

  • eliminar los caracteres innecesarios y dejar solo los valores de las edades (age).

  • convertirlo en un arreglo o lista de edades para operarlo.

  • iterar y filtrar las edades que sean mayor o igual a 50.

La solución 🤓 💻

Debía hacerlo con un lenguaje seleccionado, en este caso yo usé Java.

public static void main (String[] args) {
    System.setProperty("http.agent", "Chrome");

    //Utilizado para el response
    StringBuilder response = new StringBuilder();

    try {
      URL url = new URL("https://.../api/challenges/json/ages");
      try {
        // Abro conexión http
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        BufferedReader in = new BufferedReader(
          new InputStreamReader(connection.getInputStream()));

        String inputLine;

        // Obtengo la respuesta
        while ((inputLine = in.readLine()) != null) {
          response.append(inputLine);
        }

         // Separo data de value, obtengo el value, usando una expresion regular
         // Remplazo los caracteres innecesarios,
         // Separo las ages en un arreglo, las convierto en un Stream, 
         // Filtro los mayores a 50 y cuento la cantidad de ellos.
        long result = Stream.of(response.toString().split(":")[1]
        .replaceAll("(key=)[a-zA-z0-9]+,\\s(age=)|[\\\"\\s\\}]?","").split(","))
        .filter(element -> Integer.parseInt(element) >= 50).count();

        // Imprimo resultado en consola
        System.out.println(result);

      } catch (IOException ioEx) {
        System.out.println(ioEx);
      }
    } catch (MalformedURLException malEx) {
      System.out.println(malEx);
    }
  }  

observen bien el código 👀 a ver si detectan algunas mejoras.

El post envío de la solución 🧐

Bueno ese código es terrible 💀, aunque soluciona el problema planteado carece de algunas cosas :

  • para empezar he olvidado hacer el close del BufferedReader.

  • el resultado está todo en un solo paso y se hace muy difícil entender lo que hace. Carece de codigo limpio.

Aunque sea un problema específico y no muy complejo, no debemos olvidar que nuestro código habla por nosotros 🗣.

Me quedé con la idea de que no lo hice lo mejor que pude, quizás estaba nervioso o estaba más pendiente del tiempo ⌚.

Así que lo hice nuevamente con un poco más de calma.

Refactorización 🧠

Algo sencillo con lo que podemos comenzar es separar la expresión regular en una variable:

  final String UNNECESSARY_DATA = "(key=)[a-zA-z0-9]+,\\s(age=)|[\\\"\\s\\}]?";

  long result = Stream.of(response.toString().split(":")[1]
      .replaceAll(UNNECESSARY_DATA,"").split(","))      
      .filter(element -> Integer.parseInt(element) >= 50).count();

Nota: al hacer un replace con las expresión regular nos queda algo así:

"58,64,47,68,76,79,29,32,88,65,33..."  

y haciendo el split algo así:

["58","64","47","68","76","79","29","32","88","65","33"...]  

también podemos extraer el response de la HttpURLConnection en otro método:

  static String extractResponseFrom (final HttpURLConnection connection) throws IOException {

      StringBuilder response = new StringBuilder();

      BufferedReader in = new BufferedReader(
        new InputStreamReader(connection.getInputStream()));

      String inputLine;
      while ((inputLine = in.readLine()) != null) {
          response.append(inputLine);
      }

      in.close();

      return response.toString();
  }

quedando hasta ahora así:

  public static void main(String args[]) {
      try
      {
          URL url = new URL("https://.../api/challenges/json/ages");
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();

          String response = extractResponseFrom(connection);

          long result = Stream.of(response.split(":")[1]
            .replaceAll(UNNECESSARY_DATA,"").split(","))
            .filter(element -> Integer.parseInt(element) >= 50).count();

          System.out.println(result);

      }
      catch (Exception e) {
          System.out.println(e.toString());
      }
  }

Creen que se puede mejorar aún más?

Pues sí, utilicemos la programación orientada a objetos para seguir mejorándolo.

Creemos una clase que encapsule la data que devuelve el API y agreguemos algunos métodos con responsabilidad única y un truco (retornar this) para encadenar los métodos y realizar una solución más declarativa:

  static class Data {
      private static final String UNNECESSARY_DATA = "(key=)[a-zA-z0-9]+,\\s(age=)|[\\\"\\s\\}]?";
      private String data;

      Data(String value) {
          this.data = value;
      }

      Data getValue() {
          data = data.split(":")[1];
          return this;
      }

      Data retrieveAges() {
          data = data.replaceAll(UNNECESSARY_DATA, "");
          return this;
      }

      Stream<String> toStream() {
          return Stream.of(data.split(","));
      }
  }

Incorporemos otra pequeña mejora a la hora de filtrar las edades (ages) utilizando la interfaz de Predicados

  static class GreaterThan50 implements Predicate<String> {

      @Override
      public boolean test(String element) {
          return Integer.parseInt(element) >= 50;
      }

  }

recordar que lo que manipulamos siempre es un String, por eso la implementación de predicados hace internamente un Parse a Entero.

Solución final 🥇

Quedando la solución refactorizada de la siguiente manera:

  public static void main(String args[]) {
      try
      {
          URL url = new URL("https://.../api/challenges/json/ages");
          HttpURLConnection connection = (HttpURLConnection) url.openConnection();

          if (HttpURLConnection.HTTP_OK == connection.getResponseCode()) {
              String response = extractResponseFrom(connection);

              Predicate<String> IS_GREATER_THAN_50 = new GreaterThan50();

              long result = new Data(response).getValue().retrieveAges()
                      .toStream().filter(IS_GREATER_THAN_50).count();

              System.out.println(result);
          }
      }
      catch (Exception e) {
          System.out.println(e.toString());
      }
  }

Probablemente se pueda mejorar aún más, por ejemplo, se puede encapsular la llamada http en otro método, pero sin duda está mucho mejor que la primera versión.

📚 Aprendizajes

Hoy día usamos frameworks y/o librerias para la mayoría de las cosas que hacemos por diferentes razones y nos acostumbramos a que hacen mucho por nosotros, por ejemplo si usamos Spring y su ecosistema, Spring Data, etc. no debemos preocuparnos por abrir o cerrar una conexión a BD, hacer un commit o un rollback de una transacción.

Es importante conocer los fundamentos y como funcionan las piezas por debajo.

Palabras finales:

  • No apresurarse a enviar la solución, dar una última revisada a detalle.

  • Aplicar refactorización para que el código sea legible.

  • Utilizar los recursos que dispones (programación orientada a objetos, programación funcional, conocimientos del lenguaje) para crear una solución lo más simple y entendible posible.

  • Aplicar principios SOLID luego hablaré en otro apartado de esto.

  • Aprender de tus experiencias es fundamental para el crecimiento.

Espero que te sirva la mía y tú? también has pasado por una similar?

«Cuando te das cuenta de que has cometido un error, toma medidas inmediatas para corregirlo.»
Dalai Lama