Propriedades devem ter efeitos colaterais


19

As propriedades em C # devem ter efeitos colaterais, além de notificar uma alteração nos seus estados?

Vi propriedades usadas de várias maneiras diferentes. Das propriedades que carregam o valor na primeira vez em que são acessadas às propriedades que têm efeitos colaterais massivos, como causar um redirecionamento para uma página diferente.



1
Entre as descobertas de @ Job, por que devo evitar o uso de propriedades em c #? a opinião de Jeffrey Richter é muito relevante para esta questão.
rwong

@rwong Sim relevante, mas completamente sem sentido -, no entanto, deve ser lido (para estar ciente dos problemas que ele destaca). Pelo menos nesta questão, estou no campo de Skeet.
Apoorv Khurasia

Embora Isso vale para construtores, isso também é relevante: programmers.stackexchange.com/a/164255/1996
Jim G.

Respostas:


40

Suponho que você esteja falando de propriedades somente leitura ou, pelo menos , de getters de propriedades , pois um setter de propriedades , em quase todas as instâncias, terá efeitos colaterais. Caso contrário, não é muito útil.

Em geral, um bom design segue o princípio da menor surpresa . Não faça coisas que os chamadores não esperam que você faça, especialmente se essas coisas mudarem os resultados futuros .

Em geral , isso significa que getters de propriedades não devem ter efeitos colaterais.

No entanto , tenhamos cuidado com o que queremos dizer com "efeito colateral".

Um efeito colateral é, tecnicamente, qualquer modificação de estado. Pode ser um estado acessível ao público, ou ... pode ser um estado totalmente privado.

Carregadores preguiçosos / diferidos são um exemplo de estado quase exclusivamente privado. Contanto que não seja responsabilidade do chamador liberar esse recurso, você estará reduzindo a surpresa e a complexidade em geral usando a inicialização adiada. Normalmente, um chamador não espera sinalizar explicitamente a inicialização de uma estrutura interna . Portanto, a inicialização lenta não viola o princípio acima.

Outro exemplo é uma propriedade sincronizada. Para que um método seja seguro para threads, muitas vezes precisará ser protegido por uma seção crítica ou mutex. Entrar em uma seção crítica ou adquirir um mutex é um efeito colateral; você está modificando o estado, geralmente o estado global . No entanto, esse efeito colateral é necessário para evitar um tipo muito pior de surpresa - a surpresa de os dados serem modificados (ou pior, parcialmente modificados) por outro encadeamento.

Então, eu afrouxaria um pouco a restrição para o seguinte: As leituras de propriedades não devem ter efeitos colaterais visíveis ou efeitos colaterais que alterem sua semântica .

Se não houver uma maneira possível de um chamador ser afetado ou mesmo ter conhecimento de um efeito colateral, esse efeito colateral não causará nenhum dano. Se seria impossível escrever um teste para verificar a existência de um efeito colateral específico, ele é localizado o suficiente para rotular como um detalhe de implementação privada e, portanto, sem preocupação legítima para o mundo externo.

Mas tenha cuidado; como regra geral, você deve tentar evitar efeitos colaterais, porque muitas vezes o que você pensa ser um detalhe de implementação privada pode vazar inesperadamente e tornar-se público.


2
+1 - uma resposta completa agradável incluindo os casos mais complicados que transformam um "deve" em um "deveria ... exceto quando ...."
quickly_now

Outro exemplo de um efeito colateral aceitável em um getter: uma função de registro.
Loren Pechtel

5

Responderei sua pergunta com uma pergunta: O que acontece quando você modifica a propriedade Width de um formulário?

No topo da minha cabeça, isso irá:

  • Mude o valor do campo de apoio.
  • Dispare um evento.
  • Altere as dimensões do formulário, relate a alteração ao gerenciador de janelas e solicite uma nova pintura.
  • Notifique os controles no formato da alteração para que qualquer um que precise alterar o tamanho ou a posição quando o formulário for redimensionado possa fazê-lo.
  • Cause todos os controles que foram alterados para disparar eventos e informe ao gerenciador de janelas que eles precisam ser redesenhados.
  • Provavelmente outras coisas também.

(Aviso: sou um desenvolvedor Delphi, não um desenvolvedor .NET. Isso pode não ser 100% exato para C #, mas aposto que é bem próximo, especialmente considerando o quanto da estrutura .NET original era baseada em Delphi.)

Todos esses "efeitos colaterais" acontecem quando você altera a propriedade Width em um formulário. Algum deles parece inapropriado ou incorreto de alguma forma?


desde que não entre em um loop infinito que se chama. Para evitar isso, cada "alteração" será mapeada para pelo menos três eventos: um disparado antes da alteração, um disparado durante a alteração e um disparado após a conclusão.
rwong

5

Dê uma olhada nas Regras de Design da Microsoft , especialmente uma delas:

CA1024: Use propriedades onde apropriado

... Um método é um bom candidato para se tornar uma propriedade se uma dessas condições estiver presente:

  • Não aceita argumentos e retorna as informações de estado de um objeto.
  • Aceita um único argumento para definir parte do estado de um objeto.

As propriedades devem se comportar como se fossem campos; se o método não puder, não deverá ser alterado para uma propriedade. Os métodos são melhores que as propriedades nas seguintes situações:

  • O método executa uma operação demorada. O método é notavelmente mais lento que o tempo necessário para definir ou obter o valor de um campo.
  • O método realiza uma conversão. O acesso a um campo não retorna uma versão convertida dos dados que ele armazena.
  • O método Get tem um efeito colateral observável. A recuperação do valor de um campo não produz efeitos colaterais.
  • A ordem de execução é importante. A definição do valor de um campo não depende da ocorrência de outras operações.
  • Chamar o método duas vezes sucessivas cria resultados diferentes.
  • O método é estático, mas retorna um objeto que pode ser alterado pelo chamador. A recuperação do valor de um campo não permite que o chamador altere os dados armazenados pelo campo.
  • O método retorna uma matriz ...

2

Eu acredito que as propriedades não devem ter efeitos colaterais. No entanto, há uma exceção a essa regra: carregamento lento. Se você deseja carregar preguiçosamente algo em uma propriedade, tudo bem, porque o cliente não pode dizer que o carregamento lento está indo e geralmente não se importa.

EDIT : Ah, mais uma coisa - disparar um evento dizendo "PropertyXYZ Changed!" (por exemplo INotifyPropertyChanged) - é bom nos levantadores.


2

Além do carregamento lento mencionado anteriormente, também há o caso das funções de log. Isso seria raro, mas não fora de questão.


2

Quase não há absolutos na programação. Para responder sua pergunta, precisamos examinar algumas propriedades desejáveis ​​dos objetos e verificar se isso é algo que se aplica ao nosso caso.

  1. Queremos que as aulas sejam facilmente testáveis.
  2. Queremos que as classes ofereçam suporte à simultaneidade
  3. Queremos que as aulas sejam fáceis de raciocinar para terceiros

Se respondermos sim a algumas dessas perguntas, provavelmente é uma boa idéia pensar com muito cuidado sobre propriedades e seus efeitos colaterais. Presumo que quando você diz que efeitos colaterais você quer dizer secundárias efeitos colaterais desde a atualização do valor é um efeito colateral em si.

Quanto mais efeitos colaterais, em geral, mais difícil será uma aula para testar. Quanto mais efeitos colaterais forem secundários, mais difícil será a simultaneidade, mas esse é um problema difícil que provavelmente exige alguma reflexão importante sobre o design.

A grande pegadinha é provavelmente para as pessoas que tratam sua classe como uma "caixa preta", não estaria prontamente disponível que algum estado tenha mudado apenas porque eles leram uma propriedade ou que alguma outra propriedade seja alterada porque outra mudança (o que pode levar atualizações em cascata).

Portanto, em geral não, queremos que as propriedades sejam o mais fácil de raciocinar e o mais simples possível, isso é meio implícito na decisão de design, caso contrário, seria um método. No entanto, um bom programador pode sempre quebrar as regras, apenas tenha uma boa razão :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.