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.
Son los que emergen del código confuso, difícil de leer y de entender.
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.
-
Considera el siguiente código:
def dijkstra_algorithm(graph): ...
¿Para qué crees que sirve este algoritmo?
-
Busca quién era Dijkstra y qué hace su algoritmo y sugiere un mejor nombre.
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.
-
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?
-
Refactoriza el código para que no haya tanta repetición.
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.
-
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? -
Reemplaza cada comentario y código asociado por una llamada a una función distinta.
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.
-
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
-
¿Podrías eliminar los bucles utilizando funciones de Python?
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
"""
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: