Por que o Código Limpo está sugerindo evitar variáveis ​​protegidas?

168

O Código Limpo sugere evitar variáveis ​​protegidas na seção "Distância Vertical" do capítulo "Formatação":

Os conceitos intimamente relacionados devem ser mantidos verticalmente próximos um do outro. Claramente, esta regra não funciona para conceitos que pertencem a arquivos separados. Porém, conceitos estreitamente relacionados não devem ser separados em arquivos diferentes, a menos que você tenha uma boa razão. De fato, esse é um dos motivos pelos quais as variáveis ​​protegidas devem ser evitadas .

Qual é o raciocínio?

Matsemann
fonte
7
mostrar um exemplo onde você acha que precisa -lo
mosquito
63
Eu não levaria muito nesse livro como evangelho.
Rig
40
@ Rig você está na blasfêmia nessas partes com um comentário como esse.
precisa saber é o seguinte
40
Eu estou bem com isso. Eu li o livro e posso ter minha própria opinião.
Rig
10
Eu li o livro e, embora concorde com a maior parte do conteúdo, também não diria que o considero evangélico. Eu acho que todas as situações são diferentes .. e ser capaz de seguir as diretrizes exatas representadas no livro é bastante difícil para muitos projetos.
Simon Whitehead

Respostas:

277

Variáveis ​​protegidas devem ser evitadas porque:

  1. Eles tendem a levar a problemas YAGNI . A menos que você tenha uma classe descendente que realmente faz coisas com o membro protegido, torne-o privado.
  2. Eles tendem a levar a problemas de LSP . As variáveis ​​protegidas geralmente têm alguma invariância intrínseca associada a elas (ou elas seriam públicas). Os herdeiros precisam, então, manter essas propriedades, que as pessoas podem estragar ou violar voluntariamente.
  3. Eles tendem a violar o OCP . Se a classe base fizer muitas suposições sobre o membro protegido, ou se o herdeiro for muito flexível com o comportamento da classe, isso poderá fazer com que o comportamento da classe base seja modificado por essa extensão.
  4. Eles tendem a levar a herança para extensão, em vez de composição. Isso tende a levar a um acoplamento mais rígido, mais violações do SRP , testes mais difíceis e uma série de outras coisas que se enquadram na discussão 'composição de favor sobre herança'.

Mas, como você vê, todos estes são "tendem a". Às vezes, um membro protegido é a solução mais elegante. E funções protegidas tendem a ter menos desses problemas. Mas há várias coisas que fazem com que sejam tratadas com cuidado. Com qualquer coisa que exija esse tipo de cuidado, as pessoas cometerão erros e, no mundo da programação, isso significa bugs e problemas de design.

Telastyn
fonte
Obrigado por muitas boas razões. No entanto, o livro menciona especificamente em um contexto de formatação e distância vertical, é sobre essa parte que estou pensando.
Matsemann 28/08/12
2
Parece que o tio Bob está se referindo à distância vertical quando alguém está se referindo a um membro protegido entre as classes, e não na mesma classe.
Robert Harvey
5
@ Matsemann - com certeza. Se me lembro do livro com precisão, essa seção se concentra na legibilidade e na capacidade de descoberta do código. Se variáveis ​​e funções funcionarem em um conceito, elas deverão ser fechadas no código. Membros protegidos serão usados ​​por tipos derivados, que necessariamente não podem estar próximos do membro protegido, pois estão em um arquivo completamente diferente.
Telastyn
2
@ steve314 Não consigo imaginar um cenário em que a classe base e todos os seus herdeiros morem apenas em um arquivo. Sem um exemplo, estou inclinado a pensar que é um abuso de herança ou uma má abstração.
Telastyn
3
YAGNI = Você não precisa LSP = Princípio de substituição de Liskov OCP = Princípio de abertura / fechamento SRP = Princípio de responsabilidade única #
Bryan Glazer
54

É realmente a mesma razão pela qual você evita os globais, apenas em uma escala menor. É porque é difícil encontrar em todos os lugares que uma variável está sendo lida, ou pior, gravada e difícil tornar o uso consistente. Se o uso for limitado, como ser escrito em um local óbvio na classe derivada e lido em um local óbvio na classe base, as variáveis ​​protegidas podem tornar o código mais claro e conciso. Se você está tentado a ler e escrever uma variável à vontade em vários arquivos, é melhor encapsulá-la.

Karl Bielefeldt
fonte
26

Não li o livro, mas posso dar uma facada no que o tio Bob quis dizer.

Se você colocar protectedalgo, isso significa que uma classe pode herdá-lo. Mas as variáveis-membro devem pertencer à classe em que estão contidas; isso faz parte do encapsulamento básico. A colocação protectedde uma variável de membro quebra o encapsulamento porque agora uma classe derivada tem acesso aos detalhes de implementação da classe base. É o mesmo problema que ocorre quando você cria uma variável publicem uma classe comum.

Para corrigir o problema, você pode encapsular a variável em uma propriedade protegida, da seguinte maneira:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

Isso permite nameque seja definido com segurança a partir de uma classe derivada usando um argumento construtor, sem expor os detalhes de implementação da classe base.

Robert Harvey
fonte
Você criou uma propriedade pública com setter protegido. O campo protegido deve ser resolvido com uma propriedade protegida com setter privado.
28812 Andy
2
+1 agora. O setter também pode ser protegido, mas os IDs de ponto que a base pode aplicar se o novo valor for válido ou não.
218 Andy Andy
Apenas para oferecer um ponto de vista oposto - eu protejo todas as minhas variáveis ​​em uma classe base. Se eles devem ser acessados ​​apenas através de uma interface pública, não deve ser uma classe derivada, deve conter apenas um objeto de classe base. Mas eu também evitar hierarquias mais de 2 classes de profundidade
Martin Beckett
2
Há uma enorme diferença entre protectede publicmembros. Se BaseTypeexpõe um membro público, isso implica que todos os tipos derivados devem ter o mesmo membro funcionando da mesma maneira, uma vez que uma DerivedTypeinstância pode ser passada para o código que recebe uma BaseTypereferência e espera usar esse membro. Por outro lado, se BaseTypeexpõe um protectedmembro, DerivedTypepode-se esperar acessar esse membro base, mas basenão pode ser outra coisa senão a BaseType.
Supercat 8/12
18

O argumento do tio Bob é principalmente de distância: se você tiver um conceito importante para uma classe, agrupe o conceito com a classe nesse arquivo. Não é separado entre dois arquivos no disco.

As variáveis ​​de membro protegidas estão espalhadas em dois lugares e meio que parecem mágicas. Você faz referência a essa variável, mas ela não está definida aqui ... onde está definida? E assim começa a caçada. Melhor evitar protectedcompletamente, seu argumento.

Agora eu não acredito que essa regra precise ser obedecida à letra o tempo todo (como: você não deve usar protected). Veja o espírito do que ele está conseguindo ... agrupe as coisas relacionadas em um arquivo - use técnicas e recursos de programação para fazer isso. Eu recomendaria que você não analisasse demais e se envolvesse nos detalhes.

J. Polfer
fonte
9

Algumas respostas muito boas já estão aqui. Mas uma coisa a acrescentar:

Na maioria das linguagens OO modernas, é um bom hábito (no Java AFAIK é necessário) colocar cada classe em seu próprio arquivo (por favor, não se preocupe com o C ++, onde você costuma ter dois arquivos).

Portanto, é praticamente impossível manter as funções em uma classe derivada acessando uma variável protegida de uma classe base "verticalmente próxima" no código fonte da definição de variável. Mas, idealmente, elas devem ser mantidas lá, pois as variáveis ​​protegidas são destinadas ao uso interno, o que torna as funções que as acessam frequentemente "um conceito intimamente relacionado".

Doc Brown
fonte
1
Não em Swift. Curiosamente, o Swift não tem "protegido", mas possui "arquivo privado", que substitui o "amigo" protegido e o C ++.
gnasher729
@ gnasher729 - é claro, o Swift tem muito Smalltalk em sua herança. Smalltalk não tem nada além de variáveis ​​privadas e métodos públicos. E private no Smalltalk significa private: dois objetos, mesmo da mesma classe, não podem acessar as variáveis ​​um do outro.
Jules
4

A idéia básica é que um "campo" (variável no nível da instância) declarado como protegido provavelmente seja mais visível do que deveria ser e menos "protegido" do que você gostaria. Não há modificador de acesso em C / C ++ / Java / C # que seja equivalente a "acessível apenas por classes filho dentro do mesmo assembly", garantindo assim a capacidade de definir seus próprios filhos que podem acessar o campo em seu assembly, mas não permitir que crianças criadas em outras assembléias tenham o mesmo acesso; O C # possui modificadores internos e protegidos, mas combiná-los torna o acesso "interno ou protegido", não "interno e protegido". Portanto, um campo protegido é acessível a qualquer criança, independentemente de você ter escrito essa criança ou outra pessoa. Protegido é, portanto, uma porta aberta para um hacker.

Além disso, os campos por sua definição praticamente não têm validação inerente à sua alteração. em C #, você pode criar um somente leitura, o que torna os tipos de valor efetivamente constantes e os tipos de referência incapazes de serem reinicializados (mas ainda muito mutáveis), mas é isso. Como tal, mesmo protegidos, seus filhos (nos quais você não pode confiar) têm acesso a esse campo e podem defini-lo como algo inválido, tornando o estado do objeto inconsistente (algo a ser evitado).

A maneira aceita de trabalhar com campos é torná-los privados e acessá-los com uma propriedade e / ou um método getter e setter. Se todos os consumidores da classe precisarem do valor, torne público o publicador (pelo menos). Se apenas crianças precisarem, proteja o getter.

Outra abordagem que responde à pergunta é se perguntar; por que o código em um método filho precisa da capacidade de modificar meus dados de estado diretamente? O que isso diz sobre esse código? Esse é o argumento da "distância vertical". Se houver código em um filho que deva alterar diretamente o estado pai, talvez esse código deva pertencer ao pai em primeiro lugar?

KeithS
fonte
4

É uma discussão interessante.

Para ser franco, boas ferramentas podem atenuar muitos desses problemas "verticais". De fato, na minha opinião, a noção de "arquivo" é algo que dificulta o desenvolvimento de software - algo no qual o projeto Light Table está trabalhando (http://www.chris-granger.com/2012/04/12/light- tabela --- um-novo-ide-conceito /).

Tire os arquivos da imagem e o escopo protegido se tornará muito mais atraente. O conceito protegido fica mais claro em idiomas como o SmallTalk - onde qualquer herança simplesmente estende o conceito original de uma classe. Deve fazer tudo e ter tudo o que a classe pai fez.

Na minha opinião, as hierarquias de classe devem ser o mais superficial possível, com a maioria das extensões provenientes da composição. Nesses modelos, não vejo motivos para que as variáveis ​​privadas não sejam protegidas. Se a classe estendida deve representar uma extensão, incluindo todo o comportamento da classe pai (o que eu acredito que deveria e, portanto, os métodos privados são inerentemente ruins), por que a classe estendida não deve representar também o armazenamento desses dados?

Dito de outra maneira - em qualquer caso em que eu ache que uma variável privada seria melhor que uma protegida, a herança não é a solução para o problema em primeiro lugar.

spronkey
fonte
0

Como diz lá, esse é apenas um dos motivos, ou seja, o código vinculado logicamente deve ser colocado dentro de entidades vinculadas físicas (arquivos, pacotes e outros). Em uma escala menor, é o mesmo que o motivo pelo qual você não coloca uma classe que faz consultas ao banco de dados dentro do pacote com classes que exibem a interface do usuário.

A principal razão, no entanto, de que variáveis ​​protegidas não são recomendadas é porque elas tendem a quebrar o encapsulamento. Variáveis ​​e métodos devem ter a visibilidade mais limitada possível; para referência adicional, consulte Java Efetivo de Joshua Bloch , Item 13 - "Minimize a acessibilidade de classes e membros".

No entanto, você não deve levar todo esse ad litteram, apenas como uma linha de guilda; se as variáveis ​​protegidas forem muito ruins, elas não teriam sido colocadas no idioma em primeiro lugar. Um local razoável para os campos protegidos imho está dentro da classe base a partir de uma estrutura de teste (as classes de teste de integração a estendem).

m3th0dman
fonte
1
Não presuma que, como um idioma permite, não há problema em fazê-lo. Não tenho certeza se existe realmente uma razão divina para permitir campos protegidos; Na verdade, nós os proibimos no meu empregador.
288 Andy
2
@ Andy Você não leu o que eu disse; Eu disse que os campos protegidos não são recomendados e as razões para isso. Claramente, existem cenários em que variáveis ​​protegidas são necessárias; você pode querer um valor final constante apenas nas subclasses.
M3th0dman
Você pode reformular algumas de suas postagens, pois parece usar variáveis ​​e campos de forma intercambiável e deixar explícito que consideraria coisas como consts protegidos uma exceção.
219 Andy
3
@ Andy É bastante óbvio que desde que eu estava me referindo a variáveis ​​protegidas, eu não estava falando sobre variáveis ​​locais ou de parâmetros, mas a campos. Mencionei também um cenário em que variáveis ​​protegidas não constantes são úteis; Embora seja errado uma empresa impor a maneira como os programadores escrevem código.
M3th0dman 30/08/12
1
Se fosse óbvio, eu não teria ficado confuso e com voto negativo. A regra foi feita por nossos desenvolvedores seniores, assim como nossa decisão de executar o StyleCop, o FxCop e exigir testes de unidade e revisões de código.
30512 Andy
0

Acho que o uso de variáveis ​​protegidas para testes JUnit pode ser útil. Se você os tornar privados, não será possível acessá-los durante o teste sem reflexão. Isso pode ser útil se você estiver testando um processo complexo por meio de muitas alterações de estado.

Você pode torná-los privados, com métodos getter protegidos, mas se as variáveis ​​forem usadas apenas externamente durante um teste JUnit, não tenho certeza de que seja uma solução melhor.

usuario
fonte
-1

Sim, eu concordo que isso é um tanto estranho, uma vez que (se bem me lembro) o livro também discute todas as variáveis ​​não privadas como algo a ser evitado.

Eu acho que o problema com o modificador protegido é que não apenas o membro exposto às subclasses também é tornado visível para todo o pacote. Eu acho que é esse aspecto duplo que o tio Bob está fazendo exceção aqui. Obviamente, nem sempre se pode ter métodos protegidos e, portanto, a qualificação de variável protegida.

Eu acho que uma abordagem mais interessante é tornar tudo público, que forçará você a ter turmas pequenas, o que, por sua vez, torna toda a métrica de distância vertical um pouco discutível.

CurtainDog
fonte