Melhore a manutenibilidade, legibilidade e desempenho do seu código Python com técnicas eficazes de refatoração. Aprenda estratégias práticas e as melhores práticas para melhorar a qualidade do código.
Técnicas de Refatoração em Python: Um Guia Abrangente para a Melhoria da Qualidade do Código
No cenário em constante evolução do desenvolvimento de software, manter um código limpo, eficiente e compreensível é primordial. O Python, conhecido pela sua legibilidade, ainda pode ser vítima de "code smells" e débito técnico se não for gerido cuidadosamente. Refatoração é o processo de reestruturar o código de computador existente — alterando a fatoração — sem modificar o seu comportamento externo. Em essência, é limpar o seu código sem o quebrar. Este guia explora várias técnicas de refatoração em Python, fornecendo exemplos práticos e as melhores práticas para elevar a qualidade do seu código.
Por Que Refatorar Código Python?
A refatoração oferece inúmeros benefícios, incluindo:
- Legibilidade Aprimorada: Torna o código mais fácil de entender e manter.
- Complexidade Reduzida: Simplifica lógicas complexas, reduzindo a probabilidade de erros.
- Manutenibilidade Melhorada: Facilita a modificação e extensão do código.
- Desempenho Aumentado: Pode otimizar o código para uma melhor velocidade de execução.
- Débito Técnico Menor: Evita a acumulação de código que é difícil de manter ou estender.
- Melhor Design: Leva a uma arquitetura de código mais robusta e flexível.
Ignorar a refatoração pode levar a um código difícil de entender, modificar e testar. Isso pode aumentar significativamente o tempo de desenvolvimento e o risco de introduzir bugs.
Quando Refatorar?
Saber quando refatorar é crucial. Aqui estão alguns cenários comuns:
- Antes de Adicionar Novos Recursos: Refatorar o código existente pode facilitar a integração de novas funcionalidades.
- Após Corrigir um Bug: Refatorar o código ao redor pode evitar que bugs semelhantes voltem a ocorrer.
- Durante as Revisões de Código: Identifique áreas que podem ser melhoradas e refatore-as.
- Quando Encontrar "Code Smells": "Code smells" são indicadores de problemas potenciais no seu código.
- Refatoração Programada Regularmente: Incorpore a refatoração no seu processo de desenvolvimento como uma atividade regular.
Identificando "Code Smells"
"Code smells" são indicações superficiais que geralmente correspondem a um problema mais profundo no sistema. Eles nem sempre indicam um problema, mas frequentemente justificam uma investigação mais aprofundada.
"Code Smells" Comuns em Python:
- Código Duplicado: Código idêntico ou muito semelhante que aparece em vários lugares.
- Método/Função Longo: Métodos ou funções que são excessivamente longos e complexos.
- Classe Grande: Classes que têm demasiadas responsabilidades.
- Lista de Parâmetros Longa: Métodos ou funções com demasiados parâmetros.
- Aglomerados de Dados: Grupos de dados que frequentemente aparecem juntos.
- Obsessão Primitiva: Usar tipos de dados primitivos em vez de criar objetos.
- Instruções Switch: Longas cadeias de instruções if/elif/else ou instruções switch.
- Cirurgia de Espingarda (Shotgun Surgery): Fazer uma única alteração requer fazer muitas pequenas alterações em diferentes classes.
- Alteração Divergente: Uma classe é comumente alterada de diferentes maneiras por diferentes razões.
- Inveja de Funcionalidade (Feature Envy): Um método acede mais aos dados de outro objeto do que aos seus próprios dados.
- Cadeias de Mensagens: Um cliente pede a um objeto para solicitar a outro objeto que solicite a ainda outro objeto...
Técnicas de Refatoração em Python: Um Guia Prático
Esta seção detalha várias técnicas comuns de refatoração em Python com exemplos práticos.
1. Extrair Método/Função
Esta técnica envolve pegar um bloco de código dentro de um método ou função e movê-lo para um novo método ou função separado. Isso reduz a complexidade do método original e torna o código extraído reutilizável.
Exemplo:
def print_invoice(customer, details):
print("***********************")
print(f"Customer: {customer}")
print("***********************")
total_amount = 0
for order in details["orders"]:
total_amount += order["amount"]
print(f"Amount is : {total_amount}")
if total_amount > 1000:
print("You earned a discount!")
Refatorado:
def print_header(customer):
print("***********************")
print(f"Customer: {customer}")
print("***********************")
def calculate_total(details):
total_amount = 0
for order in details["orders"]:
total_amount += order["amount"]
return total_amount
def print_invoice(customer, details):
print_header(customer)
total_amount = calculate_total(details)
print(f"Amount is : {total_amount}")
if total_amount > 1000:
print("You earned a discount!")
2. Extrair Classe
Quando uma classe tem demasiadas responsabilidades, extraia algumas delas para uma nova classe. Isso promove o Princípio da Responsabilidade Única.
Exemplo:
class Person:
def __init__(self, name, phone_number, office_area_code, office_number):
self.name = name
self.phone_number = phone_number
self.office_area_code = office_area_code
self.office_number = office_number
def get_name(self):
return self.name
def get_phone_number(self):
return f"({self.office_area_code}) {self.office_number}"
Refatorado:
class PhoneNumber:
def __init__(self, area_code, number):
self.area_code = area_code
self.number = number
def get_phone_number(self):
return f"({self.area_code}) {self.number}"
class Person:
def __init__(self, name, phone_number):
self.name = name
self.phone_number = phone_number
def get_name(self):
return self.name
3. Embutir Método/Função
Isto é o oposto de Extrair Método. Se o corpo de um método é tão claro quanto o seu nome, pode embutir o método substituindo as chamadas ao método pelo conteúdo do método.
Exemplo:
def get_rating(driver):
return more_than_five_late_deliveries(driver) ? 2 : 1
def more_than_five_late_deliveries(driver):
return driver.number_of_late_deliveries > 5
Refatorado:
def get_rating(driver):
return driver.number_of_late_deliveries > 5 ? 2 : 1
4. Substituir Variável Temporária por Consulta
Em vez de usar uma variável temporária para guardar o resultado de uma expressão, extraia a expressão para um método. Isso evita a duplicação de código e promove uma melhor legibilidade.
Exemplo:
def get_price(order):
base_price = order.quantity * order.item_price
discount_factor = 0.98 if base_price > 1000 else 0.95
return base_price * discount_factor
Refatorado:
def get_price(order):
return base_price(order) * discount_factor(order)
def base_price(order):
return order.quantity * order.item_price
def discount_factor(order):
return 0.98 if base_price(order) > 1000 else 0.95
5. Introduzir Objeto de Parâmetro
Se tiver uma longa lista de parâmetros que aparecem frequentemente juntos, considere criar um objeto de parâmetro para os encapsular. Isso reduz o comprimento da lista de parâmetros e melhora a organização do código.
Exemplo:
def calculate_total(width, height, depth, weight, shipping_method):
# Lógica de cálculo
pass
Refatorado:
class ShippingDetails:
def __init__(self, width, height, depth, weight, shipping_method):
self.width = width
self.height = height
self.depth = depth
self.weight = weight
self.shipping_method = shipping_method
def calculate_total(shipping_details):
# Lógica de cálculo usando atributos de shipping_details
pass
6. Substituir Condicional por Polimorfismo
Quando tem uma instrução condicional complexa que escolhe o comportamento com base no tipo de um objeto, considere usar polimorfismo para delegar o comportamento a subclasses. Isso promove uma melhor organização do código e facilita a adição de novos tipos.
Exemplo:
def calculate_bonus(employee):
if employee.employee_type == "SALES":
return employee.sales * 0.1
elif employee.employee_type == "ENGINEER":
return employee.projects_completed * 100
elif employee.employee_type == "MANAGER":
return 1000
else:
return 0
Refatorado:
class Employee:
def calculate_bonus(self):
return 0
class SalesEmployee(Employee):
def __init__(self, sales):
self.sales = sales
def calculate_bonus(self):
return self.sales * 0.1
class EngineerEmployee(Employee):
def __init__(self, projects_completed):
self.projects_completed = projects_completed
def calculate_bonus(self):
return self.projects_completed * 100
class ManagerEmployee(Employee):
def calculate_bonus(self):
return 1000
7. Decompor Condicional
Semelhante a Extrair Método, isto envolve dividir uma instrução condicional complexa em métodos menores e mais manejáveis. Isso melhora a legibilidade e facilita a compreensão da lógica da condicional.
Exemplo:
if (platform.upper().index("MAC") > -1) and (browser.upper().index("IE") > -1) and was_initialized() and resize > MAX_RESIZE:
# Fazer algo
pass
Refatorado:
def is_mac_os():
return platform.upper().index("MAC") > -1
def is_ie_browser():
return browser.upper().index("IE") > -1
if is_mac_os() and is_ie_browser() and was_initialized() and resize > MAX_RESIZE:
# Fazer algo
pass
8. Substituir Número Mágico por Constante Simbólica
Substitua valores numéricos literais por constantes nomeadas. Isso melhora a legibilidade e facilita a alteração dos valores mais tarde. Isso também se aplica a outros valores literais como strings. Considere códigos de moeda (ex: 'USD', 'EUR', 'JPY') ou códigos de status (ex: 'ACTIVE', 'INACTIVE', 'PENDING') de uma perspetiva global.
Exemplo:
def calculate_area(radius):
return 3.14159 * radius * radius
Refatorado:
PI = 3.14159
def calculate_area(radius):
return PI * radius * radius
9. Remover Intermediário
Se uma classe está simplesmente a delegar chamadas para outra classe, considere remover o intermediário e permitir que o cliente aceda diretamente à classe de destino.
Exemplo:
class Person:
def __init__(self, department):
self.department = department
def get_manager(self):
return self.department.get_manager()
class Department:
def __init__(self, manager):
self.manager = manager
def get_manager(self):
return self.manager
Refatorado:
class Person:
def __init__(self, manager):
self.manager = manager
def get_manager(self):
return self.manager
10. Introduzir Asserção
Use asserções para documentar suposições sobre o estado do programa. Isso pode ajudar a detetar erros precocemente e tornar o código mais robusto.
Exemplo:
def calculate_discount(price, discount_percentage):
if discount_percentage < 0 or discount_percentage > 100:
raise ValueError("Discount percentage must be between 0 and 100")
return price * (1 - discount_percentage / 100)
Refatorado:
def calculate_discount(price, discount_percentage):
assert 0 <= discount_percentage <= 100, "Discount percentage must be between 0 and 100"
return price * (1 - discount_percentage / 100)
Ferramentas para Refatoração em Python
Várias ferramentas podem ajudar na refatoração em Python:
- Rope: Uma biblioteca de refatoração para Python.
- PyCharm: Um popular IDE Python com suporte integrado para refatoração.
- VS Code com Extensão Python: Um editor versátil com capacidades de refatoração através de extensões.
- Sourcery: Uma ferramenta de refatoração automatizada.
- Bowler: Uma ferramenta de refatoração do Facebook para modificações de código em grande escala.
Melhores Práticas para Refatoração em Python
- Escreva Testes Unitários: Garanta que o seu código está bem testado antes de refatorar.
- Refatore em Pequenos Passos: Faça alterações pequenas e incrementais para minimizar o risco de introduzir erros.
- Teste Após Cada Passo de Refatoração: Verifique se as suas alterações não quebraram nada.
- Use Controlo de Versão: Faça commit das suas alterações frequentemente para poder reverter facilmente se necessário.
- Comunique com a Sua Equipa: Informe a sua equipa sobre os seus planos de refatoração.
- Foque na Legibilidade: Priorize tornar o seu código mais fácil de entender.
- Não Refatore Apenas por Refatorar: Refatore quando isso resolve um problema específico.
- Automatize a Refatoração Onde Possível: Utilize ferramentas para automatizar tarefas de refatoração repetitivas.
Considerações Globais para Refatoração
Ao trabalhar em projetos internacionais ou para um público global, considere estes fatores durante a refatoração:
- Localização (L10n) e Internacionalização (I18n): Garanta que o seu código suporta adequadamente diferentes idiomas, moedas e formatos de data. Refatore para isolar a lógica específica da localidade.
- Codificação de Caracteres: Use a codificação UTF-8 para suportar uma vasta gama de caracteres. Refatore o código que assume uma codificação específica.
- Sensibilidade Cultural: Esteja atento às normas culturais e evite usar linguagem ou imagens que possam ser ofensivas. Reveja literais de string e elementos da interface do utilizador durante a refatoração.
- Fusos Horários: Lide corretamente com as conversões de fuso horário. Refatore o código que faz suposições sobre o fuso horário do utilizador. Use bibliotecas como `pytz`.
- Manuseamento de Moeda: Use tipos de dados e bibliotecas apropriados para manusear valores monetários. Refatore o código que realiza conversões de moeda manuais. Bibliotecas como `babel` são úteis.
Exemplo: Localizando Formatos de Data
import datetime
def format_date(date):
return date.strftime("%m/%d/%Y") # Formato de data dos EUA
Refatorado:
import datetime
import locale
def format_date(date, locale_code):
locale.setlocale(locale.LC_TIME, locale_code)
return date.strftime("%x") # Formato de data específico da localidade
# Exemplo de uso:
# format_date(datetime.date(2024, 1, 1), 'en_US.UTF-8') # Saída: '01/01/2024'
# format_date(datetime.date(2024, 1, 1), 'de_DE.UTF-8') # Saída: '01.01.2024'
Conclusão
A refatoração é uma prática essencial para manter código Python de alta qualidade. Ao identificar e resolver "code smells", aplicar técnicas de refatoração apropriadas e seguir as melhores práticas, pode melhorar significativamente a legibilidade, a manutenibilidade e o desempenho do seu código. Lembre-se de priorizar os testes e a comunicação ao longo do processo de refatoração. Adotar a refatoração como um processo contínuo levará a um fluxo de trabalho de desenvolvimento de software mais robusto e sustentável, particularmente ao desenvolver para um público global e diversificado.