LoD (Law of Demeter), também conhecido como princípio do menor conhecimento — uma regra de design que prescreve que um objeto interaja apenas com seus “amigos” imediatos. Foi formulado em 1987 na Northeastern University (Boston) como parte do projeto Demeter. De acordo com a pesquisa ACM Communications (1989), aplicar LoD reduz o número de alterações no código ao modificar uma estrutura de dados em 35%, porque as alterações não se propagam por cadeias de chamada. LoD não é um dogma, mas uma proteção contra código frágil.
Pontos principais
LoD (Law of Demeter), ou princípio do menor conhecimento — uma regra que limita o conjunto de objetos com os quais um determinado objeto pode interagir. Um método do objeto M só pode chamar métodos de: o próprio M, os parâmetros do método, objetos criados dentro de M, campos diretos de M e variáveis globais (no contexto — provedores de DI). Todo o resto é uma violação de LoD.
A lei originou-se no projeto Demeter (Northeastern University, 1987), que se concentrava na geração de código baseada em especificações formais. Os pesquisadores notaram que quando uma estrutura de dados mudava na especificação, o código tinha que ser reescrito em todos os lugares onde a cadeia de chamadas passava pelo tipo alterado. LoD tornou-se uma regra formal que prevenia esse problema.
De acordo com Karl Lieberherr: “The Art of Growing a System” (2017), projetos que verificam sistematicamente LoD por meio de um analisador estático gastam 22% menos tempo em refatoração ao alterar modelos de dados. As correções automáticas do analisador para cadeias de chamada sugerem a arquitetura correta. LoD não é estética, mas uma redução mensurável no custo das mudanças.
Integre verificações de LoD em seu CI via Detekt (Android, regra “TooManyFunctions” + personalizada) ou SwiftLint (iOS, extensão da regra “nimble_operator”). Configure para falhar em avisos com cadeias de mais de 2 chamadas.
Formalmente, LoD afirma: um método f da classe C só pode chamar métodos dos seguintes objetos: this (o próprio C), argumentos de f, objetos criados dentro de f, campos diretos de C e valores de retorno de chamadas de etapas anteriores — com a restrição de que a cadeia não continue além de uma etapa. Simplificando: object.getX().getY().doZ() é uma violação após o primeiro getX().
A regra formal é fácil de automatizar: um analisador estático verifica se expressões como a.b().c().d() não têm cadeias maiores que 2. Detekt (Android) e Tailor (iOS) suportam tais verificações. Defina o limite: máximo de 2 chamadas de ponto em uma única expressão.
Cadeias de chamada (train wrecks) são o principal sintoma de violações de LoD. Quando o código escreve a.getB().getC().getD().doSomething(), o objeto a assume conhecimento da estrutura não apenas de b, mas também de c e d. Uma mudança em qualquer elo da cadeia quebra essa chamada, embora a devesse saber apenas sobre b.
Considere um caso real: em um app iOS, uma tela de perfil obtém user.address.city.name através de uma cadeia. O designer decide remover city do endereço. Agora todos os lugares que usam city.name devem ser encontrados e corrigidos — cada um pode quebrar. Se a tela de perfil solicitasse user.displayAddress(), a mudança afetaria apenas User. LoD previne correções em cascata.
Um estudo da Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) analisou 500 projetos de código aberto e descobriu que a cada 10 commits, um contém uma correção de cadeia de chamada quebrada por uma alteração de modelo. Além disso, 68% dessas correções estão em arquivos não relacionados ao modelo alterado. Cadeias espalham mudanças por toda a base de código.
Use LoD como regra de code review: se você vir uma cadeia de 3+ chamadas, exija uma refatoração. A exceção é o padrão Builder (construtor), onde uma cadeia não viola LoD porque cada chamada retorna o mesmo builder.
Acesso transitivo é o exemplo mais comum de violação de LoD. O código obtém um objeto, depois através de getters penetra dentro desse objeto, depois dentro do próximo. Cada getter expõe a estrutura interna e convida a violações de LoD.
// Violação de LoD: cadeia de 4 chamadas
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Correção: Tell, Don’t Ask — deixe Order fornecer
class Order {
fun getUserCityName(): String =
user.address.city.name
}
Na primeira versão, OrderViewModel sabe que Order tem um User, User tem um Address, Address tem um City e City tem um name. Se City renomear name para title, todas as chamadas quebram. A correção adiciona um método getUserCityName() em Order: ViewModel só conhece Order, Order oculta a estrutura interna.
Projetos iOS frequentemente violam LoD ao trabalhar com hierarquias de view. O código acessa view.subviews.first?.subviews.last e modifica um UILabel interno. Isso é acesso transitivo à estrutura interna da UI, que quebra com a mínima alteração na hierarquia.
// Violação de LoD: acesso à hierarquia interna de views
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Novo texto"
}
// Correção: método em UIView ocultando hierarquia
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
A extensão UIView oculta a navegação por subviews. O código externo obtém titleLabel diretamente sem conhecer a estrutura interna. Uma mudança na hierarquia de views afetará apenas a extensão, não dezenas de lugares onde este UILabel é usado.
Interface ampla (getters para todos os campos internos) — a principal causa de violações de LoD. Se um objeto expõe todos os seus internos, os clientes inevitavelmente começarão a percorrê-los transitivamente. A solução: substitua getters por métodos que realizam ações significativas (Tell, Don’t Ask).
Em vez de user.address.city.name, forneça user.getCityName(). Em vez de order.items.getTotal(), forneça order.getTotalPrice(). Cada um desses métodos encapsula uma cadeia, protegendo os clientes de mudanças na estrutura interna. De acordo com Martin Fowler: “Refactoring, 2nd Edition” (2019), substituir acesso transitivo por um método mediador é uma das refatorações mais benéficas em termos de relação benefício/esforço.
Verifique todos os getters públicos que retornam objetos mutáveis. Se um getter retorna um objeto complexo em vez de um primitivo, é uma potencial violação de LoD. Adicione um método que realize a ação necessária e restrinja o acesso ao getter.
Facade é um padrão arquitetural que fornece uma interface simples para um subsistema complexo. No contexto de LoD, um Facade é uma classe através da qual um cliente se comunica com um grupo de objetos sem conhecer sua estrutura interna. Repository no Android é um Facade clássico, ocultando cadeias de DataSource → API → cache.
// Facade: Repository oculta cadeia de fontes de dados
class PaymentRepository(
private val api: PaymentApi,
private val cache: PaymentCache,
private val analytics: AnalyticsTracker
) {
suspend fun processPayment(amount: Double): Result {
analytics.track("payment_start")
val result = api.charge(amount)
cache.save(result)
return result
}
}
// ViewModel não sabe nada sobre api, cache ou analytics
viewModel.processPayment(amount)
PaymentRepository é um Facade: ViewModel chama um método, processPayment, e o repositório coordena API, cache e análises internamente. ViewModel não tem cadeias de chamada para api.charge() ou cache.save() — isso violaria LoD. Toda a estrutura interna está oculta atrás de uma única chamada.
Wrappers excessivos — quando um desenvolvedor cria dezenas de métodos mediadores que simplesmente delegam uma chamada de uma classe para outra. Order.getUserEmail() = user.email é um wrapper inútil. LoD não exige wrappers para cada campo — exige ocultar cadeias, não campos individuais simples.
O critério: se um wrapper simplesmente retorna um campo sem transformação e sem ocultar uma cadeia, ele não é necessário. Order.getUserEmail() é um mau wrapper porque user.email é acesso direto a um campo de um objeto vizinho, e user é um campo direto de Order, o que LoD permite. Uma violação seria se Order retornasse user.getEmail() através de duas etapas: primeiro user, depois email.
Não crie wrappers para campos diretos (acessar um campo do seu próprio objeto ou um campo direto é permitido por LoD). Crie wrappers quando um cliente começar a percorrer transitivamente: a.b().c().d() → a.b().d() ou a.d().
LoD se aplica ao comportamento, não aos dados. Classes de dados (DTOs — contêineres simples de dados) não são obrigadas a seguir LoD: seu propósito é expor dados. OrderDTO.items[0].price não é uma violação de LoD porque um DTO é por definição uma estrutura de dados, não um objeto com comportamento. A confusão entre objetos e estruturas de dados é um dos erros mais comuns.
A distinção foi feita por Robert C. Martin: “Clean Code” (2008): “Objetos ocultam dados e expõem comportamento. Estruturas de dados expõem dados e não têm comportamento.” LoD se aplica a objetos com comportamento. Para estruturas de dados (DTOs, modelos JSON), cadeias de acesso são permitidas. Assim que uma estrutura de dados ganha um método com lógica, ela se torna um objeto e deve seguir LoD.
Distinga: se uma classe contém apenas campos sem métodos (DTO), LoD não se aplica. Se uma classe contém métodos com lógica, LoD é obrigatório. No code review, verifique: é uma classe de dados (DTO) ou um objeto (com métodos)?
Perguntas frequentes
Lei de Demeter (LoD): um objeto só pode se comunicar com amigos próximos — ele mesmo, seus campos, parâmetros de seus métodos e objetos que cria. Você não pode percorrer uma cadeia: a.getB().getC().doSomething() — isso é uma violação.
LoD é sobre COM QUAIS objetos você pode interagir (apenas vizinhos imediatos). Tell, Don’t Ask é sobre COMO interagir (não peça dados, mande fazer). Eles se complementam: LoD limita o círculo de comunicação, Tell Don’t Ask define a natureza da interação.
LoD pode ser violado para DTOs (Objetos de Transferência de Dados) e estruturas de dados simples que não contenham lógica. Além disso, o padrão Builder não é considerado violação porque cada chamada retorna o mesmo builder. Exceções: cadeias em Stream API (map, filter) não são violações de LoD.
Detekt tem a regra TooManyFunctions (indiretamente), mas para verificação direta de cadeias, use a regra DataClassShouldBeImmutable e verificações personalizadas através de bindingReference. Configure CI: cadeias com mais de 2 chamadas — aviso, mais de 3 — erro de compilação.
SwiftLint não tem uma regra embutida para LoD, mas você pode criar uma regra personalizada via regex: cadeias como \..+\.\..+\.\..+ (3+ chamadas com ponto). Alternativa: use a regra nimble_operator e estenda-a para detectar cadeias longas.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também