Boas Práticas Nathan Geeksman

Funções puras e por que elas tornam seu código mais testável

Funções puras e por que elas tornam seu código mais testável

Funções puras e por que elas tornam seu código mais testável

Introdução

Desenvolver software de alta qualidade é um desafio contínuo que requer a implementação de práticas robustas e testáveis. Neste contexto, uma ferramenta poderosa para alcançar essa meta são as funções puras. A integração dessas funções no desenvolvimento de software contribui significativamente na qualidade dos códigos gerados.

As funções puras desempenham um papel crucial ao garantir que o código seja mais testável, pois elas se preocupam em realizar apenas uma única tarefa e não dependem de estados externos. Esse comportamento facilita a criação de casos de teste independentes e eficazes, reduzindo assim a complexidade da manutenção do código.

Este artigo visa fornecer uma visão profunda das funções puras e suas implicações positivas no desenvolvimento de software. Ao final dessa leitura, o leitor estará apto a compreender como implementar as práticas recomendadas de programação funcional na própria aplicação para torná-la mais robusta e fácil de manter.

O que é e por que importa

As funções puras são um conceito fundamental na programação funcional, que representa uma abordagem para desenvolver código mais limpo, modular e testável. Uma função pura ou funcional é aquela que produz o mesmo resultado sempre que recebe os mesmos argumentos de entrada e não depende do estado do programa ou de resultados anteriores.

Características

As funções puras possuem algumas características importantes:

  • Entrada-Saída: Recebem uma entrada e geram uma saída, sem alterar o estado externo.
  • Imutabilidade: Não modificam os dados de entrada ou outros objetos.
  • Determinismo: Produzem sempre o mesmo resultado para a mesma entrada.

Essas características permitem que as funções puras sejam mais fáceis de testar e, consequentemente, reduzam a complexidade do código. Além disso, elas facilitam a depuração e a manutenção dos sistemas, pois é possível entender com facilidade como o programa funciona.

Motivações

A adoção de funções puras no desenvolvimento de software traz diversas motivações importantes:

  • Testabilidade: As funções puras são mais fáceis de testar, pois podem ser avaliadas isoladamente.
  • Modularidade: Cada função tem uma única responsabilidade, facilitando a manutenção e atualização do código.
  • Reusabilidade: As funções puras podem ser reutilizadas em outros contextos.

Problemas resolvidos

As funções puras abordam diretamente alguns problemas comuns que surgem na programação tradicional, como:

  • Cadeias de função longa: A separação em funções menores e independentes facilita a compreensão do código.
  • Dependência entre modulos: O uso de funções puras reduz a complexidade da integração entre módulos.

Ao adotar as práticas recomendadas das funções puras, os desenvolvedores podem criar códigos mais robustos e fáceis de manter. Além disso, isso contribui para a criação de sistemas que são mais escaláveis e seguros.

Como funciona na prática

Exemplo Prático: Função de Cálculo de Desconto

Considere uma função calculaDesconto que recebe dois parâmetros, preco e descontoPorcentagem, e devolve o preço com desconto aplicado. Essa função é pura por seguir as características da entrada-saída, imutabilidade e determinismo.

def calcula_desconto(preco, desconto_porcentagem):
    resultado = preco * (1 - (desconto_porcentagem / 100))
    return resultado

Etapas de Funcionamento

  • Receber Entrada: A função recebe dois parâmetros: preco e descontoPorcentagem.
  • Processar Informação: Calcula o desconto a ser aplicado, considerando que é uma porcentagem sobre o preço.
  • Geração de Saída: Retorna o resultado do cálculo como um novo valor, sem modificar os dados originais.
  • Imutabilidade e Determinismo: Como cada vez que se chama calcula_desconto com os mesmos parâmetros, a saída será sempre a mesma.

Uso Prático

No código acima, o desenvolvedor pode testar facilmente a função criando casos de teste para diferentes entradas. Por exemplo:

preco = 100
desconto_porcentagem = 10
resultado = calcula_desconto(preco, desconto_porcentagem)
print(resultado)  # Saída: 90

preco = 150
desconto_porcentagem = 0
resultado = calcula_desconto(preco, desconto_porcentagem)
print(resultado)  # Saída: 150

A funcionalidade da função é clara e fácil de entender. Isso facilita a manutenção e a evolução do código em longo prazo.

Benefícios

As funções puras, como calcula_desconto, permitem que os códigos sejam mais claros, testáveis e escaláveis. Esses benefícios são conseguidos por meio da divisão de responsabilidades simples e independentes, facilitando a compreensão do fluxo de dados no código.

Exemplo em Ação

A adopção de funções puras pode fazer um grande impacto na produtividade e na qualidade do seu software. Ao invés de ter apenas uma função complexa que calcula o desconto para qualquer tipo de preço, dividir a responsabilidade dessa tarefa entre várias funções mais simples facilita a identificação das causas raiz dos problemas.

def calcula_preco_com_desconto(preco, porcentagem):
    preco_descontado = preco * (1 - (porcentagem / 100))
    return preco_descontado

def calcula_preco_sem_desconto(preco):
    return preco

def calcular_desconto(aplicar_desconto, preco, desconto_porcentagem):
    if aplicar_desconto:
        preco_com_desconto = calcula_preco_com_desconto(preco, desconto_porcentagem)
        return preco_com_desconto
    else:
        preco_sem_desconto = calcula_preco_sem_desconto(preco)
        return preco_sem_desconto

Isso reduz a complexidade do código e torna mais fácil identificar e corrigir problemas no futuro.

Exemplo real

Um exemplo real de como funções puras podem ser úteis é no cálculo dos descontos em um sistema de e-commerce. Suponha que você esteja trabalhando em um projeto de uma loja online e precisamos calcular o preço do produto após aplicar um desconto.

def calcula_preco_com_desconto(preco, porcentagem):
    # Aplica a porcentagem do desconto ao preço original
    preco_descontado = preco * (1 - (porcentagem / 100))
    return preco_descontado

def calcula_preco_sem_desconto(preco):
    # Retorna o preço original do produto
    return preco

def calcular_desconto(aplicar_desconto, preco, desconto_porcentagem):
    if aplicar_desconto:
        # Se for aplicado o desconto, calcula o novo preço com desconto
        preco_com_desconto = calcula_preco_com_desconto(preco, desconto_porcentagem)
        return preco_com_desconto
    else:
        # Se não for aplicado o desconto, retorna o preço original do produto
        preco_sem_desconto = calcula_preco_sem_desconto(preco)
        return preco_sem_desconto

preco_original = 200.0
desconto_porcentagem = 20
aplicar_desconto = True

preco_final = calcular_desconto(aplicar_desconto, preco_original, desconto_porcentagem)
print(f"Preço final: R$ {preco_final:.2f}")

Neste exemplo, as funções calcula_preco_com_desconto e calcula_preco_sem_desconto são responsáveis por calcular o preço do produto com ou sem desconto, respectivamente. A função calcular_desconto decide se deve aplicar o desconto ao preço original e chama as outras duas funções para calcular o novo preço.

Essas funções são independentes e não dependem uma da outra, facilitando a manutenção e a evolução do código em longo prazo. Além disso, elas permitem que os desenvolvedores entendam facilmente como funciona o fluxo de dados no código e identifiquem as causas raiz dos problemas.

Boas práticas

Encapsule lógica de negócios em funções puras

  • Cada função deve ter uma responsabilidade clara e específica.
  • Evite funções longas e complexas, preferindo dividir a lógica em várias funções menores.
  • Use nomes de funções descritivos para facilitar a compreensão do código.

Isso é mais fácil quando as funções são independentes

  • Cada função deve ter um único ponto de entrada e saída, evitando a dependência entre elas.
  • Use parâmetros para fornecer dados necessários às funções, ao invés de acessar variáveis globais.
  • Isso facilita a manutenção do código, pois mudanças em uma função não afetam outras.

Armadilhas comuns

Funções que dependem de estados globais são complicadas de entender e manter

  • Quanto mais as funções dependerem de variáveis globais, maior será o esforço para entender e debugar o código.
  • Use parâmetros em vez de variáveis globais para fornecer dados às funções.

Evite cálculos complexos dentro das funções

  • Cálculos complexos podem tornar as funções difíceis de manter, pois pequenas alterações no cálculo podem causar problemas.
  • Divida cálculos complexos em várias funções menores, cada uma responsável por uma parte específica do processo.

Conclusão

Em resumo, as funções puras são fundamentais para tornar seu código mais testável e manutenível ao longo do tempo. Ao encapsular lógica de negócios em funções independentes, você garante que cada uma tenha uma responsabilidade clara e específica, facilitando a compreensão e identificação de problemas.

Para levar essas práticas para o próximo nível, é recomendável explorar técnicas como injeção de dependências, design de tipo de dados robusto e uso eficaz de testes unitários. Além disso, revisitar códigos existentes com foco em refatorar funções longas e complexas em unidades menores e mais manejáveis é um passo crucial para melhorar a qualidade do seu código.

Ao seguir essas diretrizes, você poderá desenvolver soluções de software mais escaláveis, fáceis de manter e que minimizam o risco de bugs críticos.

Referências

  • Beck, Kent. Test-Driven Development by Example. Disponível em: https://www.amazon.com.br/Test-Driven-Development-Example-Kent/dp/0321146530. Acesso: 2024.
  • Fowler, Martin. Refatoração: Improving the Design of Existing Code. Disponível em: http://martinfowler.com/books/refactoring.html. Acesso: 2024.
  • Fowler, Martin. Patterns of Enterprise Application Architecture. Disponível em: https://www.amazon.com.br/Patterns-Enterprise-Application-Architecture-Martin/dp/0596005233. Acesso: 2024.
  • Newkirk, Pramod e Frost, Michael. Frameworks by Example. Disponível em: https://www.amazon.com.br/Frameworks-Example-Pramod-Newkirk/dp/0321117850. Acesso: 2024.
  • Wikipedia. Funcionalidade pura. Disponível em: https://pt.wikipedia.org/wiki/Funcionalidade_pura. Acesso: 2024.