Em Java, C # e em muitas outras linguagens fortemente tipadas, verificadas estaticamente, somos usados para escrever código como este:
public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }
Algumas linguagens verificadas dinamicamente não fornecem palavras-chave para expressar o nível de "privacidade" de um determinado aluno e, em vez disso, se baseiam em convenções de codificação. O Python, por exemplo, prefixa membros privados com um sublinhado:
_m(self): pass
Pode-se argumentar que o fornecimento dessas palavras-chave em idiomas verificados dinamicamente adicionaria pouco uso, pois é verificado apenas em tempo de execução.
No entanto, também não consigo encontrar um bom motivo para fornecer essas palavras-chave em idiomas verificados estaticamente. Acho que é necessário preencher meu código com palavras-chave bastante detalhadas, como protected
irritantes e perturbadoras. Até agora, eu não estava em uma situação em que um erro do compilador causado por essas palavras-chave tivesse me salvado de um bug. Muito pelo contrário, já estive em situações em que um lugar errado protected
me impedia de usar uma biblioteca.
Com isso em mente, minha pergunta é:
As informações estão escondendo mais do que uma convenção entre programadores usadas para definir o que faz parte da interface oficial de uma classe?
Pode ser usado para impedir que o estado secreto de uma classe seja atacado? A reflexão pode substituir esse mecanismo? O que faria valer a pena para o compilador impor a ocultação de informações?
fonte
Respostas:
Estou estudando para a certificação Java e um monte deles diz respeito a como gerenciar modificadores de acesso. E eles fazem sentido e devem ser usados corretamente.
Eu trabalhei com python e, durante minha jornada de aprendizado, ouvi dizer que, em python, a convenção existe porque as pessoas que trabalham com ele devem saber o que significa e como aplicá-lo. Dito isto,
_m(self): pass
o sublinhado me alertaria para não mexer com esse campo. Mas todos seguirão essa convenção? Eu trabalho com javascript e devo dizer que não . Às vezes, preciso verificar um problema e o motivo é que a pessoa estava fazendo algo que não deveria fazer ...leia esta discussão sobre o sublinhado líder em python
Pelo que eu disse, sim.
Sim, ele pode ser usado para proteger o estado secreto de uma classe e deve ser usado não apenas para isso, mas também para impedir que os usuários mexam com o estado dos objetos, a fim de mudar seus comportamentos para algo que eles acham que deve ser o caminho que o objeto deveria ter. estive trabalhando. Você, como desenvolvedor, deve planejar e pensar sobre isso e projetar sua classe de uma maneira que faça sentido, de uma maneira que seu comportamento não seja violado. E o compilador, como um bom amigo, ajudará você a manter o código da maneira que você o criou, aplicando as políticas de acesso.
EDITADO Sim, reflexão pode, verifique os comentários
A última pergunta é interessante e estou disposto a ler as respostas a respeito.
fonte
O especificador de acesso "privado" não se refere ao erro do compilador gerado na primeira vez em que você o vê. Na realidade, trata-se de impedir que você acesse algo que ainda está sujeito a alterações quando a implementação da classe que mantém o membro privado é alterada.
Em outras palavras, não permitir que você o use quando ainda estiver funcionando impede que você continue usando-o acidentalmente quando não estiver mais funcionando.
Como Delnan observou abaixo, a convenção de prefixo desencoraja o uso acidental de membros que estão sujeitos a alterações desde que a convenção seja seguida e entendida corretamente. Para um usuário malicioso (ou ignorante), não faz nada para impedi-lo de acessar esse membro com todas as possíveis consequências. Em idiomas com suporte embutido para especificadores de acesso, isso não acontece na ignorância (erro do compilador) e se destaca como um dedo dolorido quando malicioso (construções estranhas para chegar ao membro privado).
O especificador de acesso "protegido" é uma história diferente - não pense nisso simplesmente como "não muito público" ou "muito parecido com privado". "Protegido" significa que você provavelmente desejará usar essa funcionalidade quando derivar da classe que contém o membro protegido. Os membros protegidos fazem parte da "interface de extensão" que você usará para adicionar funcionalidades às classes existentes sem alterar elas próprias as classes existentes.
Então, breve recapitulação:
fonte
Se você estiver escrevendo um código que será consumido por outra pessoa, a ocultação de informações pode fornecer uma interface muito mais fácil de entender. "Outra pessoa" pode ser outro desenvolvedor de sua equipe, desenvolvedores consumindo uma API que você escreveu comercialmente ou até mesmo seu futuro que "simplesmente não consegue se lembrar de como a coisa funciona". É muito mais fácil trabalhar com uma classe que possui apenas 4 métodos disponíveis do que com 40.
fonte
_member
ouprivate member
na minha classe é insignificante, desde que o outro programador entenda que ele deve apenas olhar parapublic
membros ou membros não prefixados.Eu sugeriria que, como pano de fundo, você comece lendo sobre invariantes de classe .
Um invariante é, para resumir uma longa história, uma suposição sobre o estado de uma classe que deve permanecer verdadeiro durante toda a vida útil de uma classe.
Vamos usar um exemplo C # realmente simples:
Oque esta acontecendo aqui?
addresses
é inicializado na construção da classe.readonly
, para que nada por dentro possa tocá-lo após a construção (isso nem sempre é correto / necessário, mas é útil aqui).Send
método pode fazer uma suposição queaddresses
nunca seránull
. Não é necessário executar essa verificação porque não há como o valor ser alterado.Se outras classes pudessem gravar no
addresses
campo (ou seja, se fossepublic
), essa suposição não seria mais válida. Todos os outros métodos da classe que dependem desse campo teriam que começar a fazer verificações nulas explícitas ou correr o risco de travar o programa.Então, sim, é muito mais que uma "convenção"; todos os modificadores de acesso dos membros da classe coletivamente formam um conjunto de suposições sobre quando e como esse estado pode ser alterado. Essas suposições são posteriormente incorporadas a membros e classes dependentes e interdependentes, para que os programadores não precisem raciocinar sobre todo o estado do programa ao mesmo tempo. A capacidade de fazer suposições é um elemento crítico para gerenciar a complexidade do software.
Para essas outras perguntas:
Sim e não. O código de segurança, como a maioria dos códigos, dependerá de certos invariantes. Modificadores de acesso certamente são úteis como sinalização para chamadores confiáveis que eles não deveriam mexer com ele. O código malicioso não se importa, mas o código malicioso também não precisa passar pelo compilador.
Claro que pode. Mas a reflexão requer que o código de chamada tenha esse nível de privilégio / confiança. Se você estiver executando um código malicioso com total confiança e / ou privilégios administrativos, já perdeu essa batalha.
O compilador já o aplica. O mesmo acontece com o tempo de execução em .NET, Java e outros ambientes - o código de operação usado para chamar um método não terá êxito se o método for privado. As únicas maneiras de contornar essa restrição exigem código confiável / elevado, e o código elevado sempre pode simplesmente gravar diretamente na memória do programa. É aplicado o máximo que pode ser aplicado sem a necessidade de um sistema operacional personalizado.
fonte
_
estados do contrato, mas não o impõe. Isso pode dificultar o desconhecimento do contrato, mas não dificulta a quebra do contrato, principalmente quando comparado a métodos tediosos e muitas vezes restritivos como o Reflection. Uma convenção diz: "você não deve quebrar isso". Um modificador de acesso diz: "você não pode quebrar isso". Estou não tentando dizer que uma abordagem é melhor do que o outro - eles são ambos muito bem dependendo da sua filosofia, mas eles são, no entanto, muito diferentes abordagens.private
/protected
é como um preservativo. Você não precisa usar um, mas se não estiver usando, é melhor ter certeza do tempo e do (s) seu (s) parceiro (s). Ele não impedirá um ato malicioso e pode nem funcionar sempre, mas certamente reduzirá os riscos de descuido e será muito mais eficaz do que dizer "por favor, tenha cuidado". Ah, e se você tiver um, mas não o use, não espere nenhuma simpatia de mim.Ocultar informações é muito mais do que apenas uma convenção; tentar contorná-lo pode realmente quebrar a funcionalidade da classe em muitos casos. Por exemplo, é uma prática bastante comum armazenar um valor em uma
private
variável, expô-lo usando uma propriedadeprotected
oupublic
ao mesmo tempo e, no getter, verifique se há nulo e faça qualquer inicialização necessária (por exemplo, carregamento lento). Ou armazene algo em umaprivate
variável, exponha-o usando uma propriedade e, no setter, verifique se o valor mudou e disparouPropertyChanging
/PropertyChanged
eventos. Mas sem ver a implementação interna, você nunca saberia tudo o que está acontecendo nos bastidores.fonte
A ocultação de informações evoluiu de uma filosofia de projeto de cima para baixo. O Python foi chamado de linguagem de baixo para cima .
A ocultação de informações é aplicada bem no nível da classe em Java, C ++ e C #, portanto, não é realmente uma convenção nesse nível. É muito fácil tornar uma classe uma "caixa preta", com interfaces públicas e detalhes ocultos (privados).
Como você apontou, em Python, cabe aos programadores seguir a convenção de não usar o que se pretende ocultar, pois tudo é visível.
Mesmo com Java, C ++ ou C #, em algum momento a ocultação de informações se torna uma convenção. Não há controles de acesso nos níveis mais altos de abstração envolvidos em arquiteturas de software mais complexas. Por exemplo, em Java, você pode encontrar o uso de ".internal". nomes de pacotes. Esta é apenas uma convenção de nomenclatura, porque não é fácil para esse tipo de ocultação de informações ser aplicado somente através da acessibilidade do pacote.
Um idioma que se esforça para definir formalmente o acesso é Eiffel. Este artigo aponta algumas outras fraquezas de ocultar informações de linguagens como Java.
Antecedentes: O esconderijo de informações foi proposto em 1971 por David Parnas . Ele ressalta nesse artigo que o uso de informações sobre outros módulos pode "aumentar desastrosamente a conectividade da estrutura do sistema". De acordo com essa idéia, a falta de ocultação de informações pode levar a sistemas fortemente acoplados e difíceis de manter. Ele continua com:
fonte
Ruby e PHP possuem e aplicam em tempo de execução.
O ponto de ocultar informações é realmente mostrar informações. Ao "ocultar" os detalhes internos, o objetivo se torna aparente de uma perspectiva externa. Existem idiomas que abraçam isso. Em Java, os padrões de acesso ao pacote interno, no haXe ao protegido. Você os declara explicitamente públicos para expô-los.
O objetivo disso é facilitar o uso de suas classes, expondo apenas uma interface altamente coerente. Você quer que o resto seja protegido, para que ninguém esperto venha e mexa com seu estado interno para induzir sua classe a fazer o que ele quer.
Além disso, quando os modificadores de acesso são impostos em tempo de execução, você pode usá-los para impor um certo nível de segurança, mas não acho que essa seja uma solução particularmente boa.
fonte
pydoc
remove_prefixedMembers
da interface. Interfaces desordenadas não têm nada a ver com a escolha de uma palavra-chave verificada pelo compilador.Modificadores de acesso podem definitivamente fazer algo que a convenção não pode.
Por exemplo, em Java, você não pode acessar o membro / campo privado, a menos que use reflexão.
Portanto, se eu escrever a interface para um plug-in e negar corretamente os direitos de modificar campos privados por meio de reflexão (e definir o gerenciador de segurança :)), posso enviar algum objeto para funções implementadas por qualquer pessoa e saber que ele não pode acessar seus campos privados.
É claro que pode haver alguns bugs de segurança que lhe permitiriam superar isso, mas isso não é filosoficamente importante (mas, na prática, definitivamente é).
Se o usuário executa minha interface em seu ambiente, ele tem controle e, portanto, pode burlar os modificadores de acesso.
fonte
Não é demais salvar os autores de aplicativos dos erros, mas permitir que os autores da biblioteca decidam quais partes de sua implementação estão comprometidas em manter.
Se eu tiver uma biblioteca
Talvez eu queira substituir
foo
a implementação e possivelmente mudar defooHelper
maneiras radicais. Se várias pessoas decidiram usarfooHelper
várias apesar de todos os avisos na minha documentação, talvez eu não consiga fazer isso.private
permite que os autores das bibliotecas dividam as bibliotecas em métodos de tamanho gerenciável (eprivate
classes auxiliares) sem o medo de serem forçados a manter esses detalhes internos por anos.Em uma nota lateral, em Java
private
não é imposta pelo compilador, mas pelo verificador de bytecode Java .Em Java, não apenas a reflexão pode substituir esse mecanismo. Existem dois tipos de
private
em Java. O tipo deprivate
impedimento de uma classe externa acessar osprivate
membros de outra classe externa que é verificado pelo verificador de bytecode, mas também osprivate
que são usados por uma classe interna por meio de um método acessador sintético privado de pacote, como emComo a classe
B
(realmente chamadaC$B
) usai
, o compilador cria um método acessador sintético que permiteB
acessarC.i
de uma maneira que ultrapassa o verificador de bytecode. Infelizmente, comoClassLoader
permite criar uma classe a partir de abyte[]
, é bastante simples obter informações particulares queC
foram expostas a classes internas criando uma nova classe noC
pacote de que é possível seC
o jarro não tiver sido selado.A
private
aplicação adequada requer coordenação entre os carregadores de classe, o verificador de código de bytes e a política de segurança que pode impedir o acesso reflexivo a partes privadas.Sim. A "decomposição segura" é possível quando os programadores podem colaborar enquanto preservam as propriedades de segurança de seus módulos - não preciso confiar no autor de outro módulo de código para não violar as propriedades de segurança do meu módulo.
Linguagens de recursos de objetos como Joe-E usam ocultação de informações e outros meios para tornar possível a decomposição segura:
O documento vinculado a essa página fornece um exemplo de como a
private
aplicação possibilita a decomposição segura.fonte
Ocultar informações é uma das principais preocupações de um bom design de software. Confira todos os documentos de Dave Parnas do final dos anos 70. Basicamente, se você não pode garantir que o estado interno do seu módulo seja consistente, não pode garantir nada sobre o seu comportamento. E a única maneira de garantir seu estado interno é mantê-lo privado e apenas permitir que ele seja alterado pelos meios fornecidos.
fonte
Ao fazer da proteção uma parte do idioma, você ganha algo: garantia razoável.
Se eu tornar uma variável privada, tenho garantia razoável que ela será tocada apenas pelo código nessa classe ou por amigos explicitamente declarados dessa classe. O escopo do código que poderia tocar razoavelmente nesse valor é limitado e definido explicitamente.
Agora, existem maneiras de contornar a proteção sintática? Absolutamente; a maioria dos idiomas os possui. No C ++, você sempre pode converter a classe em algum outro tipo e cutucar seus bits. Em Java e C #, você pode refletir sobre isso. E assim por diante.
No entanto, fazer isso é difícil . É óbvio que você está fazendo algo que não deveria estar fazendo. Você não pode fazer isso acidentalmente (fora das gravações selvagens em C ++). Você deve pensar de bom grado: "Vou tocar em algo que me foi dito pelo meu compilador". Você deve voluntariamente fazer algo irracional .
Sem proteção sintática, um programador pode acidentalmente estragar tudo. Você precisa ensinar ao usuário uma convenção e eles devem segui -la sempre . Caso contrário, o mundo se torna muito inseguro.
Sem proteção sintática, o ônus recai sobre as pessoas erradas: as muitas pessoas que usam a classe. Eles devem seguir a convenção ou ocorrerão danos não especificados. Risque o que pode ocorrer : dano não especificado .
Não há nada pior do que uma API em que, se você fizer a coisa errada, tudo poderá funcionar de qualquer maneira. Isso fornece garantias falsas ao usuário de que ele fez a coisa certa, de que está tudo bem, etc.
E o que dizer de novos usuários para esse idioma? Eles não apenas precisam aprender e seguir a sintaxe real (imposta pelo compilador), mas agora devem seguir esta convenção (imposta pelos seus pares, na melhor das hipóteses). E se não o fizerem, nada de ruim pode acontecer. O que acontece se um programador não entender por que a convenção existe? O que acontece quando ele diz "estrague tudo" e apenas cutuca seus amigos? E o que ele pensa se tudo continuar funcionando?
Ele acha que a convenção é estúpida. E ele não vai segui-lo novamente. E ele dirá a todos os seus amigos para não se incomodarem também.
fonte