<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Fernando Poblete Arrau on Medium]]></title>
        <description><![CDATA[Stories by Fernando Poblete Arrau on Medium]]></description>
        <link>https://medium.com/@ferpobletea?source=rss-eaca11e8e235------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*IfjBsZNN1VfWHXwxjOC2Zw.png</url>
            <title>Stories by Fernando Poblete Arrau on Medium</title>
            <link>https://medium.com/@ferpobletea?source=rss-eaca11e8e235------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 17 May 2026 03:11:29 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ferpobletea/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Encapsulamiento]]></title>
            <link>https://medium.com/@ferpobletea/encapsulamiento-e27f80fe828b?source=rss-eaca11e8e235------2</link>
            <guid isPermaLink="false">https://medium.com/p/e27f80fe828b</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[object-oriented-design]]></category>
            <dc:creator><![CDATA[Fernando Poblete Arrau]]></dc:creator>
            <pubDate>Thu, 13 Jun 2024 22:47:03 GMT</pubDate>
            <atom:updated>2024-06-13T22:47:03.756Z</atom:updated>
            <content:encoded><![CDATA[<p>Me ha costado más de lo que esperaba escribir por acá pero ahora, por fin, traigo este segundo artículo. Como mencioné anteriormente, los contenidos que iré publicando tienen que ver con falencias que he observado trabajando sobre diversas bases de código, distintos clientes y con distintas tecnologías, lo que me hace pensar que pueden ser bastante transversales.</p><p>Puede que para algunos no sea un contenido tan novedoso pero son temas a los que, en el mundo de la IA, Cloud y tecnologías novedosas apareciendo cada día, no se les presta demasiada atención.</p><p>En este artículo me referiré al principio de <strong>Encapsulamiento</strong>, o a la falta de éste ya que, si bien es un concepto fundamental de la programación, en ocasiones no se respeta, o quizás no se entiende.</p><p>Según <a href="https://es.wikipedia.org/wiki/Encapsulamiento_(inform%C3%A1tica)">Wikipedia</a>: “encapsulación (o encapsulamiento) se refiere a la agrupación de datos con los métodos que operan en esos datos (…). La encapsulación se utiliza para ocultar los valores o el estado de un objeto(…), evitando el acceso directo a ellos (…).</p><p>Para ir aterrizando la idea, un ejemplo muy sencillo en código Java.</p><pre>public class Cuadrado {<br>  private double lado;<br>  <br>  public Cuadrado(double lado) {<br>    this.lado = lado;<br>  }<br>  <br>  public double obtenerArea() {<br>    return this.lado * this.lado;<br>  }<br><br>  public double obtenerPerimetro() {<br>    return this.lado * 4;<br>  }<br>}</pre><p>La clase Cuadrado agrupa los datos (el lado del cuadrado), con los métodos que operan esos datos (obtenerArea y obtenerPerimetro).</p><p>Ahora bien, ¿a qué me refiero cuando digo que el principio de Encapsulamiento no se respeta? Para responder, usaremos el siguiente ejemplo, en código Java.</p><pre>public class Pizza {<br>  private Variedad variedad;<br>  private List&lt;Ingrediente&gt; ingredientes;<br>  private Tamano tamano; // Tamano es un ENUM Individual, Mediana, Familiar<br>  private double precio;<br><br>  public Pizza() {<br><br>  }<br><br>  public Pizza(Variedad variedad, List&lt;Ingrediente&gt; ingredientes, Tamano tamano, double precio) {<br>    this.variedad = variedad;<br>    this.tamano = tamano;<br>    this.precio = precio;<br>    this.ingredientes = ingredientes;<br>  }<br><br>  public List&lt;String&gt; getIngredientes() { <br>    return this.ingredientes; <br>  }<br><br>  public void setIngredientes(List&lt;Ingrediente&gt; ingredientes) { <br>   this.ingredientes = ingredientes; <br>  }<br><br>  public Variedad getVariedad() { <br>    return this.variedad;<br>  }<br><br>  public void setVariedad(Variedad variedad) { <br>    this.variedad = variedad; <br>  }<br><br>  public Tamano getTamano() { <br>    return this.tamano;<br>  }<br><br>  public void setTamano(Tamano tamano) { <br>    this.tamano = tamano;<br>  }<br><br>  public double getPrecio() {<br>    return this.precio;<br>  }<br><br>  public void setPrecio(double precio) {<br>    this.precio = precio;<br>  }<br><br>}</pre><p>Esta clase es muy sencilla pero refleja un estilo de programación muy común. Atributos privados, métodos públicos, un par de setter/getter para cada atributo y dos constructores, uno vacío y otro con todos los parámetros posibles.</p><p>Ahora bien, ¿qué problemas hay?</p><h3>1. Implementar siempre Setter y Getter</h3><p>El primero y más típico de los ‘vicios’ que he podido observar en este aspecto, corresponde a implementar un setter y un getter para todos los atributos de una clase. Siempre, para todos los atributos, para todas las clases del programa.</p><p>Si bien acceder a los atributos a través de métodos es una forma de encapsulamiento, al tener un getter y setter para cada atributo, la clase queda <strong>casi</strong> tan expuesta como si sus atributos fueran públicos. Este ejemplo en particular tiene dos problemas: de usabilidad (al programador que la utiliza) y de integridad de datos.</p><h3>1. 1 Usabilidad</h3><p>Con la implementación anterior, quien quiera utilizar la clase anterior tendrá mucho trabajo que hacer. Un ejemplo de cómo utilizar la clase Pizza podría ser el siguiente.</p><pre>public static void main (String[] args) {<br>   Pizza pizza = new Pizza();<br>   pizza.setVariedad(new Variedad(Variedad.Hawaiana));<br>   List&lt;Ingrediente&gt; ingredientes = new ArrayList&lt;&gt;( <br>     Arrays.asList( <br>       new Ingrediente(“Queso”),<br>       new Ingrediente(&quot;Jamón&quot;),<br>       new Ingrediente(&quot;Piña&quot;)<br>     )<br>   );<br>   pizza.setIngredientes(ingredientes);<br>   pizza.setTamano(Tamano.MEDIANA);<br>   double precio = /* alguna logica para calcular el precio */<br>   pizza.setPrecio(precio);<br>   System.out.println(&quot;Precio de la pizza: $&quot; + pizza.getPrecio());<br>}</pre><p>Es decir, quien use la clase debe:</p><ul><li>instanciar un objeto de tipo Variedad</li><li>instanciar varios objetos del tipo Ingrediente</li><li>construir una lista con estos últimos</li><li>conocer la regla de cálculo de precios</li></ul><p>Son muchos detalles. Rompe la definición misma de encapsulamiento, al inicio de este artículo.</p><h3>1.2 Integridad</h3><p>Con el método <strong>setPrecio</strong>, es posible manipular el precio después de construido el objeto, sin considerar los ingredientes o tamaño de la pizza. Por otra parte (aunque esto podría depender de las reglas de negocio de la pizzería), los ingredientes no deberían setearse directamente, sino que autoasignarse según la variedad seleccionada (‘Española’, ‘Hawaiana’, etc.). Nada impide que alguien haga algo así.</p><pre>public static void main (String[] args) {<br>   Pizza pizza = new Pizza();<br>   pizza.setVariedad(new Variedad(Variedad.Hawaiana));<br>   List&lt;Ingrediente&gt; ingredientes = new ArrayList&lt;&gt;( <br>     Arrays.asList( <br>       new Ingrediente(“Queso”),<br>       new Ingrediente(&quot;Jamón&quot;),<br>       new Ingrediente(&quot;Piña&quot;),<br>     )<br>   );<br>   pizza.setIngredientes(ingredientes);<br>   pizza.setTamano(Tamano.MEDIANA);<br>   double precio = /* alguna logica para calcular el precio */<br>   pizza.setPrecio(precio);<br>   System.out.println(&quot;Precio de la pizza: $&quot; + pizza.getPrecio());<br>   pizza.setTamano(Tamano.FAMILIAR);                // pizza familiar al precio de mediana<br>   ingredientes.push(new Ingrediente(“Pepperoni”);  // pepperoni gratis<br>   pizza.setIngredientes(ingredientes);<br>}</pre><p><strong>¿Está mal entonces agregar getters y setters? No, lo que está mal es hacerlo por defecto, casi automáticamente. Lo correcto es agregarlo para los atributos que realmente lo requieren.</strong></p><p>Y ojo que si usas alguna librería o herramienta para autogeneración de métodos (como Java +<a href="https://projectlombok.org/">Lombok</a>, o C# con <a href="https://learn.microsoft.com/es-es/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties">propiedades autoimplementadas</a>), es aún más fácil caer en el error.</p><p>La siguiente mala práctica tiene relación con la implementación de constructores.</p><h3>2. Implementar siempre un constructor vacío y/o uno con todos los atributos de la clase</h3><p>En el ejemplo de la pizza, junto con los getters y setters se incluían dos constructores. El primero vacío, y el segundo con todos los atributos de la clase como parámetros del constructor. Este tipo de implementación también es muy común, pero también rompe el encapsulamiento de la clase de la misma forma que lo hacen los setters.</p><p>Por ejemplo, si a la clase pizza le sacamos los setters pero mantenemos el constructor con los parámetros quedaría así.</p><pre>public class Pizza {<br>  private Variedad variedad;<br>  private List&lt;Ingrediente&gt; ingredientes;<br>  private Tamano tamano; // Tamano es un ENUM Individual, Mediana, Familiar<br>  private double precio;<br><br>  public Pizza() {<br><br>  }<br><br>  public Pizza(Variedad variedad, List&lt;Ingrediente&gt; ingredientes, Tamano tamano, double precio) {<br>    this.variedad = variedad;<br>    this.tamano = tamano;<br>    this.precio = precio;<br>    this.ingredientes = ingredientes;<br>  }<br><br>  public List&lt;String&gt; getIngredientes() { <br>    return this.ingredientes; <br>  }<br><br>  public Variedad getVariedad() { <br>    return this.variedad;<br>  }<br><br>  public Tamano getTamano() { <br>    return this.tamano;<br>  }<br><br>  public double getPrecio() {<br>    return this.precio;<br>  }<br><br>}</pre><p>Es verdad que ya no es posible inyectar valores al objeto después de construido, pero sí es posible construirlo con cualquier valor en sus atributos. El problema de integridad es prácticamente el mismo.</p><p>Y en cuanto a usabilidad tampoco mejora.</p><pre>public static void main (String[] args) {<br>   Variedad variedad = new Variedad(Variedad.Hawaiana);<br>   List&lt;Ingrediente&gt; ingredientes = new ArrayList&lt;&gt;( <br>     Arrays.asList( <br>       new Ingrediente(“Queso”),<br>       new Ingrediente(&quot;Jamón&quot;),<br>       new Ingrediente(&quot;Piña&quot;)<br>     )<br>   );<br>   Tamano tamano = Tamano.MEDIANA;<br>   double precio = /* alguna logica para calcular el precio */<br>   Pizza pizza = new Pizza(variedad, ingredientes, tamano, precio);<br>   System.out.println(&quot;Precio de la pizza: $&quot; + pizza.getPrecio());<br>}</pre><p>En mi experiencia, rara vez un constructor va a necesitar realmente todos sus atributos en su construcción (salvo un objeto inmutable). Por otra parte, un constructor vacío no ayuda mucho. Sin embargo, es muy común ver la implementación de este par de constructores.</p><p>Lo que corresponde, entonces, es implementar constructores que tengan sentido para la naturaleza de la clase y de la aplicación.</p><h3>Mejoras en la implementación</h3><p>Mejorando la implementación de getters, setters y constructores, la clase Pizza podría quedar de la siguiente forma.</p><pre>public class Pizza {<br>  private Variedad variedad;<br>  private List&lt;Ingrediente&gt; ingredientes;<br>  private Tamano tamano; // Tamano es un ENUM Individual, Mediana, Familiar<br>  private double precio;<br><br>  // Definimos que el tamano y variedad deben pasarse al construir un objeto<br>  public Pizza(Tamano tamano, Variedad variedad) { <br>    this.tamano = tamano;<br>    this.variedad = variedad;<br>    // supongamos un método que retorna los ingredientes segun <br>    // la variedad seleccionada<br>    this.ingredientes = variedad.obtenerIngredientesSegunVariedad(); <br>    this.precio = calcularPrecio();<br>  }<br><br>  public List&lt;String&gt; getIngredientes() { <br>    return this.ingredientes; <br>  }<br><br>  public Variedad getVariedad() { <br>    return this.variedad;<br>  }<br><br>  public Tamano getTamano() { <br>    return this.tamano;<br>  }<br><br>  public double getPrecio() {<br>    return this.precio;<br>  }<br><br>  private double calcularPrecio() {<br>     double precio = /* calculos en base a los ingredientes y tamaño */<br>     return precio;<br>  }<br><br>}</pre><p>Con estos cambios estamos protegiendo de mejor forma los datos de la clase y además dando una mayor claridad de cómo se debe consumir esta clase.</p><p>Para el consumidor quedaría así.</p><pre>public static void main (String[] args) {<br>   Pizza pizza = new Pizza(Tamano.FAMILIAR, new Variedad(Variedad.Hawaiana));<br>   System.out.println(&quot;Precio de la pizza: $&quot; + pizza.getPrecio());<br>} // solo dos lineas!!</pre><p>¿Y si queremos una Hawaiana con Pepperoni? Creamos un método y lo utilizamos después de la creación del objeto.</p><pre>public void agregarIngrediente(Ingrediente ingrediente) {<br>    this.ingredientes.add(ingrediente); // ¿ves un posible error acá? sigue leyendo<br>    this.precio = calcularPrecio(); // se actualiza el precio cada vez que se agrega <br>                                    // un ingrediente extra<br>  }</pre><pre>public static void main (String[] args) {<br>   Pizza pizza = new Pizza(Tamano.FAMILIAR, new Variedad(Variedad.Hawaiana));<br>   pizza.agregarIngrediente(new Ingrediente(&quot;Pepperoni&quot;));<br>   System.out.println(&quot;Precio de la pizza: $&quot; + pizza.getPrecio());<br>}</pre><h3>Resumiendo …</h3><p>La implementación de getters, setters y constructores debe respetar la naturaleza de la clase, exponiendo sólo lo que realmente necesite ser expuesto. En este sentido, mientras menos detalles sean expuestos, mejor. Y mientras más expuestos quedan los detalles de implementación de una clase, más fácilmente llegaremos a un código altamente acoplado.</p><h3>Y Resumiendo el resumen …</h3><p>Encapsulamiento va más allá de <strong>‘atributos privados, métodos públicos’.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e27f80fe828b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Manejo de Excepciones]]></title>
            <link>https://medium.com/@ferpobletea/manejo-de-excepciones-d7e236e8cbd4?source=rss-eaca11e8e235------2</link>
            <guid isPermaLink="false">https://medium.com/p/d7e236e8cbd4</guid>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[exception-handling]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-design]]></category>
            <dc:creator><![CDATA[Fernando Poblete Arrau]]></dc:creator>
            <pubDate>Mon, 04 Mar 2024 14:22:45 GMT</pubDate>
            <atom:updated>2024-03-04T14:23:45.689Z</atom:updated>
            <content:encoded><![CDATA[<p>Como anuncié <a href="https://medium.com/@ferpobletea/hallazgos-de-un-dev-freelance-5fcb926a0262">antes</a> , empiezo con ésta una serie de publicaciones acerca de algunas falencias recurrentes a nivel de código que he encontrado en estos últimos casi 5 años en mi carrera como independiente, trabajando para distintos tipos de clientes, industrias y con distintas tecnologías.</p><p>Mi objetivo es abordar temas técnicos, simples y concretos, que espero puedan servir de ayuda.</p><p>El primer tema que abordaré será el manejo de excepciones.</p><h3>¿Qué es el manejo de excepciones?</h3><p>El manejo de excepciones es una técnica de programación que permite controlar los errores ocasionados durante la ejecución de un programa (fuente: <a href="https://es.wikipedia.org/wiki/Manejo_de_excepciones">Wikipedia</a>). Y complementando con su definición en inglés (también <a href="https://en.wikipedia.org/wiki/Exception_handling">Wikipedia</a>), permite manejar el caso Se trata de patrones — o antipatrones — de una pre condición no satisfecha.</p><h3>¿Por qué es importante?</h3><p>Tener un correcto manejo de excepciones nos protege de comportamientos inesperados hacia nuestros usuarios, nuestro negocio o hacia nosotros mismos como desarrolladores.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Icf1tm96ucngK7Zx" /></figure><blockquote>(<a href="https://www.reddit.com/r/mildlyinteresting/comments/cizsyt/blue_screen_of_death_on_this_billboard/">imagen</a> de mildlyinteresting/ en reddit)</blockquote><p>Sin un correcto manejo de excepciones, podríamos tener inconsistencias a nivel de datos y/o flujos de la aplicación. Esto nos puede llevar a mostrar al usuario errores técnicos incomprensibles, dañando además nuestra imagen.</p><h3>La implementación</h3><p>A continuación usaré como ejemplo un proyecto sencillo escrito en Java y Spring Boot, pero poniendo foco en conceptos que se pueden aplicar de igual forma a otros frameworks o lenguajes.</p><p>Primero, partimos de una implementación <strong>sin manejo de excepciones</strong>. El siguiente código corresponde a un controlador Rest que responde a una petición HTTP GET.</p><pre>@RestController<br>public class CustomersController {<br><br>   private CustomerService service;<br><br>   @GetMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;Customer&gt; getCustomer(@PathVariable Long id) {<br>       Customer customer = service.getCustomer(id);<br>       return ResponseEntity.ok(customer);<br>   }<br>}<br><br>@Service<br>public class CustomerService {<br><br>   CustomerRepository repository;<br><br>   public Customer getCustomer(long id) {<br>       return repository.findById(id).get();<br>   }<br>}</pre><p>Tomando el parámetro id desde la URL (<a href="https://url-server/customers/1">https://url-server/customers/1</a>), el controlador obtiene un objeto Customer desde el CustomerService. Finalmente, retorna un estado HTTP Ok y los atributos del objeto Customer en el cuerpo de la respuesta, de la siguiente forma:</p><pre>{<br>   &quot;id&quot;: 1,<br>   &quot;firstName&quot;: &quot;John&quot;,<br>   &quot;lastName&quot;: &quot;Doe&quot;<br>}</pre><p>Pero ¿qué pasa cuando hacemos una llamada por un id no existente? En este caso la respuesta que obtenemos será:</p><pre>{<br>   &quot;timestamp&quot;: &quot;2024-02-07T15:01:13.087+00:00&quot;,<br>   &quot;status&quot;: 500,<br>   &quot;error&quot;: &quot;Internal Server Error&quot;,<br>   &quot;trace&quot;: &quot;java.util.NoSuchElementException: No value present<br>            at java.base/java.util.Optional.get(Optional.java:143)<br>            at edu.fpoblete.exceptionhandlingdemo.services.CustomerService.getCustomer(CustomerService.java:19)<br>            at edu.fpoblete.exceptionhandlingdemo.controllers.CustomersController.getCustomer(CustomersController.java:25)<br>            at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)<br>            at java.base/java.lang.reflect.Method.invoke(Method.java:580)<br>            at org.springframework.web.method.support.<br> …<br>}</pre><p>En este caso, la excepción <strong><em>java.util.NoSuchElementException</em></strong> indica que tratamos de acceder a un elemento inexistente. Más específicamente, el stacktrace nos indica el lugar de ocurrencia del error:</p><pre>at edu.fpoblete.exceptionhandlingdemo.services.CustomerService<br>  .getCustomer(CustomerService.java:19)<br>at edu.fpoblete.exceptionhandlingdemo.controllers.CustomersController<br>  .getCustomer(CustomersController.java:25)</pre><p>Es decir, dentro del método getCustomer en la clase CustomerService:</p><pre>   public Customer getCustomer(long id) {<br>       return repository.findById(id).get();<br>   }</pre><p>Esta implementación conlleva -al menos- tres problemas:</p><ol><li><strong>No hay manejo interno:</strong> el código no hace ningún manejo que nos ayude, como desarrolladores, a identificar qué pasó ni cómo podríamos resolverlo.</li><li><strong>Respuesta de la API</strong>: nuestra API no retorna un código o mensaje de error claro. Nuestro consumidor (una aplicación web, un proceso de backend u otro servicio/API) no sabrá qué pasó ni qué hacer al respecto.</li><li><strong>Exposición de detalles de implementación:</strong> a través del stacktrace del error, estamos revelando qué lenguaje utilizamos, algunas librerías, el nombre de nuestro controlador y servicio. Esto podría favorecer a un posible atacante.</li></ol><p>A continuación implementaré paso a paso un manejo de excepciones para el código antes expuesto. Este paso a paso refleja distintas realidades de proyectos en los que he trabajado, y busca ayudar a quienes puedan estar teniendo dificultades diseñando o implementando el manejo de excepciones en sus proyectos.</p><p><strong>#1 Capturar y relanzar la excepción</strong></p><p>Como primer paso capturamos la excepción específica que ya conocemos y dejamos un log que nos ayudará a detectar y corregir el problema. Luego, relanzamos la excepción.</p><pre>public Customer getCustomer(long id) {<br>   try {<br>       return repository.findById(id).get();<br>   } catch (NoSuchElementException e) {<br>       logger.error(&quot;Error getting customer with id &quot; + id);<br>       throw e;<br>   }<br>}</pre><p>Resolvimos el primero de los problemas expuestos arriba (manejo interno), pero seguimos sin resolver los otros dos (respuesta de la API y exposición de detalles de implementación). Mejoremos nuestra implementación.</p><p><strong># 2 Esconder la excepción</strong></p><p>Con esta implementación, para evitar que el error llegue al usuario o consumidor, la excepción se captura y se esconde, retornando en el cuerpo un objeto vacío.</p><pre>public Customer getCustomer(long id) {<br>   try {<br>       return repository.findById(id).get();<br>   } catch (NoSuchElementException e) {<br>       logger.error(&quot;Error getting customer with id &quot; + id);<br>       return new Customer();<br>   }<br>}</pre><p>En este caso, la respuesta de nuestra API quedaría:</p><pre>{<br>   &quot;id&quot;: null,<br>   &quot;firstName&quot;: null,<br>   &quot;lastName&quot;: null<br>}</pre><p>Es decir, dejamos de enviar un código de error (junto con el stacktrace), pero aún no estamos indicando correctamente a nuestro consumidor qué fue lo que pasó.</p><p><strong># 3 Manejo ok pero con código repetido</strong></p><p>Como siguiente paso, ahora que capturamos (try/catch) y manejamos internamente el error (de momento al menos el log), lo que nos falta es dar una respuesta correcta a nuestros consumidores.</p><p>Como primera forma de resolver esto, podemos agregar también un try/catch en el controlador, pero esta vez lo que haremos será retornar un HTTP 404 notFound. Esto ya indica una respuesta clara al consumidor.</p><pre>@RestController<br>public class CustomersController {<br><br>   private CustomerService service;<br><br>   @GetMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;Customer&gt; getCustomer(@PathVariable Long id) {<br>      try {<br>          Customer customer = service.getCustomer(id);<br>          return ResponseEntity.ok(customer);<br>      } catch (NoSuchElementException e) {<br>          return ResponseEntity.notFound().build();<br>      }<br>   }<br><br>}<br><br>@Service<br>public class CustomerService {<br><br>   CustomerRepository repository;<br><br>   public Customer getCustomer(long id) {<br>      try {<br>          return repository.findById(id).get();<br>      } catch (NoSuchElementException e) {<br>          logger.error(&quot;Error getting customer with id &quot; + id);<br>          throw e;<br>      }<br>   }<br><br>}</pre><p>En este caso, la respuesta queda así:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/951/0*vpPe5HMxU3OKtnZI" /></figure><p>Con esta implementación, si bien ya estamos cubriendo los tres problemas expuestos, estamos repitiendo código entre el servicio y el controlador. Esta repetición de código aumenta aún más cuando implementamos, por ejemplo, la funcionalidad de deleteCustomer.</p><pre>@RestController<br>public class CustomersController {<br><br>   private CustomerService service;<br><br>   @GetMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;Customer&gt; getCustomer(@PathVariable Long id) {<br>      try {<br>          Customer customer = service.getCustomer(id);<br>      } catch (NoSuchElementException e) {<br>          return ResponseEntity.notFound().build();<br>      }<br>      return ResponseEntity.ok(customer);<br>   }<br><br>   @DeleteMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;String&gt; deleteCustomer(@PathVariable Long id) {<br>      try {<br>         service.deleteCustomer(id);<br>      } catch (NoSuchElementException e) {<br>          return ResponseEntity.notFound().build();<br>      }<br>      return ResponseEntity.ok().build();<br>   }<br>}<br><br>@Service<br>public class CustomerService {<br><br>   CustomerRepository repository;<br><br>   public Customer getCustomer(long id) {<br>      try {<br>          return repository.findById(id).get();<br>      } catch (NoSuchElementException e) {<br>          logger.error(&quot;Error getting customer with id &quot; + id);<br>          throw e;<br>      }<br>   }<br><br>   public void deleteCustomer(long id) {<br>      try {<br>          Customer customer = repository.findById(id).get();<br>          repository.delete(customer);           <br>      } catch (NoSuchElementException e) {<br>          logger.error(&quot;Error getting customer with id &quot; + id);<br>          throw e;<br>      }<br>   }<br>}</pre><p>Por otra parte, estamos generando un acoplamiento entre el controlador -la capa más externa de la aplicación- y una excepción de bajo nivel, como la java.util.NoSuchElementException, lo que no es deseable.</p><p>Así que vamos por el siguiente paso.</p><p><strong># 4 Refactorizando</strong></p><p>Lo que toca ahora entonces será no sólo cumplir con la parte funcional del manejo de excepciones sino también con que la implementación siga un diseño consistente y escalable.</p><p>Los cambios que haremos son:</p><ul><li>Crear una clase CustomerNotFoundException. Esta excepción nos permite en el controlador manejar excepciones de alto nivel, orientado a lógica de negocio y no a detalles de implementación.</li><li>Capturar en el servicio la NoSuchElementException pero lanzar hacia el controlador la recién creada CustomerNotFoundException.</li><li>Mover el log desde el bloque catch del servicio hacia la excepción misma. Esto permite centralizar el loggeo (u otro tipo de manejo que hagamos de la excepción) evitando repetir código, no sólo entre capas sino también entre funcionalidades. Si más adelante queremos cambiar el log por, por ejemplo, la publicación de un mensaje en una cola, tenemos que hacer el cambio en un sólo lugar.</li></ul><pre>@RestController<br>public class CustomersController {<br><br>   private CustomerService service;<br><br>   @GetMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;Customer&gt; getCustomer(@PathVariable Long id) {<br>      try {<br>          Customer customer = service.getCustomer(id);<br>      } catch (CustomerNotFoundException e) {<br>          return ResponseEntity.notFound().build();<br>      }<br>      return ResponseEntity.ok(customer);<br>   }<br><br>   @DeleteMapping(&quot;/customers/{id}&quot;)<br>   public ResponseEntity&lt;String&gt; deleteCustomer(@PathVariable Long id) {<br>      try {<br>         service.deleteCustomer(id);<br>      } catch (CustomerNotFoundException e) {<br>          return ResponseEntity.notFound().build();<br>      }<br>      return ResponseEntity.ok().build();<br>   }<br>}<br><br>@Service<br>public class CustomerService {<br><br>   CustomerRepository repository;<br><br>   public Customer getCustomer(long id) {<br>      try {<br>          return repository.findById(id).get();<br>      } catch (NoSuchElementException e) {<br>          throw new CustomerNotFoundException(id);<br>      }<br>   }<br><br>   public void deleteCustomer(long id) {<br>      try {<br>          Customer customer = repository.findById(id).get();<br>          repository.delete(customer);           <br>      } catch (NoSuchElementException e) {<br>          throw new CustomerNotFoundException(id);<br>      }<br>   }<br>}<br><br>public class CustomerNotFoundException extends Exception {<br><br>   Logger logger = new Logger();<br><br>   public CustomerNotFoundException(long id) {<br>       super(&quot;Error getting customer with id &quot; + id);<br>       logger.error(&quot;Error getting customer with id &quot; + id);<br>   }<br>}</pre><p>Con este último cambio cumplimos funcionalmente y tenemos un diseño más escalable y desacoplado.</p><p>¿Qué piensas? ¿cómo implementas tú el manejo de excepciones en tus proyectos?</p><p><em>Nota: al usar Java se podría -y sugiere- utilizar funcionalidades de la librería Optional, como ifPresent() o orElse(), pero este ejemplo preferí dejarlo lo más independiente posible del uso de librerías o frameworks.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d7e236e8cbd4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hallazgos de un DEV freelance]]></title>
            <link>https://medium.com/@ferpobletea/hallazgos-de-un-dev-freelance-5fcb926a0262?source=rss-eaca11e8e235------2</link>
            <guid isPermaLink="false">https://medium.com/p/5fcb926a0262</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-testing]]></category>
            <dc:creator><![CDATA[Fernando Poblete Arrau]]></dc:creator>
            <pubDate>Fri, 12 Jan 2024 14:34:24 GMT</pubDate>
            <atom:updated>2024-01-15T11:42:50.984Z</atom:updated>
            <content:encoded><![CDATA[<p>Estos últimos 4 años, he trabajado freelance en proyectos de desarrollos y asesorías. Para distintos tipos de clientes: pymes, startups, grandes empresas, instituciones públicas. Distintas industrias: minería, banca, logística, retail, servicio público. Y, por supuesto, con distintas tecnologías: Java / Spring Boot, Python, Net Core, Vue, React, Angular, NodeJS, PHP / Laravel, Bash e incluso Visual Basic.</p><p>Esta diversidad, si bien no me acerca a ser realmente experto en una industria o tecnología, me permite (y obliga a) a abordar y entender la programación y la ingeniería de Software de una manera amplia, más allá de las características propias de cada lenguaje o tecnología.</p><p>Desde esta vereda, la del freelance, he detectado algunos patrones de código -o antipatrones de código- comunes. Aspectos que, por las razones que sea, distintos tipos de clientes, de distintas industrias, y con distintas tecnologías, están repitiendo.</p><p>Generalmente los técnicos atribuimos esto a la falta de tiempo, al apuro, que viene desde las áreas de negocio, y que nos obliga a sacar productos en plazos poco razonables. Creo también que se habla mucho de IA, de Cloud, de frameworks, pero poco de Ingeniería de Software, de programación, pura y dura. Quizás algo de eso hay también.</p><p>Todo lo anterior me motivó a volver a escribir, específicamente sobre temas de la Ingeniería de Software, quizás poco vendedores hoy en día, pero esenciales si queremos construir software de buena calidad. Manejo de excepciones, Inyección de dependencias, Métricas de código, Refactoring, Pruebas Unitarias, Acoplamiento, Encapsulamiento, por nombrar algunos.</p><p>Así, en próximos artículos quiero profundizar en temas técnicos a través de pinceladas de teoría, junto con ejemplos prácticos y justificación de por qué su relevancia, a nivel técnico y de negocio.</p><p>Espero lograrlo y, con esto, poder ayudar a otros desarrolladores a estar más consciente de este tipo de temas y a evitar los problemas en el tiempo que pueden traer.</p><p>Nos estamos leyendo.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5fcb926a0262" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[No, we won’t increase our velocity!]]></title>
            <link>https://medium.com/@ferpobletea/no-we-wont-speed-up-5735b926163d?source=rss-eaca11e8e235------2</link>
            <guid isPermaLink="false">https://medium.com/p/5735b926163d</guid>
            <category><![CDATA[scrum]]></category>
            <category><![CDATA[scrum-team-velocity]]></category>
            <category><![CDATA[agile]]></category>
            <dc:creator><![CDATA[Fernando Poblete Arrau]]></dc:creator>
            <pubDate>Tue, 03 May 2016 02:59:00 GMT</pubDate>
            <atom:updated>2016-05-06T14:48:16.479Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*Z-xA-qZp3z251AzQcQmzWQ.png" /></figure><p>Something happened in my team last Sprint that’s not very common: we finished all stories four days before the end of the Sprint. Our Product Owner was happy because we managed to add two more stories and we were happy because we were able to work on spikes and technical tasks from our continuous improvement backlog.</p><p>At our next retrospective some team members pointed at our improving velocity as a positive result, having set a new record of 23 story points. However, my take on this same event was that it was a bad result because we had overestimated two of our stories. This difference of opinion around the same event led us to discuss whether we wanted, as a team, to increase our velocity or not, as measured in story points delivered per Sprint.</p><p>My opinion is very clear on this — I DON’T want to increase my team’s velocity and not because I want to be mediocre or complacent. I do want to get better but I do not think that velocity is a useful way to measure a team’s performance. If we want to get better, it’s not velocity that we should be focusing on.</p><p>Before I continue with this post, I want to point out that I am very aware of <a href="https://twitter.com/hashtag/NoEstimates">#NoEstimates</a>, but as a team we have decided not to change our approach to story points due to the fact that we face other changes, both in methodology and technology and <a href="https://twitter.com/ferpobletea/status/641939016024236032">I am of the opinion</a>, as are others (<a href="https://twitter.com/ixhd/status/642020647846195200">here</a> and <a href="https://twitter.com/estherderby/status/641764740927127552">here</a>), that it is not helpful to change too many things at once.</p><p>So, why shouldn’t we try to increase our velocity? I can think of at least three reasons.</p><h3>Continuous Improvement versus Velocity</h3><p>When we estimate stories, we use the Fibonacci scale, and we consider three main factors that affect the size of a story:</p><ol><li>Repetitiveness. Have we implemented anything similar in the past?</li><li>Risk. Are there any unknowns associated with the process, organization, definitions, stakeholders? Or is there something we don’t know whether it will affect us?</li><li>Complexity. How many tasks are needed to complete the story and what is their complexity?</li></ol><p>As a part of our continuous improvement process, we are always looking at this three factors and every time we introduce an improvement for one of them, it allows us to make our stories smaller. Let me explain this with an example.</p><p>Let’s say in Sprint 1 we have to implement an interface to a configurations table. After a few refinements, we estimate that story as 8 points due to the following:</p><ol><li>We’re going to work with an <a href="https://en.wikipedia.org/wiki/List_of_object-relational_mapping_software">ORM</a> that we have never used before (<strong>size increases due to low Repetitiveness</strong>).</li><li>We have to generate all the views manually, including forms and validations (<strong>size increases due to high Complexity</strong>).</li><li>There isn’t a clearly identified owner for that particular database (<strong>size increases due to high Risk</strong>).</li><li>Creating tables is a manual process in every environment (development, beta, UAT, production). (<strong>size increases due to high Complexity</strong>).</li></ol><p>At the end of Sprint 1 we discussed all these factors in our Retrospective and, after talking to our PO, we defined two technical tasks for Sprint 2: one for automating the creation of views using a template and another for automating the creation of tables as we move across environments.</p><p>The PO also met with stakeholders and reached an agreement on who should own the database.</p><p>In Sprint 3, we have to make a similar interface but for a different table. The ORM is no longer new for us, we can create views and tables automatically, and we have a clearly identified owner for the database. As all factors for Sprint 1 no longer apply, now we give this story only 2 points. That means we can build eight interfaces instead of two, but our velocity is still the same!</p><p>That’s not really a problem, you just have to know how to read the numbers and what’s going on behind them.</p><h3>Predictability versus Velocity</h3><p>On one occasion, a project manager — new to agile and used to Gannt charts and detailed planning before the start of a project — claimed to be finally convinced by this new way of working and that even the executives were convinced. However, the one issue on which they remained unhappy was that Sprint after Sprint the team didn’t deliver what they had committed to.</p><p>This project manager didn’t care if the team delivered 10, 20 or 500 story points, but did care — a lot — about having certainty (or at least a high degree of certainty) of what the team was going to deliver in two weeks.</p><p>What’s the point of increasing such an arbitrary measure as story points? If nobody outside the team understands what a story point is, why would they want the team to deliver more of them? <strong>The team had better focus on improving predictability, not velocity.</strong></p><h3>The subconscious bias</h3><p>If you have a goal of delivering more and more points every sprint, the easiest way to achieve that goal is to always overestimate. I’m not saying that people will cheat, but our subconscious can play tricks on us, especially when the goal is imposed by a manager outside the team or even worse, when that goal is linked to a performance appraisal and/or bonus.</p><h3>So, how do we know if we are improving?</h3><p>There are a lot of metrics we can measure and manage: bugs in production, automated test coverage, stakeholder satisfaction, just to name a few. The important thing to understand is that all metrics have pros and cons, that all of them can encourage bad behaviors if they’re misunderstood, and that having too many metrics is the same as having none, but that could be a topic for a new post.</p><p><em>Original post was written in Spanish and you can find it </em><a href="https://medium.com/@ferpobletea/no-no-subiremos-nuestra-velocidad-8795f62a6d03#.mfv555der"><em>here</em></a><em>. Many thanks to </em><a href="https://medium.com/@cesaridrovo"><em>César Idrovo</em></a><em> for his help with the english translation</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5735b926163d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[¡No, no subiremos nuestra velocidad!]]></title>
            <link>https://medium.com/@ferpobletea/no-no-subiremos-nuestra-velocidad-8795f62a6d03?source=rss-eaca11e8e235------2</link>
            <guid isPermaLink="false">https://medium.com/p/8795f62a6d03</guid>
            <category><![CDATA[scrum]]></category>
            <category><![CDATA[agile]]></category>
            <category><![CDATA[scrum-team-velocity]]></category>
            <dc:creator><![CDATA[Fernando Poblete Arrau]]></dc:creator>
            <pubDate>Fri, 25 Sep 2015 02:39:59 GMT</pubDate>
            <atom:updated>2015-09-28T02:04:11.470Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jbg1oOTWCF4itPVz8mAaCw.png" /></figure><p>En el último Sprint en mi equipo ocurrió algo que pocas veces ocurre: terminamos todas las historias cuando quedaban aún 4 días de Sprint. El Product Owner estaba feliz porque incluimos 2 historias más y nosotros estábamos felices porque incluimos Spikes y tareas técnicas de nuestro Backlog de mejora continua.</p><p>Al llegar la Retrospectiva algunos comentaron, como algo positivo, que habíamos subido nuestra productividad, llegando al récord de 23 puntos en un Sprint. Sin embargo, respecto del mismo hecho, yo comenté como algo negativo que habíamos sobreestimado dos de las historias. Esta diferencia de opinión respecto del mismo hecho nos llevó a discutir sobre si como equipo queremos/debemos o no tratar de subir nuestra velocidad, medida como story points entregados por sprint.</p><p>Mi postura al respecto es bastante clara, yo NO quiero subir la velocidad de mi equipo y no es que sea mediocre o conformista. Es normal que un equipo quiera mejorar en el tiempo y entiendo la tentación de tratar de entregar más en cada sprint, pero no es la velocidad lo que nos debe preocupar.</p><p>Antes de seguir con este artículo, quiero mencionar que sí conozco <a href="https://twitter.com/hashtag/NoEstimates">#NoEstimates</a> pero como equipo hemos decidido mantenernos con los story points debido a que estamos enfrentando otros cambios, tanto metodológicos como tecnológicos y <a href="https://twitter.com/ferpobletea/status/641939016024236032">soy de la opinión</a>, al igual que otras personas (<a href="https://twitter.com/ixhd/status/642020647846195200">aquí</a> y <a href="https://twitter.com/estherderby/status/641764740927127552">aquí</a>), que no es bueno hacer muchos cambios a la vez.</p><p>Ahora retomando, ¿por qué no debemos apuntar a aumentar la velocidad? Al menos a mí se me ocurren tres razones.</p><h3>Mejora continua versus velocidad</h3><p>En mi equipo usamos la escala de Fibonacci para estimar y consideramos tres factores que contribuyen en el tamaño de una historia:</p><ol><li>Repetitividad. ¿Hemos implementado en el pasado algo similar en el pasado?</li><li>Riesgo. ¿Existe algo desconocido en el proceso, organización, definiciones, stakeholders? ¿o algo conocido pero que no sabemos si nos va a afectar?</li><li>Complejidad. ¿Cuántas tareas se necesitan para completar esta historia y cuál es su complejidad?</li></ol><p>Como parte de la mejora continua, estamos preocupados permanentemente de estos tres factores y, cada vez que logramos implementar alguna mejora en uno de ellos, las historias se van haciendo cada vez más chicas. Déjenme ilustrar esto con un ejemplo para que quede más claro.</p><p>Supongamos que en el Sprint 1 tenemos que implementar un mantenedor para una tabla de configuración. Después de varios refinamientos estimamos la historia en 8 puntos, ya que identificamos lo siguiente:</p><ol><li>Vamos a usar un <a href="https://en.wikipedia.org/wiki/List_of_object-relational_mapping_software">ORM</a> con el que nunca antes hemos trabajado. <strong>(baja Repetitividad, crece tamaño)</strong>.</li><li>Tenemos que generar todas las vistas manualmente, incluyendo formularios y validaciones. <strong>(sube Complejidad, crece tamaño).</strong></li><li>No está claro quién es el dueño de la Base de Datos. <strong>(sube Riesgo, crece tamaño).</strong></li><li>El proceso de creación de tablas es manual en cada uno de los ambientes (desarrollo, beta, uat, producción). <strong>(sube Complejidad, crece tamaño).</strong></li></ol><p>Al terminar el Sprint 1, conversamos estos temas en la retrospectiva y, luego de hablarlo con el PO, incluimos en el Sprint 2 una tarea técnica para automatizar la creación de vistas a partir de un modelo y otra para automatizar la creación de tablas al pasar de un ambiente a otro.</p><p>Por otra parte, el PO logró reunir a los stakeholders y se llegó a un acuerdo sobre quién es el dueño de la Base de Datos.</p><p>En el Sprint 3 debemos implementar otro mantenedor, para otra tabla del sistema. ¿Cuántos puntos debiéramos darle a esta nueva historia? Dado que los 4 aspectos anteriores fueron mejorados o resueltos, ahora el equipo estima la historia en 2 puntos. <strong>Ahora podemos hacer 8 mantenedores por Sprint en vez de 2, ¡pero nuestra velocidad sigue siendo la misma!</strong></p><p>La verdad es que esto no es realmente un problema, sólo hay que saber mirar los hechos detrás de los números.</p><h3>Predicitibilidad versus velocidad</h3><p>Una vez un jefe de proyecto, nuevo en la agilidad y acostumbrado a cartas Gantt y a la planificación anticipada y detallada, me comentó que finalmente se había convencido con esta nueva forma de trabajar y que incluso los ejecutivos ya estaban convencidos. Lo único que no lo tenía feliz era que el equipo, Sprint tras Sprint, no alcanzaba a entregar lo que había comprometido.</p><p>A este jefe de proyecto no le importaban si eran 10, 20 o 500 story points, lo que le importaba era tener una certeza (o un buen grado de certeza) de qué iba a entregar el equipo de aquí a dos semanas más.</p><p>¿Qué sentido tiene querer aumentar la velocidad de un equipo cuando se usa una medida tan arbitraria como story points? ¿a quién le sirve que aumente algo que es medido en una escala que nadie entiende fuera del equipo? <strong>Más vale la pena que el equipo se enfoque en mejorar su predictibilidad, no su velocidad</strong>.</p><h3>La trampa del inconsciente</h3><p>Dejé para el final la que quizás es la más obvia de las razones. Si uno tiene como objetivo aumentar la cantidad de puntos entregados por sprint, entonces lo más fácil es asignar números más grandes al estimar. No que las personas hagan intencionalmente trampa, pero el inconsciente nos puede engañar y llevar a este tipo de vicios, sobre todo cuando la capa de management introduce metas del tipo <strong>“aumentar en 20% la velocidad por semestre”</strong>, o peor aún cuando asocia la velocidad de un equipo a su evaluación y/o a su bono de desempeño.</p><h3>¿Cómo sabemos si vamos mejorando?</h3><p>Si no subimos nuestra velocidad, entonces ¿cómo sabemos si vamos mejorando? Existen muchas otras métricas que podemos implementar y gestionar: cantidad de defectos en producción, cobertura de pruebas automáticas, performance, <a href="https://es.wikipedia.org/wiki/Complejidad_ciclom%C3%A1tica">complejidad ciclomática</a> o satisfacción de los stakeholders, por nombrar algunas. Es importante entender eso sí que todas las métricas tienen ventajas y desventajas, todas pueden generar vicios y trampas inconscientes y que no nos podemos llenar de métricas porque tener muchas es lo mismo que no tener ninguna, pero eso podría quedar como tema para un futuro post.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8795f62a6d03" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>