Construa software sustentável��Minimizando a mutabilidade para um código mais confiável e manutenível
Uiratan Cavalcante
2024
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”
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
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”
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”
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”
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
Diminuindo a mutabilidade
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
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
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
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
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
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
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
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?
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)
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
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
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;
}
Event Sourcing
Reconstituição do estado atual do sistema
Armazenar todas as mudanças de estado como uma sequência de eventos
Command Query Separation (CQS)
Cada método é um comando ou uma consulta
A função que devolva um valor não deve ter efeitos colaterais observáveis
Refatoração
Minimizar o número de objetos mutáveis
Objetos Mutáveis são um code smell
Obrigado!
Bibliografia