Eu estava pensando como seria mais fácil ler o código se, ao chamar uma função, você pudesse escrever:
doFunction(param1=something, param2=somethingElse);
Não consigo pensar em nenhuma desvantagem e isso tornaria o código muito mais legível. Eu sei que você poderia passar uma matriz como o único argumento e ter as chaves da matriz como os nomes dos parâmetros, no entanto, isso ainda não seria tão legível.
Existe uma desvantagem disso que estou perdendo? Caso contrário, por que muitos idiomas não permitem isso?
Respostas:
A especificação de nomes de parâmetros nem sempre torna o código mais legível: como exemplo, você prefere ler
min(first=0, second=1)
oumin(0, 1)
?Se você aceitar o argumento do parágrafo anterior, é óbvio que especificar nomes de parâmetros não deve ser obrigatório. Por que todos os idiomas não tornam a especificação de nomes de parâmetros uma opção válida?
Eu posso pensar em pelo menos duas razões:
Observe que eu não conheço nenhuma linguagem que implemente parâmetros nomeados sem também implementar parâmetros opcionais (que exigem parâmetros nomeados) - portanto, você deve desconfiar de superestimar seus benefícios de legibilidade, que podem ser obtidos de forma mais consistente com a definição de interfaces fluentes .
fonte
Parâmetros nomeados tornam o código mais fácil de ler e mais difícil de escrever
Quando estou lendo um pedaço de código, os parâmetros nomeados podem introduzir um contexto que facilita a compreensão do código. Considere, por exemplo, este construtor:
Color(1, 102, 205, 170)
. O que diabos isso significa? Na verdade,Color(alpha: 1, red: 102, green: 205, blue: 170)
seria muito mais fácil de ler. Mas, infelizmente, o Compilador diz "não" - ele querColor(a: 1, r: 102, g: 205, b: 170)
. Ao escrever código usando parâmetros nomeados, você gasta um tempo desnecessário pesquisando os nomes exatos - é mais fácil esquecer os nomes exatos de alguns parâmetros do que esquecer sua ordem.Isso me incomodou ao usar uma
DateTime
API que tinha duas classes de irmãos por pontos e durações com interfaces quase idênticas. EnquantoDateTime->new(...)
aceitou umsecond => 30
argumento, oDateTime::Duration->new(...)
procuradoseconds => 30
e semelhante para outras unidades. Sim, faz absolutamente sentido, mas isso me mostrou que os parâmetros nomeados são fáceis de usar.Nomes ruins nem facilitam a leitura
Outro exemplo de como os parâmetros nomeados podem ser ruins é provavelmente a linguagem R. Este pedaço de código cria um gráfico de dados:
Você vê dois argumentos posicionais para o x e y linhas de dados, e em seguida uma lista de parâmetros nomeados. Existem muito mais opções com padrões, e apenas aquelas listadas cujos padrões eu queria alterar ou especificar explicitamente. Depois que ignoramos que esse código usa números mágicos e que podemos nos beneficiar do uso de enumerações (se R tiver algum!), O problema é que muitos desses nomes de parâmetros são indecifráveis.
pch
é realmente o caractere da plotagem, o glifo que será desenhado para cada ponto de dados.17
é um círculo vazio, ou algo assim.lty
é o tipo de linha. Aqui1
está uma linha sólida.bty
é o tipo de caixa. A configuração para"n"
evitar que uma caixa seja desenhada ao redor da plotagem.ann
controla a aparência das anotações do eixo.Para alguém que não sabe o que significa cada abreviação, essas opções são bastante confusas. Isso também revela por que o R usa esses rótulos: não como código de auto-documentação, mas (sendo uma linguagem de tipo dinâmico) como chaves para mapear os valores para suas variáveis corretas.
Propriedades de parâmetros e assinaturas
As assinaturas de funções podem ter as seguintes propriedades:
Idiomas diferentes chegam a coordenadas diferentes deste sistema. Em C, os argumentos são ordenados, sem nome, sempre necessários e podem ser varargs. Em Java, a situação é semelhante, exceto que as assinaturas podem ser sobrecarregadas. No Objetivo C, as assinaturas são ordenadas, nomeadas, necessárias e não podem ser sobrecarregadas porque é apenas açúcar sintático em torno de C.
Linguagens digitadas dinamicamente com varargs (interfaces de linha de comando, Perl,…) podem emular parâmetros nomeados opcionais. Os idiomas com sobrecarga de tamanho de assinatura têm algo como parâmetros opcionais posicionais.
Como não implementar parâmetros nomeados
Ao pensar em parâmetros nomeados, geralmente assumimos parâmetros nomeados, opcionais e não ordenados. Implementar isso é difícil.
Parâmetros opcionais podem ter valores padrão. Eles devem ser especificados pela função chamada e não devem ser compilados no código de chamada. Caso contrário, os padrões não poderão ser atualizados sem recompilar todo o código dependente.
Agora, uma pergunta importante é como os argumentos são realmente passados para a função. Com parâmetros ordenados, os argumentos podem ser passados em um registro ou em sua ordem inerente na pilha. Quando excluímos os registros por um momento, o problema é como colocar argumentos opcionais não ordenados na pilha.
Para isso, precisamos de alguma ordem sobre os argumentos opcionais. E se o código da declaração for alterado? Como a ordem é irrelevante, uma nova ordem na declaração da função não deve alterar a posição dos valores na pilha. Também devemos considerar se é possível adicionar um novo parâmetro opcional. Da perspectiva dos usuários, parece que sim, porque o código que não usava esse parâmetro anteriormente ainda deve funcionar com o novo parâmetro. Portanto, isso exclui pedidos como usar o pedido na declaração ou usar o pedido alfabético.
Considere isso também à luz da subtipagem e do Princípio de Substituição de Liskov - na saída compilada, as mesmas instruções devem poder invocar o método em um subtipo com possíveis parâmetros nomeados e em um supertipo.
Possíveis implementações
Se não podemos ter uma ordem definitiva, precisamos de alguma estrutura de dados não ordenada.
A implementação mais simples é simplesmente passar o nome dos parâmetros junto com os valores. É assim que os parâmetros nomeados são emulados no Perl ou com as ferramentas de linha de comando. Isso resolve todos os problemas de extensão mencionados acima, mas pode ser um enorme desperdício de espaço - não uma opção no código crítico de desempenho. Além disso, o processamento desses parâmetros nomeados agora é muito mais complicado do que simplesmente retirar valores de uma pilha.
Na verdade, os requisitos de espaço podem ser reduzidos usando o pool de strings, o que pode reduzir comparações posteriores de strings a comparações de ponteiros (exceto quando não é possível garantir que as strings estáticas sejam realmente agrupadas, nesse caso, as duas strings terão que ser comparadas em detalhe).
Em vez disso, também poderíamos passar uma estrutura de dados inteligente que funciona como um dicionário de argumentos nomeados. Isso é barato para o chamador, porque o conjunto de teclas é estaticamente conhecido no local da chamada. Isso permitiria criar uma função de hash perfeita ou pré-calcular um trie. O receptor ainda terá que testar a existência de todos os nomes de parâmetros possíveis, o que é um pouco caro. Algo assim é usado pelo Python.
Portanto, é muito caro na maioria dos casos
Se uma função com parâmetros nomeados deve ser adequadamente extensível, uma ordem definitiva não pode ser assumida. Portanto, existem apenas duas soluções:
Outras armadilhas
Os nomes de variáveis em uma declaração de função geralmente têm algum significado interno e não fazem parte da interface - mesmo que muitas ferramentas de documentação ainda os mostrem. Em muitos casos, você deseja nomes diferentes para uma variável interna e o argumento nomeado correspondente. Os idiomas que não permitem escolher os nomes visíveis externamente de um parâmetro nomeado não os ganham muito se o nome da variável não for usado com o contexto de chamada em mente.
Um problema com emulações de argumentos nomeados é a falta de verificação estática no lado do chamador. Isso é especialmente fácil de esquecer ao passar um dicionário de argumentos (olhando para você, Python). Isto é importante porque a passagem de um dicionário é uma solução comum, por exemplo, em JavaScript:
foo({bar: "baz", qux: 42})
. Aqui, nem os tipos dos valores nem a existência ou ausência de determinados nomes podem ser verificados estaticamente.Emulando parâmetros nomeados (em idiomas estaticamente tipados)
O simples uso de strings como chaves e qualquer objeto como valor não é muito útil na presença de um verificador de tipo estático. No entanto, argumentos nomeados podem ser emulados com estruturas ou literais de objeto:
fonte
it is easier to forget the exact names of some parameters than it is to forget their order
" ... essa é uma afirmação interessante.Pela mesma razão que a notação húngara não é mais amplamente usada; Intellisense (ou seu equivalente moral em IDE não Microsoft). A maioria dos IDE modernos dirá tudo o que você precisa saber sobre um parâmetro, apenas passando o mouse sobre a referência do parâmetro.
Dito isto, várias linguagens suportam parâmetros nomeados, incluindo C # e Delphi. No C #, é opcional; portanto, você não precisa usá-lo se não quiser, e há outras maneiras de declarar membros especificamente, como a inicialização de objetos.
Parâmetros nomeados são úteis principalmente quando você deseja especificar apenas um subconjunto de parâmetros opcionais, e não todos. No C #, isso é muito útil porque você não precisa mais de vários métodos sobrecarregados para fornecer ao programador essa flexibilidade.
fonte
sin(radians=2)
, não precisa do "Intellisense".O Bash certamente suporta esse estilo de programação, e o Perl e o Ruby suportam listas de parâmetros de nome / valor (como qualquer idioma com suporte nativo a hash / mapa). Nada impede que você escolha definir uma estrutura / registro ou hash / mapa que anexa nomes aos parâmetros.
Para idiomas que incluem armazenamentos de hash / map / keyvalue nativamente, você pode obter a funcionalidade desejada, simplesmente adotando o idioma para passar hashes de chave / valor para suas funções / métodos. Depois de experimentar em um projeto, você terá uma ideia melhor sobre se ganhou alguma coisa, seja pelo aumento da produtividade derivado da facilidade de uso ou pela melhoria da qualidade devido a defeitos reduzidos.
Observe que programadores experientes se acostumaram a passar parâmetros por pedido / slot. Você também pode achar que esse idioma só é valioso quando você tiver mais do que alguns argumentos (digamos> 5/6). E como um grande número de argumentos geralmente é indicativo de métodos (excessivamente) complexos, você pode achar que esse idioma é apenas benéfico para os métodos mais complexos.
fonte
Eu acho que é a mesma razão pela qual c # é mais popular que o VB.net. Enquanto o VB.NET é mais "legível", por exemplo, você digita "end if" em vez de um colchete de fechamento, mas acaba sendo mais um material que preenche o código e acaba dificultando a compreensão.
Acontece que o que torna o código mais fácil de entender é a concisão. Quanto menos, melhor. Os nomes dos parâmetros de função são normalmente bastante óbvios e não ajudam muito a entender o código.
fonte
Os parâmetros nomeados são uma solução para um problema na refatoração do código-fonte e não se destinam a tornar o código-fonte mais legível . Parâmetros nomeados são usados para auxiliar o compilador / analisador na resolução de valores padrão para chamadas de função. Se você deseja tornar o código mais legível, adicione comentários significativos.
Os idiomas difíceis de refatorar geralmente oferecem suporte a parâmetros nomeados, porque serão
foo(1)
interrompidos quando a assinatura dasfoo()
alterações, masfoo(name:1)
são menos propensos a serem quebrados, exigindo menos esforço do programador para fazer alteraçõesfoo
.O que você faz quando precisa introduzir um novo parâmetro para a função a seguir e existem centenas ou milhares de linhas de código chamando essa função?
A maioria dos programadores fará o seguinte.
Agora, nenhuma refatoração é necessária e a função pode ser executada no modo legado quando
gender
for nulo.Para especificar
gender
um valor agora é HARDCODED na chamadaage
. Como este exemplo:O programador olhou para a definição da função, viu que
age
tinha um valor padrão0
e usou esse valor.Agora, o valor padrão para
age
a definição da função é completamente inútil.Parâmetros nomeados permitem evitar esse problema.
Novos parâmetros podem ser adicionados
foo
e você não precisa se preocupar com a ordem em que foram adicionados e pode alterar os valores padrão sabendo que o padrão definido é realmente o valor padrão verdadeiro.fonte