1 of 25

Construa software sustentável��Minimizando a mutabilidade para um código mais confiável e manutenível

Uiratan Cavalcante

2024

2 of 25

Imutabilidade x Mutabilidade

String s = “x";

s = s.concat(“y");

String t = s;

t = t + “z";

StringBuilder sb = new SB("x");

sb.append(“y");

StringBuilder tb = sb;

tb.append(“z");

StringBuilder (Mutável)

String (Imutável)

StringBuilder

“x”

“xy”

“xyz”

sb

tb

String

“x”

s

String

“xy”

t

String

“xyz”

3 of 25

Aliasing

Várias dependências do mesmo objeto

É a essência dos riscos que examinaremos

Múltiplas referências ao mesmo objeto mutável

StringBuilder

“x”

“xy”

“xyz”

sb

tb

É o que torna os tipos mutáveis arriscados

Possivelmente situados em pontos distantes do código

4 of 25

Risco: Passagem de valores mutáveis

List<Integer> saldosContas = new ArrayList<>(Arrays.asList(300, 500, -500));

System.out.println(somarSaldosPositivos(saldosContas)); // Saída: 800

System.out.println(somarSaldos(saldosContas)); // Saída: 300

// Função pura: não altera a lista original

public static int somarSaldos(List<Integer> lista) {

  return lista.stream().mapToInt(Integer::intValue).sum();

}

saldosContas

lista

ArrayList

“300, 500, -500”

5 of 25

Risco: Passagem de valores mutáveis

List<Integer> saldosContas = new ArrayList<>(Arrays.asList(300, 500, -500));

System.out.println(somarSaldosPositivos(saldosContas)); // Saída: 800

System.out.println(somarSaldos(saldosContas)); // Saída: 800

// Função pura: não altera a lista original

public static int somarSaldos(List<Integer> lista) {

  return lista.stream().mapToInt(Integer::intValue).sum();

}

// Função impura: altera a lista original

public static int somarSaldosPositivos(List<Integer> lista) {

  lista.removeIf(n -> n < 0); // Remove valores negativos

  return somarSaldos(lista);

}

saldosContas

lista

ArrayList

“300, 500, -500”

“300, 500”

6 of 25

Risco: Retorno de valores mutáveis

public static Date inicioAnoJudicial() {

  if (dataInicial == null)

    dataInicial = calculaInicioDoAnoJudicial();

  return dataInicial;

}

private static Date dataInicial = null;

public static void cadastrarAudiencia() {

  Date dataAudiencia = inicioAnoJudicial();

  // audiência marcada para daqui 1 mês

  dataAudiencia.setMonth(dataAudiencia.getMonth() + 1);

}

dataInicial

dataAudiencia

Date

“15/01/2024”

“15/02/2024”

7 of 25

Efeitos Colaterais

Mudanças em um objeto afetam outras partes do sistema de maneiras inesperadas

1

Múltiplas threads podem tentar

modificar o mesmo objeto simultaneamente

Programação Concorrente

Complexidade Aumentada em APIs

Quem tem a responsabilidade

de modificar ou não o objeto?

Podem ser especialmente

difíceis de encontrar e corrigir

Bugs

Raciocínio e Depuração

Complica o raciocínio sobre o estado do programa, tornando a depuração mais difícil

Problemas no uso excessivo de mutabilidade

8 of 25

Diminuindo a mutabilidade

9 of 25

Diminuindo a mutabilidade

Se componentes mutáveis são recebidos ou retornados, copie defensivamente esses componentes

public static int somarSaldosPositivos(List<Integer> lista) {

// Criação de cópia defensiva

  List<Integer> copiaLista = new ArrayList<>(lista);

// Remove valores negativos na cópia

  copiaLista.removeIf(n -> n < 0);

  return somarSaldos(copiaLista);

}

public static Date inicioAnoJudicial() {

// ...

// Retorna uma cópia defensiva

  return new Date(dataInicial.getTime());

}

Cópias Defensivas

10 of 25

Diminuindo a mutabilidade

public class Endereco {

  public String rua;

  public CEP cep; // mutável

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = cep;

  }

�  // getters

  // setter

}

Regras para Classes Imutáveis

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

11 of 25

Diminuindo a mutabilidade

public final class Endereco {

  public String rua;

  public CEP cep; // mutável�

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = cep;

  }

�  // getters

  // setter

}

Regras para Classes Imutáveis

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

12 of 25

Diminuindo a mutabilidade

public final class Endereco {

  public final String rua;

  public final CEP cep; // mutável

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = cep;

  }

�  // getters

  // setter

}

Regras para Classes Imutáveis

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

13 of 25

Diminuindo a mutabilidade

public final class Endereco {

  public private final String rua;

  public private final CEP cep; // mutável

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = cep;

  }

�  // getters

  // setter

}

Regras para Classes Imutáveis

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

14 of 25

Diminuindo a mutabilidade

public final class Endereco {

  public private final String rua;

  public private final CEP cep; // mutável

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = new CEP(cep.getCodigo());

  }

  public CEP getCep() {

    return new CEP(cep.getCodigo());

  }

�  // getters

  // setter

}

Regras para Classes Imutáveis

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

15 of 25

Diminuindo a mutabilidade

public final class Endereco {

  public private final String rua;

  public private final CEP cep; // Imutável

  public Endereco(String rua, CEP cep) {

    if (rua == null || rua.isEmpty()) {

      throw new IllegalArgumentException(“msg");

    }

    this.rua = rua;

    this.cep = cep;

  }

  public CEP getCep() {

    return cep;

  }

�  // getters

  // setter

}

Sem setters

Não permitir herança

Não permitir reatribuções

Diminuir a visibilidade dos atributos

Acesso exclusivo aos componentes mutáveis

Regras para Classes Imutáveis

16 of 25

Viabilidade de imutabilidade

A imutabilidade é viável?

A imutabilidade pode ser praticável se concessões forem feitas

Temos acesso a armazenamento infinito e velocidade de processamento infinita?

Podemos eliminar as atualizações de variáveis?

17 of 25

Regras mais restritas do que o necessário

Todos os seus campos devem ser constantes (finais)

Nenhum método pode produzir uma mudança externamente visível no estado do objeto.

Resistir ao impulso de escrever um setter para cada getter.

As classes devem ser imutáveis, a menos que hajam razões para que sejam mutáveis

Nenhum método pode modificar o objeto (setters)

18 of 25

Static Factories

// Evitar instâncias diretas

private EnderecoFabrica(String rua, CEP cep) {

this.rua = rua;

  this.cep = new CEP(cep.getCodigo());

}

// Método de fábrica estático padrão

public static EnderecoFabrica of(String rua,

CEP cep) {

validarParametros(rua, cep);

  return new EnderecoFabrica(rua, cep);

}

// Método de fábrica estático adicional

public static EnderecoFabrica of(String rua,

String codCep) {

validarParametros(rua, cep);

return of(rua, new CEP(codigoCep));

}

Uma classe não se deve deixar subdividir em subclasses

Adicionar static factories públicas no lugar dos construtores públicos

Mais legibilidade (nomes mais descritivos) com os static factories e cache, por ex

19 of 25

Segregação de mutabilidade

Componentes Imutáveis

Os componentes imutáveis se comunicam com um ou mais dos outros componentes que não são puramente funcionais

Segregar a aplicação em componentes mutáveis e imutáveis.

Componente

Componente

Componente

Componente Mutável

Memória Transacional

20 of 25

Value Objects ou Immutable Types

Preferir Objetos de Valor a Entidades

Características

• Mede, quantifica ou descreve uma coisa no domínio.

• Pode ser manter imutável.

• Modela um todo conceitual com atributos relacionados

• É completamente substituível

• Pode ser comparado com outros VO

• Comportamento Sem Efeitos Colaterais

public final class Endereco {

  private final String rua;

  private final CEP cep;

�  public Endereco(String rua, CEP cep) {

// invariantes�    this.rua = rua;

    this.cidade = cidade;

    this.cep = cep;

  }

�  public CEP getCep() {

    return cep;

  }

21 of 25

Event Sourcing

Reconstituição do estado atual do sistema

  • Eventos como Fonte da Verdade: Em vez de atualizar um estado diretamente, cada mudança é registrada como um evento.
  • Reconstituição do Estado: O estado atual do sistema pode ser reconstruído ao aplicar todos os eventos em ordem.
  • Imutabilidade: Os eventos registrados são imutáveis. Uma vez que registrado, não será alterado. Integridade e consistência dos dados ao longo do tempo.

Armazenar todas as mudanças de estado como uma sequência de eventos

22 of 25

Command Query Separation (CQS)

  • Separação de Responsabilidades - Um método ou modifica o estado ou retorna informações
  • Redução de Efeitos Colaterais - Menos bugs causados por alterações inesperadas no estado do sistema
  • Previsibilidade - Ajuda a entender o código de forma mais clara, métodos de consulta não terão efeitos colaterais
  • Facilidade de Teste - Métodos de consulta não alteram o estado
  • Código mais Limpo e Manutenível - Facilita a leitura e compreensão do código

Cada método é um comando ou uma consulta

A função que devolva um valor não deve ter efeitos colaterais observáveis

23 of 25

Refatoração

Minimizar o número de objetos mutáveis

  • Remove Setting Method
  • Replace Constructor with Factory Function
  • Em APIs, Separate Query from Modifier (CQS)
  • Replace Derived Variable with Query - em dados mutáveis que podem ser calculados em outros lugares pois são uma fonte de confusão, bugs e são desnecessários

Objetos Mutáveis são um code smell

24 of 25

Obrigado!

uiratan@gmail.com

www.linkedin.com/in/uiratan

github.com/uiratan

linktr.ee/uiratancavalcante

25 of 25

Bibliografia