DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

LOD (Law of Demeter)

Introdução

O nome do princípio vem do próprio nome do projeto de pesquisa (que remete a Deméter, deusa grega da agricultura — a metáfora era "cultivar" software que cresce de forma incremental e adaptável, não do princípio de acoplamento em si).

O projeto Demeter investigava como reduzir o custo de manutenção de sistemas orientados a objetos observando que boa parte das mudanças de software quebrava código muito distante do ponto onde a mudança real acontecia — um efeito cascata causado por classes que conheciam profundamente a estrutura interna de outras classes.

Essa observação foi confirmada empiricamente alguns anos depois: em 1994, Chidamber & Kemerer publicaram as famosas métricas CK (A Metrics Suite for Object Oriented Design), nas quais o CBO (Coupling Between Objects) — quão acoplada uma classe é a outras — se tornou um dos preditores mais fortes de defeitos e esforço de manutenção em estudos empíricos posteriores de engenharia de software. Ou seja: a intuição por trás da Law of Demeter (menos acoplamento = menos bugs ao mudar código) tem respaldo em dados de décadas de pesquisa empírica em qualidade de software.

Definição

Também chamada de "Principle of Least Knowledge", a formulação clássica é:

Um método M de um objeto O só deve chamar métodos de:

  1. O próprio O
  2. Os parâmetros recebidos por M
  3. Qualquer objeto que M crie/instancie internamente
  4. Os componentes diretos de O (seus atributos/campos)
  5. Variáveis globais acessíveis a O

Resumo popular: "use apenas um ponto" — evite código como:

pedido.getCliente().getEndereco().getCidade().getNome()
Enter fullscreen mode Exit fullscreen mode

Isso é conhecido como "train wreck" (trem de vagões) — cada . é um vagão acoplado ao anterior. Se a estrutura interna de Cliente ou Endereco mudar, todo código que fez essa travessia quebra, mesmo estando em um módulo completamente não relacionado.

Porque isso importa na prática?

Quando o método M faz objeto.getX().getY().metodo(), ele passa a depender da estrutura interna de X e Y, não só da interface pública de O. Isso cria três problemas medidos empiricamente pela literatura de engenharia de software:

  • Acoplamento implícitoM está acoplado a classes que nem sabia que existiam (as intermediárias).
  • Fragilidade: uma refatoração interna em qualquer elo da cadeia propaga quebras para código distante e não óbvio.
  • Dificuldade de teste: para testar M, você precisa mockar toda a cadeia de objetos (ClienteEndereco, etc.), não só o Pedido.

Exemplo prático

Imagina duas coisas que existem no seu sistema:

  • Um Cliente (a pessoa)
  • Uma Carteira (que pertence ao cliente e guarda o dinheiro dele)
class Carteira {
  saldo: number;
  remover(valor: number) {
    this.saldo-= valor;
  }
}

class Cliente {
  carteira: Carteira;
}
Enter fullscreen mode Exit fullscreen mode

Jeito 1 (problemático)

Em outra parte do código, alguém quer cobrar R$ 50 do cliente. Ele escreve assim:

cliente.carteira.remover(50);
Enter fullscreen mode Exit fullscreen mode

Repare o que essa linha está fazendo: ela pula dentro do Clientepega a Carteira dele, e mexe na Carteira diretamente.

Quem escreveu essa linha precisou saber duas coisas que não são da conta dele:

  1. Que Cliente tem uma carteira.
  2. Que a Carteira tem um método remover.

Isso é o problema: código de fora está manipulando a carteira do cliente como se fosse dono dela.

Jeito 2 (correto)

class Cliente {
  carteira: Carteira;

  pagar(valor: number) {
    this.carteira.remover(valor);
  }
}
Enter fullscreen mode Exit fullscreen mode

E quem quer cobrar o cliente agora escreve só:

cliente.pagar(50);
Enter fullscreen mode Exit fullscreen mode

Pronto. Quem chamou essa linha não precisa saber que existe uma carteira. Só precisa saber que "cliente sabe pagar".

Por que o Jeito 2 é melhor? (o teste real)

Imagina que amanhã o sistema muda: em vez de Carteira, o cliente passa a ter um CartaoCredito.

  • No Jeito 1: toda parte do código que tinha escrito cliente.carteira.remover(50) quebra, porque carteira não existe mais. Você precisa caçar essas linhas espalhadas pelo projeto inteiro e corrigir uma por uma.
  • No Jeito 2: só a classe Cliente precisa mudar por dentro (trocar carteira.remover por cartao.cobrar, por exemplo). Quem chamava cliente.pagar(50) continua funcionando sem nenhuma alteração.
// Jeito 2 depois da mudança interna — ninguém de fora percebeu nada
class Cliente {
  cartao: CartaoCredito;

  pagar(valor: number) {
    this.cartao.cobrar(valor); // só mudou aqui dentro
  }
}
Enter fullscreen mode Exit fullscreen mode

A regra, em uma frase

Se para fazer uma coisa você precisa escrever algo.parteDeDentro.outraParteDeDentro, é sinal de que você devia ter perguntado pro algo se ele mesmo pode fazer isso por você.

Ou seja: cada . a mais na frase é uma aposta de que aquela estrutura interna nunca vai mudar. O princípio existe para você parar de apostar nisso.

Top comments (0)