Pular para o conteúdo principal

Side-Effect-Free Functions

Descrição​

Side-Effect-Free Functions propõe separar as operações em duas categorias: comandos, que modificam o estado, e consultas, que apenas devolvem um resultado. Uma função é livre de efeitos colaterais quando calcula e retorna um valor sem alterar nada observável. Os comandos devem ser simples e não devolver dados de domínio.

A lógica complexa é colocada em funções puras, de preferência em Value Objects, que são imutáveis. Assim, uma operação que combina valores devolve um novo objeto em vez de modificar o existente. Quando o cálculo está em uma Entity, pode-se extrair parte dele para um Value Object e deixar a Entity apenas aplicando o resultado.

O efeito é uma redução forte da carga mental: o desenvolvedor pode chamar e compor funções sem medo de alterar o estado de outro objeto, e os testes ficam simples.

Problemas que resolve​

  • Operações que misturam cálculo e mudança de estado, difíceis de prever e de testar.
  • Efeitos colaterais em cascata, em que chamar um método altera objetos que o chamador não imaginava.
  • Dificuldade de reaproveitar um cálculo sem disparar sua mudança de estado.
  • Bugs causados por ordem de chamada e por estado compartilhado mutável.

Quando usar​

  • Em cálculos complexos do domínio (preços, cores, prazos, taxas).
  • Ao modelar conceitos como Value Objects, que devem ser imutáveis.
  • Quando uma consulta é chamada em muitos lugares e precisa ser segura de compor.

Quando evitar​

  • Em objetos grandes cuja cópia a cada operação é cara e comprovadamente um gargalo.
  • Quando o domínio é por natureza uma sequência de mudanças de estado (por exemplo, um processo longo): ali o comando é legítimo e deve ser explícito, não escondido.

Trade-offs​

GanhoCusto / Risco
Testes simples, sem preparação de estadoCria novos objetos a cada operação
Composição segura e raciocínio localExige disciplina para separar comandos de consultas
Cache e paralelismo facilitadosPode ser pouco natural em bibliotecas mutáveis
  • Consistência: menos pontos onde o estado pode ser corrompido.
  • Desempenho: a imutabilidade pode custar alocações; meça antes de abrir mão dela.
  • Comandos: mantenha-os pequenos e com efeito claramente nomeado.

Diagrama​

Paint.mixIn é um comando que muda o estado, enquanto PigmentColor.mixedWith é uma função pura que calcula a nova cor sem alterar nenhum objeto.

Exemplo​

Em um sistema de tintas, a regra de mistura de pigmentos é complexa. Ela fica em PigmentColor, um Value Object imutável; o comando em Paint apenas aplica o resultado.

public final class PigmentColor {
private final double r, g, b;

public PigmentColor(double r, double g, double b) {
this.r = r; this.g = g; this.b = b;
}

// Função pura: devolve nova cor, nada é modificado
public PigmentColor mixedWith(PigmentColor other, int w1, int w2) {
int total = w1 + w2;
return new PigmentColor(
(r * w1 + other.r * w2) / total,
(g * w1 + other.g * w2) / total,
(b * w1 + other.b * w2) / total);
}
}

public class Paint {
private PigmentColor color;
private int volume;

// Comando: simples, só aplica o resultado
public void mixIn(Paint other) {
color = color.mixedWith(other.color, volume, other.volume);
volume += other.volume;
}
}

Padrões relacionados​

  • Value Object — lugar natural para funções puras, por serem imutáveis.
  • Entity — mantém os comandos, delegando o cálculo a valores.
  • Closure of Operations — operações que devolvem o mesmo tipo combinam bem com funções puras.
  • Assertions — documentam os efeitos dos comandos que sobram.
  • Intention-Revealing Interfaces — o nome deve deixar claro se é comando ou consulta.
  • Money (PoEAA) — valor imutável com operações puras.

Referência​