As informações estão escondendo mais do que uma convenção?


20

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 protectedirritantes 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 protectedme 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?


10
Desculpe, Eric Lippert está de férias :)
Benjol

Sua pergunta é um pouco difícil de entender. Você está basicamente perguntando se alguém pode acessar um membro privado de fora?
Morgan Herlocker


@ ironcode: Posso dizer que, de alguma forma, não me expresso com clareza o suficiente de algumas das respostas. Você pode dizer o que especificamente torna difícil de entender? Vou tentar esclarecer.
blubb

2
"Não faz sentido fornecer informações em tempo de compilação e depois apenas verificá-las em tempo de execução" - realmente? É melhor dizer isso à Microsoft, porque é nisso que uma parte significativa da estrutura .NET atual se baseia: ativar ou desativar seletivamente a digitação estática. Eu não acho que isso é realmente o que você quer dizer; a pergunta fala repetidamente sobre verificação "estática" versus verificação "dinâmica", mas essas são características do sistema de tipos; o que realmente está em questão aqui é a ligação em tempo de compilação vs. em tempo de execução .
Aaronaught

Respostas:


4

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): passo 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

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?

Pelo que eu disse, sim.

Pode ser usado para impedir que o estado secreto de uma classe seja atacado? A reflexão pode substituir esse mecanismo? Existem outras vantagens oferecidas por um mecanismo mais formal imposto pelo compilador?

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.


3
A reflexão geralmente pode ignorar a proteção de acesso. Para Java e C #, ambos podem acessar dados privados.
Mark H

Obrigado! As respostas aqui: stackoverflow.com/questions/1565734/… explicam isso!
wleao

1
A cultura Javascript é muito diferente da cultura python. O Python possui o pep-08 e quase todos os desenvolvedores de python seguem essas convenções. As violações do PEP-08 são frequentemente consideradas um bug. Então, acho que a experiência em javascript / php / perl / ruby ​​se traduz muito pouco em experiência em python neste aspecto: _private funciona muito bem em python.
Mike Korobov

1
Em algumas (mas não todas) linguagens de programação, a ocultação de informações pode ser usada para proteger contra código hostil. A ocultação de informações não pode ser usada para proteger contra usuários hostis.
187 Brian

2
@ Mike, se as violações ao PEP-08 são frequentemente "consideradas um bug", e nenhum desenvolvedor Python sonha em não seguir as convenções, então é possível que elas sejam aplicadas pelo idioma! Por que o trabalho servil quando o idioma pode fazê-lo automaticamente?
Andrés F.

27

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:

  • público: use com segurança em instâncias da classe, o objetivo da classe não será alterado.
  • protected: Para ser usado ao estender (derivado de) a classe - pode mudar se a implementação precisar mudar drasticamente.
  • privado: não toque! Pode mudar à vontade para fornecer uma melhor implementação das interfaces esperadas.

3
Mais importante, qualquer (facilmente reconhecível, que é o caso, por exemplo, de sublinhados de destaque) não convenção imposta para "detalhes de implementação, sujeitos a alterações" também evita o uso acidental de membros privados. Em Python, por exemplo, a única vez em que um programador sensato escreve código usando um membro privado de fora (e mesmo assim é desaprovado) é quando é quase inevitável, ele sabe exatamente o que faz e aceita a possível quebra em versões futuras . Ou seja, uma situação em que, por exemplo, programadores Java usariam a reflexão.

3
@ Simon Stelling - não, estou dizendo que o implementador original da classe que tornou algo privado está dizendo para você "não use isso, qualquer comportamento aqui pode mudar no futuro". Por exemplo, uma variável de membro particular de uma classe que primeiro contém uma distância entre dois pontos pode posteriormente conter a distância entre dois pontos ao quadrado por motivos de desempenho. Qualquer coisa que tivesse se baseado no antigo comportamento quebraria silenciosamente.
Joris Timmermans

1
@MadKeithV: Então, como uma palavra-chave no nível do idioma está dizendo "não toque porque ..." diferente de uma convenção significa a mesma coisa? Somente o compilador a aplicando, mas voltamos à discussão estática versus dinâmica.

7
@ Simon Stelling (e Delnan) - para mim é como perguntar "como um limitador de motor é diferente de um sinal de limite de velocidade".
Joris Timmermans

1
Estou bem ciente da diferença e nunca disse que não estava lá. Acabei de descobrir que sua resposta e a maioria dos comentários de acompanhamento apenas descrevem os tipos de anotações para membros e métodos que podem ser feitos com convenções ou palavras-chave no nível do idioma e deixam a diferença real (presença de imposição no nível da linguagem). inferido pelo leitor.

11

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.


2
Eu concordo totalmente, mas isso não responde à minha pergunta. Se eu uso _memberou private memberna minha classe é insignificante, desde que o outro programador entenda que ele deve apenas olhar para publicmembros ou membros não prefixados.
blubb

6
Sim, mas a documentação pública de uma classe escrita em um idioma dinâmico também não lança essas 36 partes particulares (pelo menos não por padrão).

3
@ Simon A afirmação de que é "apenas uma convenção" é enganosa, pois traz benefícios reais. "Apenas uma convenção" é algo como aparelho na próxima linha. Uma convenção com benefícios é algo como usar nomes significativos, o que eu diria que é totalmente diferente. Definir algo como privado impede que alguém o veja "métodos secretos"? Não, não se forem determinados.
Morgan Herlocker

1
@ Simon- também, apenas a exposição de métodos seguros de usar aumenta a probabilidade de o usuário não quebrar algo ao tentar usar a classe. Poderiam facilmente métodos que poderiam travar um servidor da web se chamados mil vezes. Você não deseja que os consumidores da sua API possam fazer isso. Abstração é mais do que apenas uma convenção.
Morgan Herlocker

4

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:

public class EmailAlert
{
    private readonly List<string> addresses = new List<string>();

    public void AddRecipient(string address)
    {
        if (!string.IsNullOrEmpty(address))
            addresses.Add(address);
    }

    public void Send(string message)
    {
        foreach (string address in addresses)
            SendTo(address, message);
    }

    // Details of SendTo not shown
}

Oque esta acontecendo aqui?

  • O membro addressesé inicializado na construção da classe.
  • É privado, então nada do lado de fora pode tocá-lo.
  • Também o fizemos readonly, para que nada por dentro possa tocá-lo após a construção (isso nem sempre é correto / necessário, mas é útil aqui).
  • Portanto, o Sendmétodo pode fazer uma suposição que addressesnunca 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 addressescampo (ou seja, se fosse public), 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:

Pode ser usado para impedir que o estado secreto de uma classe seja atacado?

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.

A reflexão pode substituir esse mecanismo?

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 que faria valer a pena para o compilador impor a ocultação de informações?

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.


1
@ Simon: Precisamos esclarecer "ignorância" aqui. Prefixar um método com os _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.
Aaronaught

2
Que tal uma analogia: um modificador de acesso 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.
Aaronaught

3

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 privatevariável, expô-lo usando uma propriedade protectedou publicao 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 uma privatevariável, exponha-o usando uma propriedade e, no setter, verifique se o valor mudou e disparou PropertyChanging/ PropertyChangedeventos. Mas sem ver a implementação interna, você nunca saberia tudo o que está acontecendo nos bastidores.


3

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:

Desejamos que a estrutura do sistema seja determinada pelos projetistas explicitamente antes do início da programação, e não inadvertidamente pelo uso da informação pelo programador.


1
+1 para a citação "uso de informações do programador", é exatamente isso (embora você tenha uma vírgula extra no final).
precisa saber é

2

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.


1
Se a sua biblioteca estiver sendo usada na mesma empresa em que você trabalha, você se importará ... Se ele travar o aplicativo, será difícil corrigi-lo.
wleao

1
Por que fingir usar um cinto de segurança quando você pode usá-lo? Eu reformularia a pergunta e perguntaria: existe algum motivo para um compilador não aplicá-la, se todas as informações estiverem disponíveis para ele. Você não quer esperar até ter um acidente para descobrir se vale a pena usar esse cinto de segurança.
Mark H

1
@ Simon: por que bagunçar uma interface com coisas que você não quer que as pessoas usem. Odeio que o C ++ exija que todos os membros estejam presentes na definição da classe em um arquivo de cabeçalho (eu entendo o porquê). As interfaces são para outras pessoas e não devem estar cheias de coisas que não podem ser usadas.
phkahler

1
@phkahler: pydocremove _prefixedMembersda interface. Interfaces desordenadas não têm nada a ver com a escolha de uma palavra-chave verificada pelo compilador.
Blubb

2

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.


2

Não estive em uma situação em que um erro do compilador causado por essas palavras-chave tivesse me salvado de um bug.

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

class C {
  public void foo() { ... }

  private void fooHelper() { /* lots of complex code */ }
}

Talvez eu queira substituir fooa implementação e possivelmente mudar de fooHelpermaneiras 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.

privatepermite que os autores das bibliotecas dividam as bibliotecas em métodos de tamanho gerenciável (e privateclasses auxiliares) sem o medo de serem forçados a manter esses detalhes internos por anos.


O que faria valer a pena para o compilador impor a ocultação de informações?

Em uma nota lateral, em Java privatenão é imposta pelo compilador, mas pelo verificador de bytecode Java .


A reflexão pode substituir esse mecanismo? O que faria valer a pena para o compilador impor a ocultação de informações?

Em Java, não apenas a reflexão pode substituir esse mecanismo. Existem dois tipos de privateem Java. O tipo de privateimpedimento de uma classe externa acessar os privatemembros de outra classe externa que é verificado pelo verificador de bytecode, mas também os privateque são usados ​​por uma classe interna por meio de um método acessador sintético privado de pacote, como em

 public class C {
   private int i = 42;

   public class B {
     public void incr() { ++i; }
   }
 }

Como a classe B(realmente chamada C$B) usa i, o compilador cria um método acessador sintético que permite Bacessar C.ide uma maneira que ultrapassa o verificador de bytecode. Infelizmente, como ClassLoaderpermite criar uma classe a partir de a byte[], é bastante simples obter informações particulares que Cforam expostas a classes internas criando uma nova classe no Cpacote de que é possível seC o jarro não tiver sido selado.

A privateaplicaçã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.


Pode ser usado para impedir que o estado secreto de uma classe seja atacado?

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:

Joe-E é um subconjunto da linguagem de programação Java projetada para suportar a programação segura de acordo com a disciplina de capacidade do objeto. O Joe-E tem como objetivo facilitar a construção de sistemas seguros, bem como facilitar as análises de segurança dos sistemas criados no Joe-E.

O documento vinculado a essa página fornece um exemplo de como a privateaplicação possibilita a decomposição segura.

Fornecendo encapsulamento seguro.

Figura 1. Um recurso de registro somente anexado.

public final class Log {
  private final StringBuilder content;
  public Log() {
    content = new StringBuilder();
  }
  public void write(String s) {
    content.append(s);
  }
}

Considere a Fig. 1, que ilustra como alguém pode construir um recurso de registro somente para acréscimo. Desde que o restante do programa seja escrito em Joe-E, um revisor de código pode ter certeza de que as entradas de log podem ser adicionadas apenas e não podem ser modificadas ou removidas. Essa revisão é prática porque requer apenas inspeção da Log classe e não requer revisão de nenhum outro código. Conseqüentemente, a verificação dessa propriedade requer apenas um raciocínio local sobre o código de log.


1

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.


1

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.

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.