Por que Qualidade de Código
André Claudino
https://www.linkedin.com/in/andreclaudino/
https://github.com/andreclaudino
http://t.me/aclaudino
- PhD. Física Computacional
- Engenheiro de Software
- Engenheiro de Dados
- Cientista de Dados
- Professor
Bom
- Atende ao cliente
- Performance atende ao cliente dentro do posśivel
- Evolui de acordo com as necessidades do cliente
- Não gera dívida técnica
Ruim
- Não atende ao cliente
- Desempenho deixa a desejar
- Impõe restrições para evoluir ou exige muito tempo
- Sempre tem o que resolver depois
Software
Mundo real
- Muito difícil criar um bom software de primeira
- Ambiente e necessidades do cliente mudam
- Software precisa evoluir rápido!
Ciclo de desenvolvimento
Código limpo leva à...
- Manutenção e Colaboração mais fáceis
- Menos erros e bugs
- Mais reutilização e escalabilidade (de trabalho)
- Mais fácil encontrar gargalos de desempenho
- Torna mudanças futuras mais fáceis
Premissas da qualidade de código
Nomenclatura
- use nomes descritivos, melhor nomes grandes que confusos
- Funções tem nome de ações que indicam o que elas fazem
a = 10
b = 5
path = "/path/to/file"
df = pd.read_csv(path)
def calculate(a: float) -> float:
pi = 3.1415
return pi * a ** 2
total_students = 10
passing_grade = 5.0
alunos_source_path = "/path/to/file"
alunos_data_frame = pd.read_csv(alunos_source_path)
# Use constantes fora das funções
PI = 3.1415
def calculate_circle_area(radius: float) -> float:
area = PI * radius ** 2
return area
Ruim
Bom
Unidades simples e precisas
- Classes, módulos e funções devem fazer uma coisa, e bem feito
- Código menor é mais legível
- Evite loops e condicionais aninhados em altos níveis, quebre em novas funções para isso.
def find_max(numbers):
max_num = None
for num in numbers:
if max_num is None:
max_num = num
elif num > max_num:
max_num = num
return max_num
def find_max(numbers: List[float]) -> float:
max_number = max(numbers)
return max_number
Ruim
Bom
Única responsabilidade
- Cada unidade deve ter apenas uma responsabilidade
- Uma responsabilidade maior pode ser quebrada em responsabilidades menores de forma hierárquica
class FileProcessor:
def process_file(self):
# Lógica para processar o arquivo
def save_data(self):
# Lógica para salvar os dados em um banco de dados
class FileLoader:
def __init__(database_saver: DatabaseSaver):
self._database_saver = database_saver
def process_file(self):
# Lógica para processar o arquivo
self._database_saver.save_data()
class DatabaseSaver:
def save_data(self):
# Lógica para salvar os dados em um banco de dados
Ruim
Bom
def manage_user(action, user_id, data=None):
if action == "get":
# Lógica para recuperar informações do usuário do banco de dados
elif action == "create":
# Lógica para criar um novo usuário no banco de dados
elif action == "update":
# Lógica para atualizar as informações de um usuário no banco de dados
elif action == "delete":
# Lógica para excluir um usuário do banco de dados
def get_user(user_id):
# Lógica para recuperar informações do usuário do banco de dados
def create_user(user_data):
# Lógica para criar um novo usuário no banco de dados
def update_user(user_id, updated_data):
# Lógica para atualizar as informações de um usuário no banco de dados
def delete_user(user_id):
# Lógica para excluir um usuário do banco de dados
Ruim
Bom
Formatação
- Python se baseia em identação, mas algumas coisas podem ser melhoradas
- Outras linguagens precisam de formatação adequada
- Use funções para melhorar código aninhado
Evite Mau cheiro
Código que facilita o comentimento de erros:
- Funções longas
- Comentários excessivos
- Classes grandes
- Loops e condicionais complexos
def f(a, b, c):
d = []
for e in range(len(a)):
if b[e] > 0 and a[e] > 0:
d.append(a[e] * b[e] + c)
return d
def calculate_positive_products(a_list: float, b_list: float, constant: float) -> float:
result = []
for a, b in zip(a_list, b_list):
if b > 0 and a > 0:
multiplied_value = a * b + constant
result.append(multiplied_value)
return result
Ruim
Bom
# Função para calcular a média dos números em uma lista
def calculate_average(numbers):
# Verifica se a lista não está vazia
if len(numbers) > 0:
# Inicializa a variável de soma
total = 0
# Loop para somar os números na lista
for num in numbers:
total += num
# Calcula a média
average = total / len(numbers)
# Retorna a média
return average
else:
# Retorna None se a lista estiver vazia
return None
Ruim
# Função para calcular a média dos números em uma lista
def calculate_average(numbers):
# Verifica se a lista não está vazia
if not numbers:
return None
# Chama função auxiliar para calcular a soma dos números
total = calculate_sum(numbers)
# Calcula a média
average = total / len(numbers)
# Retorna a média
return average
# Função auxiliar para calcular a soma dos números em uma lista
def calculate_sum(numbers):
total = 0
for num in numbers:
total += num
return total
Bom
Comentários
- Prefira código auto-explicativo
- Não confunda comentários e documentação
- Documente se preciso, mas evite comentários inúteis
- Se preciso, documente unidades (funções e módulos, não linhas de código)
def calculate_total(price: float, quantity: int) -> float:
# Multiplica o preço pela quantidade para obter o total
total = price * quantity
return total
Ruim
def calculate_total(price: float, quantity: int) -> float:
"""Calculate the total value by summing units over price
Parameters:
price (float): The price of the product
quantity (int): The quantity of purchases
Returns:
int: The total amount to be paid
"""
total = price * quantity
return total
Bom
Testes
- Testes não testam funcionalidades, mas garantem funcionamento
- Testes devem testar só um aspecto
- Testes atualizados garantem um código sempre funcional
- Adicione testes ao adicionar funcionalidades
- Modifique testes ao mudar uma regra de negócio
- Prefira TDD
Exemplo
Função verifica se um número é primo
def is_prime(number: int) -> bool:
"""
Verifica se um número é primo.
Args:
number (int): O número a ser verificado.
Returns:
bool: True se o número for primo, False caso contrário.
"""
if number < 2:
return False
for i in range(2, int(number ** 0.5) + 1):
if number % i == 0:
return False
return True
Exemplo
Bateria de testes da função tem vários aspectos que garantem o funcionamento.
import unittest
class TestIsPrime(unittest.TestCase):
def test_prime_number(self):
self.assertTrue(is_prime(2))
self.assertTrue(is_prime(3))
self.assertTrue(is_prime(5))
self.assertTrue(is_prime(7))
self.assertTrue(is_prime(11))
self.assertTrue(is_prime(13))
def test_non_prime_number(self):
self.assertFalse(is_prime(1))
self.assertFalse(is_prime(4))
self.assertFalse(is_prime(6))
self.assertFalse(is_prime(8))
self.assertFalse(is_prime(9))
self.assertFalse(is_prime(10))
def test_negative_number(self):
self.assertFalse(is_prime(-2))
self.assertFalse(is_prime(-5))
self.assertFalse(is_prime(-10))
def test_zero(self):
self.assertFalse(is_prime(0))
def test_large_prime_number(self):
self.assertTrue(is_prime(9999999967))
if __name__ == '__main__':
unittest.main()
Próxima parte
- Aplicando a o princípio da única responsabilidade de forma hierárquica
- Criando um código limpo via modularização
- Parametrizando o código para flexibilizar
Introdução Intuitiva à Qualidade de Código e Refatoração
By André Claudino
Introdução Intuitiva à Qualidade de Código e Refatoração
- 70