Código como este é um "acidente de trem" (em violação da Lei de Demeter)?

23

Navegando por algum código que escrevi, me deparei com a seguinte construção que me fez pensar. À primeira vista, parece bastante limpo. Sim, no código real, o getLocation()método tem um nome um pouco mais específico que descreve melhor exatamente qual local ele obtém.

service.setLocation(this.configuration.getLocation().toString());

Nesse caso, serviceé uma variável de instância de um tipo conhecido, declarada dentro do método this.configurationé transmitido ao construtor da classe e é uma instância de uma classe implementando uma interface específica (que exige um getLocation()método público ). Portanto, o tipo de retorno da expressão this.configuration.getLocation()é conhecido; especificamente neste caso, é a java.net.URL, enquanto service.setLocation()quer a String. Como os dois tipos String e URL não são diretamente compatíveis, é necessário algum tipo de conversão para ajustar a estaca quadrada no orifício redondo.

No entanto , de acordo com a Lei de Demeter como citado em Clean Code , um método f na classe C só deve chamar métodos em C , objetos criados por ou passados como argumentos para f , e objetos realizada em variáveis de instância de C . Qualquer coisa além disso (a final toString()no meu caso particular acima, a menos que você considere um objeto temporário criado como resultado da própria invocação do método, caso em que toda a Lei parece discutível) é desaprovada.

Existe um raciocínio válido por que uma chamada como a acima, dadas as restrições listadas, deve ser desencorajada ou até proibida? Ou eu estou apenas sendo muito cuidadoso?

Se eu fosse implementar um método URLToString()que simplesmente chama toString()um URLobjeto (como o retornado por getLocation()) passado a ele como parâmetro e retorna o resultado, eu poderia envolver a getLocation()chamada nele para obter exatamente o mesmo resultado; efetivamente, eu apenas moveria a conversão um passo para fora. De alguma forma isso seria aceitável? ( Parece -me, intuitivamente, que isso não deve fazer nenhuma diferença, pois tudo o que faz é mudar um pouco as coisas. No entanto, seguindo a letra da Lei de Deméter, como citado, seria aceitável, pois eu estaria operando diretamente em um parâmetro para uma função.)

Faria alguma diferença se isso fosse algo um pouco mais exótico do que chamar toString()um tipo padrão?

Ao responder, lembre-se de que alterar o comportamento ou API do tipo de que a servicevariável é não é prático. Além disso, por uma questão de argumento, digamos que alterar o tipo de retorno getLocation()também seja impraticável.

um CVn
fonte

Respostas:

34

O problema aqui é a assinatura de setLocation. É digitado rigorosamente .

Para elaborar: Por que isso esperaria String? A Stringrepresenta qualquer tipo de dados textuais . Potencialmente, pode ser qualquer coisa, menos um local válido.

De fato, isso coloca uma pergunta: o que é um local? Como eu sei sem analisar seu código? Se fosse assim, URLeu saberia muito mais sobre o que esse método espera.
Talvez faça mais sentido ser uma classe personalizada Location. Ok, eu não saberia a princípio o que é isso, mas em algum momento (provavelmente antes de escrever this.configuration.getLocation(), levaria um minuto para descobrir o que esse método retorna).
É verdade que, nos dois casos, preciso procurar outro lugar para entender o que é esperado. No entanto, no último caso, se eu entender, o que Locationé, posso usar sua API, no caso anterior, se eu entender, o que Stringé (o que pode ser esperado), ainda não sei o que sua API espera.

No cenário improvável, que um local é qualquer tipo de dado textual, eu o reinterpretaria para qualquer tipo de dado que tenha uma representação textual . Dado o fato, que Objecttem um toStringmétodo, você pode seguir com isso, embora isso exija bastante salto de fé dos clientes do seu código.

Além disso, você deve considerar que este é o Java que você está falando, que tem muito poucos recursos por design. É isso que está forçando você a realmente ligar toStringpara o final.
Se você usar o C #, por exemplo, que também é estaticamente digitado, poderá realmente omitir essa chamada definindo o comportamento de uma conversão implícita .
Em linguagens de tipo dinâmico, como o Objective-C, você também não precisa da conversão, porque, desde que o valor se comporte como uma string, todo mundo fica feliz.

Pode-se argumentar que a última chamada para toStringé menos uma chamada do que apenas o ruído gerado pela demanda explícita de Java. Você está chamando um método que qualquer objeto Java possui, portanto, na verdade, você não codifica nenhum conhecimento sobre uma "unidade distante" e, portanto, não viola o Princípio do Menos Conhecimento. Não há como, não importa o que getLocationretorne, que ele não tenha um toStringmétodo.

Mas, por favor, não use strings, a menos que sejam realmente a escolha mais natural (ou a menos que você esteja usando um idioma, que nem tenha enumerações ... esteja lá).

back2dos
fonte
Eu tenderia a concordar com você, mas ele já disse que não pode modificar a API de serviço.
Jhocking
1
@jhocking: Ele não disse que não pode. Ele disse que é impraticável. Discordo. Tentar aplicar as práticas recomendadas ao código que soluciona falhas no design da API realmente só faz sentido se essas soluções alternativas forem a única opção possível. No entanto, aqui, definir a API diretamente é a melhor opção. A remoção de deficiências é sempre preferível a contornar elas.
back2dos 21/09/11
bem +1 de qualquer maneira para "é menos uma chamada que realmente apenas ruído gerado pela demanda de Java para explicitação"
jhocking
1
Eu daria este +1 mesmo que apenas para "digitado em seqüência". No meu código, tento passar tipos que expressam significado / intenção o máximo possível, mas, ao trabalhar com bibliotecas e APIs de terceiros, às vezes você fica meio preso usando o que os autores dessa API decidiram. E o IIRC, um local pode realmente ser "qualquer tipo de dados textuais", mas para a implementação em que estou trabalhando, um local que não seja de URL é totalmente sem sentido.
um CVn
1
Quanto à alteração da API; este é um software de computador, por isso é quase sempre possível mudar as coisas. (Se nada mais, você sempre pode escrever uma camada de abstração.) No entanto, às vezes, isso envolve muito esforço, tanto a curto quanto a longo prazo, para ser justificável. Então, pode ser perfeitamente possível fazer essas alterações, mas ainda não é prático.
um CVn
21

A lei de Deméter é uma diretriz de design, não uma lei a ser seguida religiosamente.

Se você achar que suas classes são suficientemente dissociadas, não há nada errado com a linha, this.configuration.getLocation()especialmente se, como você diz, não for prático alterar outras partes da API.

Tenho certeza de que o cliente ficará perfeitamente feliz, mesmo que você faça a Lei de Deméter em pedaços, desde que você entregue o que ele quer e no prazo. O que não é uma desculpa para criar software ruim, mas um lembrete para ser pragmático ao desenvolver software.

Trasplazio Garzuglio
fonte
1
Como this.configurationé uma variável de instância de um tipo de interface conhecida, chamar um método definido por essa interface parece bom, mesmo de acordo com uma interpretação estrita. Sim, eu sei que é uma diretriz, assim como KISS, SOLID, YAGNI e assim por diante. Existem muito poucas, se é que existem, "leis" (no sentido jurídico) no desenvolvimento geral de software.
a CVn
4
+1 por ser pragmática ;-)
Treb
1
Eu não acho que a Lei do Dementador deveria se aplicar em um caso como esse - a configuração é basicamente um contêiner. Em todas as APIs com as quais trabalhei, procurar dentro de contêineres é um comportamento normal e esperado.
Loren Pechtel 13/08/2015
2

A única coisa que consigo pensar em não escrever código como esse é o que se this.configuration.getLocation()retorna nulo? Depende do código circundante e do público-alvo usando esse código. Mas, como Marco diz - a lei de Deméter é uma regra de ouro - é bom seguir, mas não quebre as costas desnecessariamente.

Martyn
fonte
Se this.configuration.getLocation()retornar nulo, então (a) provavelmente nunca teríamos chegado tão longe, ou (b) algo catastrófico aconteceu nesse ínterim; nesse caso, eu gostaria que o código falhasse. Portanto, embora esse seja definitivamente um ponto válido em geral, nesse caso em particular, é bastante seguro dizer que não se aplica. Além disso, ao redor de tudo isso e muito mais, há um manipulador de exceção projetado especificamente para lidar com esse tipo de falhas inesperadas.
um CVn
2

Seguir estritamente a Lei de Demeter significa que você deve implementar um método no objeto de configuração como:

function getLocationAsString() {
  return getLocation().toString();
}

mas pessoalmente eu não me incomodaria porque esta é uma situação tão pequena e, de qualquer forma, suas mãos estão meio empatadas por causa da API que você não pode mudar. As regras de programação são sobre o que você deve fazer quando tem uma escolha, mas às vezes você não tem uma escolha.

jhocking
fonte
1
É o mesmo sugerido aqui: c2.com/cgi/wiki?TrainWreck "Crie um método que represente o comportamento desejado e informe ao cliente o que fazer. Isso segue o princípio" diga, não pergunte "."
heltonbiker