Existe um motivo para não modificar valores de parâmetros passados ​​por valor?

9

Existem argumentos de engenharia de software objetivos e compatíveis a favor ou contra a modificação dos valores dos parâmetros por valor no corpo de uma função?

Uma briga recorrente (principalmente de boa diversão) na minha equipe é se os parâmetros passados ​​por valor devem ou não ser modificados. Alguns membros da equipe são inflexíveis aos quais os parâmetros nunca devem ser atribuídos, para que o valor passado originalmente para a função possa sempre ser interrogado. Não concordo e afirmo que os parâmetros nada mais são do que variáveis ​​locais inicializadas pela sintaxe de chamar o método; se o valor original de um parâmetro por valor for importante, uma variável local poderá ser declarada para armazenar explicitamente esse valor. Não estou confiante de que um de nós tenha um apoio muito bom à nossa posição.

Esse é um conflito religioso não resolvível ou existem boas razões objetivas de engenharia de software em qualquer direção?

Nota: A questão do princípio permanece independentemente dos detalhes de implementação do idioma específico. Em JavaScript, por exemplo, onde a lista de argumentos é sempre dinâmica, os parâmetros podem ser considerados como açúcar sintático para a inicialização da variável local a partir do argumentsobjeto. Mesmo assim, pode-se tratar os identificadores declarados por parâmetros como "especiais" porque eles ainda capturam a passagem de informações do chamador para o chamado.

Joshua Honig
fonte
Ainda é específico do idioma; argumentar que é independente da linguagem porque "é assim, do ponto de vista da minha (língua favorita)" é uma forma de argumento inválido. Uma segunda razão pela qual é específico de um idioma é porque uma pergunta que não tem resposta universal para todos os idiomas está sujeita às culturas e normas de cada idioma; nesse caso, diferentes normas específicas de idioma resultam em respostas diferentes para essa pergunta.
Rwong 16/04
O princípio que os membros da sua equipe estão tentando ensinar a você é "uma variável, um propósito". Infelizmente, você não encontrará provas definitivas de qualquer maneira. Pelo que vale, não me lembro de ter cooptado uma variável de parâmetro para algum outro propósito, nem me lembro de ter considerado isso. Nunca me ocorreu que poderia ser uma boa ideia, e ainda acho que não.
21818 Robert Harvey
Eu pensei que isso deveria ter sido perguntado antes, mas o único idiota que eu pude encontrar é essa pergunta mais antiga do SO .
Doc Brown
3
Considera-se menos propenso a erros uma linguagem proibir a mutação de argumentos, assim como os projetistas de linguagens funcionais consideram que as atribuições de "deixar" sejam menos propensas a erros. Duvido que alguém tenha provado essa suposição usando um estudo.
31718 Frank Hileman #

Respostas:

15

Não concordo e afirmo que os parâmetros nada mais são do que variáveis ​​locais inicializadas pela sintaxe de chamar o método

Adotei uma terceira posição: parâmetros são como variáveis ​​locais: ambos devem ser tratados como imutáveis ​​por padrão. Portanto, as variáveis ​​são atribuídas uma vez e somente lidas, não alteradas. No caso de loops, for each (x in ...)a variável é imutável no contexto de cada iteração. As razões para isso são:

  1. facilita o código "executar na minha cabeça",
  2. permite nomes mais descritivos para as variáveis.

Diante de um método com várias variáveis ​​atribuídas e que não mudam, posso me concentrar na leitura do código, em vez de tentar lembrar o valor atual de cada uma dessas variáveis.

Diante do mesmo método, desta vez com menos variáveis, mas que mudam de valor o tempo todo, agora tenho que lembrar o estado atual dessas variáveis, além de trabalhar no que o código está fazendo.

Na minha experiência, o primeiro é muito mais fácil para o meu cérebro ruim. Outros, pessoas mais inteligentes e inteligentes que eu não podem ter esse problema, mas isso me ajuda.

Quanto ao outro ponto:

double Foo(double r)
{
    r = r * r;
    r = Math.Pi * r;
    return r;
}

versus

double Foo(int r)
{
    var rSquared = r * r;
    var area = Math.Pi * rSquared;
    return area;
} 

Exemplos planejados, mas, na minha opinião, é muito mais claro o que está acontecendo no segundo exemplo devido aos nomes das variáveis: eles transmitem muito mais informações ao leitor do que aquela massa de rno primeiro exemplo.

David Arno
fonte
"variáveis ​​locais .... devem ser tratadas como imutáveis ​​por padrão." - O que?
Whatsisname
11
É muito difícil executar forloops com variáveis ​​imutáveis. Encapsular a variável na função não é suficiente para você? Se você está tendo problemas para seguir suas variáveis ​​em uma mera função, talvez suas funções sejam muito longas.
21818 Robert Harvey
O @RobertHarvey for (const auto thing : things)é um loop for, e a variável que ele introduz é imutável (localmente). for i, thing in enumerate(things):também não se transformar qualquer moradores
Caleth
3
Este é o IMHO em geral, um modelo mental muito bom. No entanto, gostaria de acrescentar uma coisa: geralmente é aceitável inicializar uma variável em mais de uma etapa e tratá-la como imutável posteriormente. Isso não é contra a estratégia geral apresentada aqui, apenas contra seguir sempre isso literalmente.
Doc Brown
3
Uau, lendo esses comentários, posso ver que muitas pessoas aqui obviamente nunca estudaram o paradigma funcional.
Joshua Jones
4

Esse é um conflito religioso não resolvível ou existem boas razões objetivas de engenharia de software em qualquer direção?

Essa é uma coisa que eu realmente gosto em C ++ (bem escrito): você pode ver o que esperar. const string& x1é uma referência constante, string x2é uma cópia e const string x3é uma cópia constante. Eu sei o que vai acontecer na função. x2 será alterado, porque se não fosse, não haveria razão para torná-lo não-const.

Portanto, se você tiver um idioma que permita , declare o que está prestes a fazer com os parâmetros. Essa deve ser a melhor escolha para todos os envolvidos.

Se o seu idioma não permitir isso, receio que não exista solução de bala de prata. Ambos são possíveis. A única orientação é o princípio da menor surpresa. Adote uma abordagem e siga-a por toda a sua base de código, não a misture.

nvoigt
fonte
Outra coisa boa é que int f(const T x)e int f(T x)só são diferentes no corpo da função.
Deduplicator
3
Uma função C ++ com um parâmetro como const int xapenas para esclarecer x não é alterada por dentro? Parece um estilo muito estranho para mim.
Doc Brown
@DocBrown Eu não estou julgando nenhum dos estilos, apenas estou dizendo que alguém que pensa que os parâmetros não devem ser alterados pode fazer o compilador garantir isso e alguém que acha que mudá-los é bom também pode fazê-lo. Ambas as intenções podem ser claramente comunicadas através do próprio idioma e isso é uma grande vantagem sobre um idioma que não pode garantir isso de qualquer maneira e em que você deve confiar em diretrizes arbitrárias e esperar que todos as leiam e estejam em conformidade com elas.
Nvoigt 17/04/19
@ nvoigt: se alguém preferir modificar um parâmetro de função ("por valor"), ele simplesmente removerá a constlista de parâmetros se isso o impedir. Então, acho que isso não responde à pergunta.
Doc Brown
@DocBrown Então não é mais const. Não é importante, seja constante ou não, o importante é que a linguagem inclua uma maneira de comunicar isso ao desenvolvedor sem dúvida. Minha resposta é que isso não importa, contanto que você o comunique claramente , o que o C ++ facilita, outros não, e você precisa de convenções.
Nvoigt 17/04/19
1

Sim, é um "conflito religioso", exceto que Deus não lê programas (tanto quanto eu sei), então é rebaixado para um conflito de legibilidade que se torna uma questão de julgamento pessoal. E eu certamente fiz isso, e vi isso feito, nos dois sentidos. Por exemplo,

propagação nula (objeto OBJECT *, t0 duplo, dt duplo) {
  duplo t = t0; / * cópia local de t0 para evitar alterar seu valor * /
  enquanto (tanto faz) {
    timestep_the_object (...);
    t + = dt; }
  Retorna; }

Então aqui, apenas por causa do nome do argumento t0 , eu provavelmente escolheria não alterar seu valor. Mas suponha que apenas mudássemos o nome do argumento para tNow ou algo assim. Então, muito mais do que provável, esqueci uma cópia local e simplesmente escrevi tNow + = dt; para essa última afirmação.

Mas, pelo contrário, suponha que eu soubesse que o cliente para quem o programa foi escrito está prestes a contratar alguns novos programadores com muito pouca experiência, que estariam lendo e trabalhando nesse código. Então eu provavelmente me preocuparia em confundi-los com passagem por valor versus referência, enquanto suas mentes estão 100% ocupadas tentando digerir toda a lógica de negócios. Portanto, nesse tipo de caso, eu sempre declararia uma cópia local de qualquer argumento que será alterado, independentemente de ser mais ou menos legível para mim.

As respostas para esse tipo de questão de estilo surgem com a experiência e raramente têm uma resposta religiosa canônica. Qualquer um que pense que há uma resposta única (e que ele sabe disso) provavelmente precisa de mais experiência.

John Forkosh
fonte