O novo operador condicional nulo do C # 6.0 é contrário à Lei de Demeter?

29

A Lei de Demeter afirma o seguinte:

  • Cada unidade deve ter apenas conhecimento limitado sobre outras unidades: somente unidades "estreitamente" relacionadas à unidade atual.
  • Cada unidade deve conversar apenas com seus amigos; não fale com estranhos.
  • Fale apenas com seus amigos imediatos.

O C # 6.0 introduziu um novo operador chamado operador nulo-condicional . IMHO, facilita a codificação e melhora a legibilidade. Mas também facilita escrever mais códigos acoplados, pois é mais fácil navegar pelos campos de classe, já verificando a nulidade (algo como var x = A?.B?.C?.D?.E?.F?).

É correto afirmar que esse novo operador contraria a Lei de Demeter?

Arthur Rizzo
fonte
2
Por que você acredita que isso A?.B?.C?.D?.E?.F?iria violá-lo - LoD não é sobre quantos pontos e se o método de chamada tiver essas informações sobre a estrutura que não está violando seus pontos, essa chamada seria perfeitamente aceitável. Que tal código poderia violar LoD não é suficiente para dizer que todos os usos do que fazer violar LoD.
14
Lendo " A lei de Deméter não é um exercício de contagem de pontos "? Ele discute esse exemplo exato.
Outis
@outis: excelente leitura. Não estou dizendo que todo código na forma de X.Y.Z.W.Ué uma violação da "lei". Mas, na minha experiência em lidar com código, 90% das vezes é apenas um código acoplado feio.
Arthur Rizzo
2
@ArthurRizzo, mas isso não é um problema com o operador condicional nulo indo contra LoD. Esse é o código que está com defeito. O operador é apenas uma ferramenta para simplificar a leitura humana. O .?não mais viola LoD do que +ou -faz.
1
RC Martin distingue entre classes de dados puras e classes comportamentais. Se as Propriedades acessadas expõem dados internos de uma classe comportamental, o trecho certamente viola o LoD, mas isso não tem nada a ver com o operador nulo-condicional. De qualquer forma, as propriedades não são obrigadas a expor dados internos, o que pode ser um cheiro, mas não viola o LoD. Segundo RC Martin, o esquema pode ser absolutamente válido com classes de dados puras.
Paul Kertscher

Respostas:

44

É correto afirmar que esse novo operador contraria a Lei de Demeter?

Não *


* O operador condicional nulo é uma ferramenta na linguagem e na estrutura .NET. Qualquer ferramenta tem a capacidade de ser abusada e usada de maneiras que possam prejudicar a capacidade de manutenção de um determinado aplicativo.

Mas o fato de que uma ferramenta pode ser abusado, não significa necessariamente que ele tem de ser abusado, nem que a ferramenta viole qualquer princípio específico (s) que podem ser realizadas.

A Lei de Deméter e outras são diretrizes sobre como você deve escrever seu código. É direcionado para humanos, não para as ferramentas. Portanto, o fato de a linguagem C # 6.0 ter uma nova ferramenta nela não afeta necessariamente como você deve escrever e estruturar seu código.

Com qualquer nova ferramenta, é preciso avaliá-lo como ... se o cara que acaba mantendo o seu código será um psicopata violento ... . Observe novamente que essa é uma orientação para a pessoa que está escrevendo o código e não sobre as ferramentas que estão sendo usadas.

Comunidade
fonte
foo = new FiveDMatrix(); foo.get(0).get(0).get(0).get(0).set(0,1);seria bom (e não é pior do que foo[0][0][0][0][0] = 1) ... e muitas outras situações em que isso não viola o LoD.
@MichaelT Quando você começa a entrar em matrizes dessa dimensionalidade, parece que seria mais fácil tratar os índices como um vetor / tupla / matriz em si e deixar os internos da classe matriz se preocupar com como os dados são realmente armazenados. (Que, agora que penso nisso, é uma aplicação da Lei de Demeter, pelo menos no que se refere ao encapsulamento.)
JAB
(E, é claro, esse tipo de prática faz com que seja mais fácil de implementar corte multidimensional e tem algumas ferramentas de matriz realmente poderosas.)
JAB
1
@JAB Eu estava apenas tentando criar um exemplo. Provavelmente seria melhor Dom file = prase("some.xml"); file.get(tag1).getChild().get(tag2).getChild() ...- é uma questão de processar a estrutura de algum código idiota. Não é um estranho ... é apenas burro. O .?torna - se muito útil em tais estruturas.
10

Tipo de.

Se você estiver acessando apenas um ( a?.Foo), será equivalente a:

a == null ? null : a.Foo

o que a maioria das pessoas concorda que não é uma violação da Lei de Demeter. Nesse ponto, é apenas açúcar sintático para melhorar a legibilidade.

Qualquer coisa além disso, e provavelmente violaria a Lei de Demeter, e esse recurso tende a promover esse tipo de uso. Eu diria mesmo que o uso "bom" acima não é suficiente para justificar esse tipo de alteração no idioma, portanto, espero que ele tenha sido feito para oferecer suporte ao uso menos claramente bom.

Dito isto, vale lembrar que a Lei de Deméter não é uma lei em si, mas mais uma diretriz. Muitos códigos violam e funcionam bem. Às vezes, a simplicidade do design ou do código vale mais do que o risco representado pela violação da Lei de Demeter.

Telastyn
fonte
qualquer coisa além disso não necessariamente quebra o LoD, por exemplo, o padrão do construtor
jk.
@Telastyn: A nova sintaxe da linguagem que estamos a falar faz chamadas de método de apoio: a?.Func1(x)?.Func2(y) O operador nulo coalescentes é outra coisa.
Ben Voigt
@ BenVoigt - ah, eu estava saindo do artigo, que indicava que só funcionava com campos, propriedades e indexadores. Eu não tinha o MSVS2015 à mão para testar. Você está certo.
Telastyn
1
um? .Foo não é equivalente a um == nulo? null: a.Foo. O primeiro avalia uma única vez, o último avalia duas vezes. Isso poderia importar se a fosse um iterador.
Loren Pechtel 10/10
9

Não. Vamos considerar o operador por conta própria e o uso fortemente encadeado que você possui para ele.

Por si só, .?Adepende da mesma quantidade de conhecimento da classe que o valor esquerdo é e do tipo retornado pelo método como .A != nullfaz, viz. Ele precisa saber que a Apropriedade existe e retorna um valor que pode ser comparado null.

Só podemos argumentar que isso viola a lei de Demeter se as propriedades digitadas o fizerem. Não somos obrigados a ter Aum tipo concreto (seu valor pode ser de um tipo derivado). O acoplamento aqui é mínimo.

Agora vamos considerar var x = A?.B?.C?.D?.E?.F.

O que significa que Adeve ser de um tipo que possa ser nulo ou que possa ter uma Bpropriedade, que deve ser de um tipo que possa ser nulo ou que tenha uma Cpropriedade, e assim por diante até que o tipo da Epropriedade seja algo que possa ser nulo ou poderia ter uma Fpropriedade.

Em outras palavras, precisamos fazer isso com uma linguagem de tipo estaticamente ou ter aplicado uma restrição nos tipos que podem ser retornados se a digitação estiver frouxa. O C # na maioria dos casos usa digitação estática, portanto não alteramos nada.

Se tivéssemos, o código a seguir também violaria a lei:

ExplicitType x;
var b = A.B;
if (b == null)
  x = null;
else
{
  var c = b.C;
  if (c == null)
    x = null;
  else
  {
    var d = c.D;
    if (d == null)
      x = null;
    else
    {
      var e = d.E;
      if (e == null)
        x = null;
      else
        x = e.F;
    }
  }
}

Qual é exatamente o mesmo . Esse código que está usando o acoplamento de diferentes elementos precisa "conhecer" a cadeia completa do acoplamento, mas está usando um código que não viola a Lei de Deméter para fazê-lo, com cada unidade tendo um acoplamento bem definido com nas próximas.

Jon Hanna
fonte
3
Com +1, o novo operador é apenas um açúcar sintático para a receita amarga que você descreveu.
Ross Patterson
1
Bem, se um desenvolvedor escrever um código parecido com esse, acho mais fácil perceber que algo pode não estar certo. Eu sei que o operador é 100% de açúcar sintático, mas ainda assim, acho que as pessoas tendem a ficar mais confortáveis ​​escrevendo algo parecido com var x = A?.B?.C?.D?.E?.Ftodos os outros, mesmo que sejam os mesmos no final.
Arthur Rizzo
2
É mais fácil perceber que algo não está certo A?.B?.C?.D?.E?.Fporque há menos que pode estar errado; ou deveríamos estar tentando seguir Fesse caminho, ou não deveríamos, enquanto a forma mais longa poderia ter erros dentro dela e também o erro de não ser a coisa correta a fazer.
Jon Hanna
@ArthurRizzo Mas se você associa o tipo de código acima a violações de LoD, é fácil perdê-lo no caso em que não há verificação nula necessária e você pode fazer isso A.B.C.D. É muito mais simples para ter uma única coisa de olhar para fora (acesso à propriedade encadeado), em vez de duas coisas diferentes que dependem de um detalhe bastante irrelevante (null-checking)
Ben Aaronson
5

O objeto pode ser criado com a finalidade de encapsular comportamentos ou manter dados, e os objetos podem ser criados com a finalidade de serem compartilhados com códigos externos ou mantidos em sigilo por seus criadores.

Objetos criados com a finalidade de encapsular o comportamento (compartilhado ou não) ou para serem compartilhados com código externo (se eles encapsulam comportamento ou dados) geralmente devem ser acessados ​​por meio de sua interface de superfície. Quando os objetos de retenção de dados são criados para uso exclusivo de seu criador, no entanto, os motivos normais da Lei de Demeter para evitar o acesso "profundo" não se aplicam. Se parte de uma classe que armazena ou manipula dados no objeto é alterada de maneira a exigir o ajuste de outro código, será possível garantir que todo esse código seja atualizado porque - como observado acima - o objeto foi criado para o uso exclusivo de uma classe.

Enquanto eu acho que o. Talvez o operador pudesse ter sido melhor projetado, há situações suficientes em que os objetos fazem uso de estruturas de dados aninhadas que o operador possui muitos casos de uso que não violariam os princípios expressos pela Lei de Demeter. O fato de poder ser usado para violar o LoD não deve ser tomado como argumento contra o operador, pois não é pior que o "." operador a esse respeito.

supercat
fonte
Por que a Lei de Demeter não se aplica a objetos contendo dados?
Telastyn
2
@Telastyn: O objetivo do LoD é evitar problemas que possam surgir se um pedaço de código acessar objetos internos com os quais outros possam manipular ou se importar . Se nada mais no universo pudesse manipular ou se importar com o estado dos objetos internos, não há necessidade de se proteger contra esses problemas.
Supercat
Não tenho certeza se concordo. Não é que outras coisas possam modificar os dados, é que você está acoplando ao objeto contido por um caminho (essencialmente três pontos de acoplamento - os dois objetos e sua relação). Às vezes, esse acoplamento não será grande coisa, mas ainda me parece mal cheiroso.
Telastyn
@Telastyn: Seu ponto de vista sobre os caminhos é bom, mas acho que meu ponto é válido. O acesso a um objeto através de vários caminhos cria um acoplamento entre esses caminhos. Se algum acesso for por caminho raso, o acesso por caminho profundo também poderá causar acoplamentos indesejados. Se todo o acesso for através de um caminho profundo específico, no entanto, não haverá nada para esse caminho profundo ser associado.
Supercat
@Telastyn É perfeitamente bom percorrer uma estrutura de dados para chegar aos dados no fundo. Não é o mesmo que chamadas de método aninhado. Você às vezes são obrigados a conhecer uma estrutura de dados e como ela está aninhado, o mesmo não vai para objetos como um serviço e os seus próprios serviços aninhados / repositórios, etc.
Por Hornshøj-Schierbeck