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
Made with Slides.com