Passando variável de membro como um parâmetro de método

33

Em um projeto, encontrei um código como este:

class SomeClass
{
    private SomeType _someField;

    public SomeType SomeField
    {
        get { return _someField; }
        set { _someField = value; }
    }

    protected virtual void SomeMethod(/*...., */SomeType someVar)
    {
    }

    private void SomeAnotherMethod()
    {
        //.............
        SomeMethod(_someField);
        //.............
    }

};

Como convencer meus colegas de equipe que esse é um código incorreto?

Eu acredito que isso é uma complicação desnecessária. Por que passar uma variável de membro como um parâmetro de método se você já tem acesso a ela? Isso também é uma violação do encapsulamento.

Você vê outros problemas com este código?

tika
fonte
21
O que faz você pensar que é ruim?
precisa saber é
@Yannis Rizos, você acha que é bom? Pelo menos isso é complicação desnecessária. Por que passar variável como parâmetro de método, se você já tem acesso a ela? Isso também é violação do encapsulamento.
tika
2
Pontos válidos, edite a pergunta para incluí-los. Não podemos ajudá-lo a convencer seus companheiros de equipe, mas podemos ajudá-lo a avaliar o código, e é disso que a sua pergunta deve se referir.
29512 yannis
8
Prefiro ter um método que possa resumir variáveis ​​diferentes do que um método que faça a constante 2 + 2. O parâmetro no método é para reutilização.
Dant
Um ponto que acho importante aqui é o tipo desse parâmetro. Se for um tipo de referência, não vejo vantagem, mas se for um tipo de valor, acho que faz sentido, pois se você modificar esse tipo de variável, o compilador o alertará sobre o local em que você quebrou o código.
Rémi

Respostas:

3

Penso que este é um tópico válido, mas a razão pela qual você está obtendo respostas contraditórias é por causa da forma como a pergunta foi formada. Pessoalmente, tive as mesmas experiências com minha equipe em que passar membros como argumentos era desnecessário e complicou o código. Teríamos uma classe que trabalha com um conjunto de membros, mas algumas funções acessam os membros diretamente e outras funções modificam os mesmos membros através de parâmetros (ou seja, usam nomes completamente diferentes) e não havia absolutamente nenhuma razão técnica para fazer isso. Por razões técnicas, quero dizer um exemplo que Kate forneceu.

Eu recomendaria dar um passo atrás e, em vez de focar exatamente na passagem dos membros como parâmetros, iniciar discussões com sua equipe sobre clareza e legibilidade. Mais formalmente ou apenas nos corredores, discuta o que torna alguns segmentos de código mais fáceis de ler e outros segmentos de código mais difíceis. Em seguida, identifique medidas de qualidade ou atributos de código limpo que, como uma equipe, você gostaria de buscar. Afinal, mesmo quando trabalhamos em projetos de campo verde, passamos mais de 90% do tempo lendo e assim que o código é escrito (digamos 10 a 15 minutos depois), ele entra em manutenção, onde a legibilidade é ainda mais importante.

Portanto, para o seu exemplo específico aqui, o argumento que eu usaria é que menos código é sempre mais fácil de ler do que mais código. Uma função que possui 3 parâmetros é mais difícil para o cérebro processar do que uma função que não possui nenhum ou 1 parâmetro. Se houver outro nome de variável, o cérebro precisará acompanhar outra coisa ao ler o código. Então, lembre-se de "int m_value" e "int localValue" e lembre-se de que um realmente significa que o outro sempre é mais caro para o seu cérebro do que simplesmente trabalha com "m_value".

Para mais munição e idéias, eu recomendaria pegar uma cópia do Código Limpo do tio Bob .

DXM
fonte
Votou depois de ver a influência do livro referenciado.
21817 Frank Hileman
Embora uma pequena parte de mim esteja muito triste por cinco anos depois de escrever a resposta, você acabou de tirar dois pontos da Internet, há outra pequena parte de mim curiosa para fornecer algumas referências que indicariam o (presumivelmente ruim) influência que o livro que referi tinha. Isso parece justo e quase valem esses pontos
DXM
a referência sou eu mesma, vendo pessoalmente a influência em outros desenvolvedores. Todas as motivações por trás de tais livros são boas, no entanto, ao especificar que todo o código deve seguir diretrizes não padronizadas (isto é, diretrizes que realmente servem a um propósito), esses livros parecem gerar uma perda de pensamento crítico, que alguns interpretam como um culto .
23817 Frank Hileman
sobre os desafios de processamento do cérebro, considere o conceito de carga cognitiva
jxramos
30

Eu posso pensar em uma justificativa para passar um campo de membro como um parâmetro em um método (privado): torna explícito do que seu método depende.

Como você diz, todos os campos de membros são parâmetros implícitos do seu método, porque o objeto todo é. No entanto, o objeto completo é realmente necessário para calcular o resultado? Se SomeMethodé um método interno do qual depende apenas _someField, não é mais fácil tornar essa dependência explícita? De fato, tornar essa dependência explícita também pode sugerir que você pode refatorar esse pedaço de código da sua classe! (Observe que presumo que não estamos falando de getters ou setters aqui, mas de um código que realmente calcula algo)

Eu não faria o mesmo argumento para métodos públicos, porque o chamador não sabe nem se importa com qual parte do objeto é relevante para calcular o resultado ...

Andres F.
fonte
2
Qual seria a dependência implícita restante? Eu assumi do seu exemplo que _someFieldé o único parâmetro necessário para calcular o resultado, e nós apenas o tornamos explícito. (. Nota: isto é importante Nós não adicionar uma dependência, nós só tornou explícita!)
Andres F.
11
-1 Se não houver dependências implícitas para os membros da instância, ele deve ser um membro estático que aceita o valor como parâmetro. Nesse caso, embora você tenha apresentado um motivo, não acho que seja uma justificativa válida. É um código ruim.
9119 Steven Sters
2
@SnOrfus eu realmente sugeriu refatoração o método inteiramente fora da classe
Andres F.
5
+1. para "... não é mais fácil tornar essa dependência explícita?" Absolutamente louca. Quem aqui diria "variáveis ​​globais são boas como regra geral"? Isso é tão COBOL-68. Ouça-me agora e acredite em mim mais tarde. Em nosso código não trivial, às vezes refatorarei para transmitir explicitamente onde as variáveis ​​globais de classe são usadas. Nós estragamos o cão em muitos casos: a) uso arbitrário alternativo de um campo privado e sua propriedade pública; b) ofuscando a transformação de um campo, "ocultando as dependências". Agora multiplique isso por uma cadeia de herança profunda de 3-5.
Radarbob
2
@ Tarion Não concordo com o tio Bob nisso. Sempre que possível, os métodos devem ser parecidos com funções e depender apenas de dependências explícitas. (Ao chamar métodos públicos no OOP, uma dessas dependências é this(ou self), mas é explicitada pela própria chamada obj.method(x)). Outras dependências implícitas são o estado do objeto; isso geralmente torna o código mais difícil de entender. Sempre que possível - e dentro do razoável - torne as dependências explícitas e com estilo funcional. No caso de métodos privados, se possível, passe explicitamente todos os parâmetros que eles precisam. E sim, ajuda a refatorá-los.
Andres F.
28

Eu vejo um forte motivo para passar variáveis-membro como argumentos de função para métodos privados - pureza da função. As variáveis ​​de membro são efetivamente um estado global do ponto de vista; além disso, um estado global mutável se esse membro for alterado durante a execução do método. Substituindo referências de variáveis ​​de membros por parâmetros de método, podemos efetivamente tornar uma função pura. As funções puras não dependem de um estado externo e não têm efeitos colaterais, sempre retornando os mesmos resultados, com o mesmo conjunto de parâmetros de entrada - facilitando o teste e a manutenção futura.

Claro que não é fácil nem prático ter todos os seus métodos como métodos puros em uma linguagem OOP. Mas acredito que você ganha muito em termos de clareza de código ao manter os métodos que lidam com lógica complexa pura e usar variáveis ​​imutáveis, enquanto mantém o tratamento de estado "global" impuro separado dentro de métodos dedicados.

Passar variáveis ​​de membro para uma função pública do mesmo objeto ao chamar a função externamente, no entanto, constituiria um grande cheiro de código na minha opinião.

scrwtp
fonte
5
Supurb resposta. Apesar do ideal, muitas classes no software de hoje são maiores e mais feias do que os programas inteiros quando a fase "Globals are Evil" foi criada. As variáveis ​​de classe são, para todos os efeitos práticos, globais no escopo da instância de classe. Ter grandes quantidades do trabalho realizado em funções puras cria um código muito mais testável e robusto.
mattnz
@mattnz, existe alguma maneira de você fornecer um link para a bibliografia de Hwat ou algum de seus livros sobre programação ideal? Eu tenho vasculhado a internet e não consigo encontrar nada nele. O Google continua tentando corrigi-lo automaticamente para "o quê".
Buttle Butkus 29/01
8

Se a função for chamada várias vezes, às vezes passando essa variável de membro e outras vezes, então tudo bem. Por exemplo, eu não consideraria isso tão ruim assim:

if ( CalculateCharges(newStartDate) > CalculateCharges(m_StartDate) )
{
     //handle increase in charges
}

onde newStartDateestá alguma variável local e m_StartDateé uma variável de membro.

No entanto, se a função apenas for chamada com a variável de membro passada para ela, isso é estranho. As funções de membro trabalham em variáveis ​​de membro o tempo todo. Eles podem estar fazendo isso (dependendo do idioma em que você está trabalhando) para obter uma cópia da variável membro - se for esse o caso e você não conseguir ver, o código poderá ser melhor se tornar explícito todo o processo.

Kate Gregory
fonte
3
Não importa que o método seja chamado com parâmetros diferentes da variável de membro. O que importa é que isso poderia ser chamado dessa maneira. Você nem sempre sabe como um método acabará sendo chamado quando você o criar.
Caleb
Talvez você deva substituir melhor a condição "if" por "needHandleIncreaseChages (newStartDate)" e seu argumento não será mais válido.
Tarion 6/06/2013
2

O que ninguém tocou é que SomeMethod está protegido virtual. Significando que uma classe derivada pode usá-lo E reimplementar sua funcionalidade. Uma classe derivada não teria acesso à variável privada e, portanto, não poderia fornecer uma implementação personalizada de SomeMethod que depende da variável privada. Em vez de assumir a dependência da variável privada, a declaração exige que o chamador a transmita.

Michael Brown
fonte
O que você está perdendo é que essa variável de membro privado possui acessadores públicos.
tika
Então você está dizendo que o método virtual protegido deve depender do acessador público de uma variável privada? E você tem um problema com o código atual?
Michael Brown
Os acessadores públicos são criados para serem usados. Período.
Tika
1

As estruturas de GUI geralmente têm algum tipo de classe 'View' que representa coisas desenhadas na tela, e essa classe geralmente fornece um método como invalidateRect(Rect r)marcar parte de sua área de desenho como precisando ser redesenhada. Os clientes podem chamar esse método para solicitar uma atualização para parte da exibição. Mas uma visão também pode chamar seu próprio método, como:

invalidateRect(m_frame);

para causar redesenho de toda a área. Por exemplo, ele pode fazer isso quando é adicionado pela primeira vez à hierarquia de visualizações.

Não há nada de errado em fazer isso - o quadro da vista é um retângulo válido e a própria vista sabe que deseja se redesenhar. A classe View pode fornecer um método separado que não usa parâmetros e usa o quadro da exibição:

invalidateFrame();

Mas por que adicionar um método especial apenas para isso quando você pode usar o mais geral invalidateRect()? Ou, se você optou por fornecer invalidateFrame(), provavelmente o implementaria em termos dos mais gerais invalidateRect():

View::invalidateFrame(void)
{
    invalidateRect(m_frame)
}

Por que passar variável como parâmetro de método, se você já tem acesso a ela?

Você deve passar variáveis ​​de instância como parâmetros para seu próprio método se o método não operar especificamente nessa variável de instância. No exemplo acima, o quadro da vista é apenas outro retângulo no que diz respeito ao invalidateRect()método.

Caleb
fonte
1

Isso faz sentido se o método é um método utilitário. Digamos, por exemplo, que você precise derivar um nome abreviado exclusivo de várias seqüências de texto livre.

Você não gostaria de codificar uma implementação separada para cada string, em vez disso, faz sentido passar a string para um método comum.

No entanto, se o método sempre funciona em um único membro, parece um pouco idiota passá-lo como parâmetro.

James Anderson
fonte
1

O principal motivo para ter uma variável de membro em uma classe é permitir que você a defina em um local e tenha seu valor disponível para todos os outros métodos da classe. Portanto, em geral, você esperaria que não houvesse necessidade de passar a variável membro para um método da classe.

No entanto, posso pensar em algumas razões pelas quais você pode passar a variável membro para outro método da classe. A primeira é se você precisa garantir que o valor da variável de membro precise ser usado inalterado quando usado com o método chamado, mesmo que esse método precise alterar o valor real da variável de membro em algum momento do processo. A segunda razão está relacionada à primeira, na qual você pode querer garantir a imutabilidade de um valor no escopo de uma cadeia de métodos - ao implementar uma sintaxe fluente, por exemplo.

Com tudo isso em mente, eu não chegaria ao ponto de dizer que o código é "ruim", como tal, se você passar uma variável de membro para um dos métodos da classe. No entanto, eu sugeriria que geralmente não é o ideal, pois isso poderia incentivar muita duplicação de código onde o parâmetro é atribuído a uma variável local, por exemplo, e os parâmetros extras adicionam "ruído" no código onde não é necessário. Se você é um fã do livro Código Limpo, deve saber que ele deve manter o número mínimo de parâmetros do método mínimo e apenas se não houver outra maneira mais sensata de acessar o parâmetro. .

S.Robins
fonte
1

Algumas coisas que vêm a mim quando penso sobre isso:

  1. Em geral, as assinaturas de métodos com menos parâmetros são mais fáceis de entender; Uma grande razão pela qual os métodos foram inventados foi eliminar longas listas de parâmetros, casando-as com os dados em que operam.
  2. Tornar a assinatura do método dependente da variável membro tornará mais difícil alterar essas variáveis ​​no futuro, porque você não só precisará alterar dentro do método, mas em qualquer lugar em que o método também seja chamado. E como o SomeMethod no seu exemplo está protegido, as subclasses também precisarão ser alteradas.
  3. Métodos (públicos ou privados) que não dependem dos internos da classe não precisam estar nessa classe. Eles poderiam ser consignados em métodos de utilidade e ser igualmente felizes. Eles quase não fazem parte dessa classe. Se nenhum outro método depende dessa variável depois de mover o (s) método (s), essa variável também deve ser usada! Provavelmente a variável deve estar em seu próprio objeto, com esse método ou métodos que operam nela se tornando públicos e sendo compostos pela classe pai.
  4. A transmissão de dados para várias funções, como a sua classe, é um programa procedural com variáveis ​​globais que simplesmente foge do design do OO. Essa não é a intenção das variáveis ​​e funções dos membros (veja acima), e parece que sua classe não é muito coesa. Eu acho que é um cheiro de código que sugere uma maneira melhor de agrupar seus dados e métodos.
Colin Hunt
fonte
0

Você está ausente se o parâmetro ("argumento") for referência ou somente leitura.

class SomeClass
{
    protected SomeType _someField;
    public SomeType SomeField
    {
        get { return _someField; }

        set {
          if (doSomeValidation(value))
          {
            _someField = value;
          }
        }
    }

    protected virtual void ModifyMethod(/*...., */ ref SomeType someVar)
    { 
      // ...
    }    

    protected virtual void ReadMethod(/*...., */ SomeType someVar)
    { 
      // ...
    }

    private void SomeAnotherMethod()
    {
        //.............

        // not recommended, but, may be required in some cases
        ModifyMethod(ref this._someField);

        //.............

        // recommended, but, verbose
        SomeType SomeVar = this.someField;
        ModifyMethod(ref SomeVar);
        this.someField = SomeVar;

        //.............

        ReadMethod(this.someField);
        //.............
    }

};

Muitos desenvolvedores, geralmente atribuem diretamente os campos internos de uma variável, nos métodos construtores. Existem algumas exceções.

Lembre-se de que "setters" podem ter métodos adicionais, não apenas atribuição, e às vezes até métodos virtuais, ou chamar métodos virtuais.

Nota: Sugiro manter os campos internos das propriedades como "protegidos".

umlcat
fonte