Skip to content

Latest commit

 

History

History
232 lines (171 loc) · 7.08 KB

File metadata and controls

232 lines (171 loc) · 7.08 KB

Olores del código y refactorización

Los olores en el código son indicativos de que un diseño falla o va en contra de los principios de diseño de software, lo que suele desenvocar en software menos tolerantes al cambio y más frágil.

El término fue acuñado por Kent Beck y popularizado por Martin Fowler en su libro Refactoring.

Refactorizar es el proceso de reescribir código con el objetivo de mejorar el diseño, y no de alterar el comportamiento del programa. Las técnicas de refactorización se podrían clasificar en:

  • Renombramientos.
  • Cambios de lugar.
  • Introducción de nuevas abastracciones.
  • Eliminación de abstracciones innecesarias.

Olores de implementación

Son los que emergen del código confuso, difícil de leer y de entender.

El nombre misterioso

El nombre de una función, de una clase o de una variable debería sugerir qué hace, qué representa o qué contiene. Se arregla con un renombramiento.

  1. Considera el siguiente código:

    def dijkstra_algorithm(graph):
       ...

    ¿Para qué crees que sirve este algoritmo?

  2. Busca quién era Dijkstra y qué hace su algoritmo y sugiere un mejor nombre.

Código duplicado

El código duplicado crea incertidumbre acerca de si las distintas apariciones son, en efecto, la misma, o si se trata de un error. Se solventa introduciendo una función y moviendo el código común a su interior.

  1. Considera el siguiente fragmento:

    class HttpChat:
    
       def __init__(self, url):
           self._url = url
    
       def login(self, user, password):
           message = Message(f'LOGIN {user}:{password}')
           message.send(self._url)
           message.wait_for_response()
           return message.response()
        
       def send(self, what):
           message = Message(f'SEND {what}')
           message.send(self._url)
           message.wait_for_response()
           return message.response()
        
       def answer(self, message_id, what):
           message = Message(f'ANSWER {what} TO {message_id}')
           message.send(self._url)
           message.wait_for_response()
           return message.response()
        
       def keep_alive(self):
           message = Message(f'I AM STILL HERE')
           message.send(self._url)

    ¿Dónde se repite el código?

  2. Refactoriza el código para que no haya tanta repetición.

La función larga

A veces una función hace demasiadas cosas, lo que provoca que tenga muchas motivos de cambio, que no quepa en pantalla o que haya que leerla varias veces. Se suele arreglar dividiendo la función en otras más cortas.

  1. Considera el siguiente código:

    def count(text, exclusion_list):
       if not isinstance(text, str):
           raise TypeError('text must be a string')
       
       words = text.split()
       valid_words = (w for w in words if w not in exclusion_list)
       histogram = {}
       for word in valid_words:
           if word not in histogram:
               histogram[word] = 0
            
           histogram[word] += 1
        
       return histogram

    ¿Podrías indicar qué hace count añadiendo algunos comentarios?

  2. Reemplaza cada comentario y código asociado por una llamada a una función distinta.

Bucles

Leer un bucle es muchas veces una tarea tediosa que aporta poco contexto a la tarea que estamos llevando a cabo. En general un bucle se usa para recorrer o encontrar. Es preferible ocultar los bucles bajo nombres más semánticos, que aporten más información al consumidor.

  1. Considera el siguiente código:

    def calculate_discounts(product_list, categories_with_discount, discount):
       products_with_discount = []
       for product in product_list:
           if product in categories_with_discount:
               discount = product.price * discount
               products_with_discount.append((product, discount))
            
       total_discount = 0
       for _, discount in products_with_discount:
           total_discount += discount 
        
       return products_with_discount, total_discount
  2. ¿Podrías eliminar los bucles utilizando funciones de Python?

Comentarios

Leer un mal comentario es una de las fuentes de confusión y pérdida de tiempo más frecuente. Los problemas con comentarios suelen solucionarse borrándolos, y trasladando la semántica al código.

Algunos malos ejemplos:

def send_message(msg):
    """Sends a message."""
class Player:
    def move_left(self):
        """Locate the character a fixed amount of pixel to the left."""
        
    def move_right(self):
        """Locate the character a fixed amount of pixel to the left."""
        
    def react_upon_damage(self):
        """Locate the character a fixed amount of pixel to the left."""
class Player:
    def move_left(self, impulse):
        """Locate the character a fixed amount of pixel to the left."""
def valid_tokens():
    return list('+-<>[].')
    
def parse(source):
    for char in source:
        # Don't do anything if the char is not valid
        if char in valid_tokens():
            ...
# Given the nature of the haystack, this function is hard to optimize.
# Don't be a fucking idiot and do not waste your time trying.
def find_needle(haystack):
    ...

Algunos buenos ejemplos:

# NOTE: Given the nature of the haystack, this function is hard to optimize.
# There is a discussion about suggested and failed optimizations in:
# https://github.mycompany.com/team/proyect/issues  
def find_needle(haystack):
    ...
# TODO: Remove #42 [1] is solved.
# [1] https://github.mycompany.com/team/proyect/issues/42
class APIAdaptor:
    ...
def sort(collection):
    """Sort by using radix sort.
    
    See also:
        * Radix Sort at https://en.wikipedia.org/wiki/Radix_sort
    """

Más olores

Esta lista es incompleta. Entrenaremos nuestra nariz y descubriremos más olores en el código cuando avancemos por el curso y nos pongamos a escribir Python, de verdad.

Los olores en el código se relacionan con los anti-patrones en que estos últimos suelen "oler". Un anti-patrón es una solucción recurrente a un problema que, usualmente, conlleva un diseño deficiente, inefectivo y contraproducente a largo plazo.

El capítulo 3 del libro de Fowler contiene los tipos de olores identificados por el autor. Los mismos pueden encontrarse en la mayoría de artículos por Internet. Entre ellos: