Por que não é mais comum codificar os nomes dos argumentos nos nomes das funções? [fechadas]

8 respostas

64

Porque é mais para escrever e ler mais

A razão mais simples é que as pessoas gostam de digitar menos e codificar essa informação significa mais digitação. Ao lê-lo, toda vez que eu tenho que ler a coisa toda, mesmo que eu esteja familiarizado com a ordem dos argumentos. Mesmo que não esteja familiarizado com a ordem dos argumentos ...

Muitos desenvolvedores usam IDEs

Os IDEs geralmente fornecem um mecanismo para ver a documentação de um determinado método ao passar o mouse ou por meio de um atalho de teclado. Por causa disso, os nomes dos parâmetros estão sempre à mão.

A codificação dos argumentos introduz duplicação e acoplamento

Os nomes dos parâmetros já devem documentar o que são. Escrevendo os nomes no nome do método, também estamos duplicando essa informação na assinatura do método. Também criamos um acoplamento entre o nome do método e os parâmetros. Digamos que expected e actual sejam confusos para nossos usuários. Ir de assertEquals(expected, actual) a assertEquals(planned, real) não requer a alteração do código do cliente usando a função. Ir de assertExpectedEqualsActual(expected, actual) a assertPlannedEqualsReal(planned, real) significa uma alteração significativa na API. Ou nós não mudamos o nome do método, o que rapidamente se torna confuso.

Use tipos em vez de argumentos ambíguos

O problema real é que temos argumentos ambíguos que são facilmente alternados porque são do mesmo tipo. Em vez disso, podemos usar nosso sistema de tipos e nosso compilador para impor a ordem correta:

class Expected<T> {
    private T value;
    Expected(T value) { this.value = value; }
    static Expected<T> is(T value) { return new Expected<T>(value); }
}

class Actual<T> {
    private T value;
    Actual(T value) { this.value = value; }
    static Actual<T> is(T value) { return new Actual<T>(value); }
}

static assertEquals(Expected<T> expected, Actual<T> actual) { /* ... */ }

// How it is used
assertEquals(Expected.is(10), Actual.is(x));

Isso pode ser aplicado no nível do compilador e garante que você não possa obtê-los de volta. Aproximando-se de um ângulo diferente, isso é essencialmente o que a biblioteca Hamcrest faz para testes.

    
por 30.06.2018 / 16:34
fonte
20

Você pergunta sobre um debate de longa data na programação. Quanto verbosidade é boa? Como resposta geral, os desenvolvedores descobriram que a verbosidade extra que nomeia os argumentos não vale a pena.

A verbosidade nem sempre significa mais clareza. Considere

copyFromSourceStreamToDestinationStreamWithoutBlocking(fileStreamFromChoosePreferredOutputDialog, heuristicallyDecidedSourceFileHandle)

versus

copy(output, source)

Ambos contêm o mesmo bug, mas nós realmente tornamos mais fácil encontrar esse bug? Como regra geral, a coisa mais fácil de depurar é quando tudo é maximamente curto, exceto as poucas coisas que possuem o bug, e essas são verbosas o bastante para dizer o que deu errado.

Existe uma longa história de adição de verbosidade. Por exemplo, há a " notação húngara geralmente impopular, que nos deu nomes maravilhosos como lpszName . Isso geralmente caiu no esquecimento da população geral de programadores. No entanto, a adição de caracteres aos nomes de variáveis de membros (como mName ou m_Name ou name_ ) continua a ter popularidade em alguns círculos. Outros caíram isso completamente. Por acaso, trabalho em uma base de código de simulação física cujos documentos de estilo de código exigem que qualquer função que retorne um vetor deve especificar o quadro do vetor na chamada de função ( getPositionECEF ).

Você pode estar interessado em alguns dos idiomas populares da Apple. Objective-C inclui os nomes dos argumentos como parte da assinatura da função (A função [atm withdrawFundsFrom: account usingPin: userProvidedPin] está escrita na documentação como withdrawFundsFrom:usingPin: . Esse é o nome da função). O Swift fez um conjunto semelhante de decisões, exigindo que você colocasse os nomes dos argumentos nas chamadas de função ( greet(person: "Bob", day: "Tuesday") ).

    
por 01.07.2018 / 03:21
fonte
8

O autor de "Código Limpo" aponta um problema legítimo, mas sua solução sugerida é bastante deselegante. Geralmente, há maneiras melhores de melhorar os nomes de métodos pouco claros.

Ele está certo de que assertEquals (das bibliotecas de teste de unidade de estilo xUnit) não esclarece qual argumento é o esperado e qual é o real. Isso também me mordeu! Muitas bibliotecas de teste de unidade notaram o problema e introduziram sintaxes alternativas, como:

actual.Should().Be(expected);

Ou similar. Que é certamente muito mais claro que assertEquals mas também muito melhor que assertExpectedEqualsActual . E também é muito mais composível.

    
por 30.06.2018 / 17:52
fonte
5

Você está tentando direcionar seu caminho entre Scylla e Charybdis para maior clareza, tentando evitar a verbosidade inútil (também conhecida como divagação sem objetivo), bem como a brevidade excessiva (também conhecida como tonteira enigmática).

Então, temos que olhar para a interface que você quer avaliar, uma maneira de fazer afirmações de depuração de que dois objetos são iguais.

  1. Existe alguma outra função que poderia ser considerada aridade e nome?
    Não, então o nome em si é claro o suficiente.
  2. Os tipos de significado são significantes?
    Não, então vamos ignorá-los. Você já fez isso? Bom.
  3. É simétrica em seus argumentos?
    Quase, no erro, a mensagem coloca cada representação de argumentos em seu próprio local.

Então, vamos ver se essa pequena diferença tem algum significado e não é coberta pelas convenções strongs existentes.

O público-alvo é incomodado se os argumentos não forem trocados acidentalmente?
Não, os desenvolvedores também recebem um rastreio de pilha e eles têm que examinar o código-fonte para corrigir o bug. Mesmo sem um rastreamento de pilha completo, a posição de asserções resolve essa questão. E se mesmo isso está faltando e não é óbvio a partir da mensagem que é qual, no máximo, duplica as possibilidades.

Os argumentos seguem a convenção?
Parece ser o caso. Embora pareça, na melhor das hipóteses, uma convenção fraca.

Assim, a diferença parece bastante insignificante, e a ordem do argumento é coberta por convenção suficientemente strong de que qualquer esforço para colocá-la no nome-da-função tem utilidade negativa.

    
por 01.07.2018 / 16:34
fonte
3

Muitas vezes, não adiciona clareza lógica.

Compare "Adicionar" a "AddFirstArgumentToSecondArgument".

Se você precisar de uma sobrecarga que, por exemplo, adiciona três valores. O que faria mais sentido?

Outro "Adicionar" com três argumentos?

ou

"AddFirstAndSecondAndThirdArgument"?

O nome do método deve transmitir seu significado lógico. Deve dizer o que faz. Contar, em um nível micro, quais medidas são necessárias não facilitam para o leitor. Os nomes dos argumentos fornecerão detalhes adicionais, se necessário. Se você precisar de mais detalhes ainda, o código estará disponível para você.

    
por 30.06.2018 / 15:50
fonte
2

Gostaria de adicionar mais alguma coisa sugerida por outras respostas, mas não acho que tenha sido mencionado explicitamente:

@puck diz "Ainda não há garantia de que o primeiro argumento mencionado no nome da função seja realmente o primeiro parâmetro."

@cbojar diz "Use tipos em vez de argumentos ambíguos"

A questão é que as linguagens de programação não entendem nomes: elas são apenas tratadas como símbolos atômicos opacos. Assim, como com comentários de código, não há necessariamente qualquer correlação entre o que uma função é nomeada e como ela realmente opera.

Compare assertExpectedEqualsActual(foo, bar) com algumas alternativas (desta página e de outro lugar), como:

# Putting the arguments in a labelled structure
assertEquals({expected: foo, actual: bar})

# Using a keyword arguments language feature
assertEquals(expected=foo, actual=bar)

# Giving the arguments different types, forcing us to wrap them
assertEquals(Expected(foo), Actual(bar))

# Breaking the symmetry and attaching the code to one of the arguments
bar.Should().Be(foo)

Todos eles têm mais estrutura do que o nome detalhado, o que dá à linguagem algo não-opaco para olhar. A definição e o uso da função também dependem dessa estrutura, portanto, ela não pode ficar fora de sincronia com o que a implementação está fazendo (como um nome ou comentário pode fazer).

Quando eu encontro ou vejo um problema como esse, antes de gritar com o computador em frustração, primeiro paro um momento para perguntar se é "justo" culpar a máquina. Em outras palavras, a máquina recebeu informações suficientes para distinguir o que eu queria do que eu pedia?

Uma chamada como assertEqual(expected, actual) faz tanto sentido quanto assertEqual(actual, expected) , então é fácil misturá-los e para que a máquina avance e faça a coisa errada. Se usássemos assertExpectedEqualsActual , poderia <<> menos probabilidade de cometer um erro, mas não fornece mais informações para a máquina (não é possível entender inglês e a escolha do nome não deve afetar semântica).

O que torna as abordagens "estruturadas" mais preferíveis, como argumentos de palavras-chave, campos rotulados, tipos distintos, etc., é que a informação extra também é legível por máquina , para que a máquina fique incorreta usos e nos ajudar a fazer as coisas direito. O caso assertEqual não é tão ruim, já que o único problema seria mensagens imprecisas. Um exemplo mais sinistro pode ser String replace(String old, String new, String content) , o que é fácil de confundir com String replace(String content, String old, String new) , que tem um significado muito diferente. Um remédio simples seria pegar um par [old, new] , o que faria com que erros acionassem um erro imediatamente (mesmo sem tipos).

Note que, mesmo com os tipos, podemos nos ver não "informando à máquina o que queremos". Por exemplo, o anti-padrão chamado "programação tipada" trata todos os dados como strings, o que facilita a confundir os argumentos (como neste caso), esquecer de executar algum passo (por exemplo, escapar), acidentalmente quebrar invariantes (eg fazendo JSON inigualável), etc.

Isso também está relacionado a "cegueira booleana", onde calculamos um monte de booleanos (ou números, etc.) em uma parte do código, mas ao tentar usá-los em outro não está claro o que eles realmente representando, se os temos misturados, etc. Compare isto com por exemplo enums distintos que têm nomes descritivos (por exemplo, LOGGING_DISABLED em vez de false ) e que causam uma mensagem de erro se os misturarmos.

    
por 02.07.2018 / 17:07
fonte
1

because it removes the need to remember where the arguments go

Realmente? Ainda não há garantia de que o primeiro argumento mencionado no nome da função seja realmente o primeiro parâmetro. Então, é melhor procurar (ou deixar seu IDE fazer isso) e ficar com nomes razoáveis do que confiar cegamente em um nome bastante bobo.

Se você ler o código, verá facilmente o que acontece quando os parâmetros são nomeados como deveriam. copy(source, destination) é muito mais fácil de entender do que algo parecido com copyFromTheFirstLocationToTheSecondLocation(placeA, placeB) .

Why don't coders adopt the former if it is, as the author asserts, clearer than the latter?

Porque existem diferentes pontos de vista em diferentes estilos e você pode encontrar x autores de outros artigos que afirmam o contrário. Você ficaria louco tentando seguir tudo que alguém escreve em algum lugar; -)

    
por 01.07.2018 / 15:16
fonte
0

Concordo que a codificação de nomes de parâmetros em nomes de funções torna a escrita e o uso de funções mais intuitivos.

copyFromSourceToDestination( // "...ahh yes, the source directory goes first"

É fácil esquecer a ordenação de argumentos em funções e comandos de shell e muitos programadores contam com recursos IDE ou referências de função por esse motivo. Ter os argumentos descritos no nome seria uma solução eloqüente para essa dependência.

No entanto, uma vez escrita, a descrição dos argumentos torna-se redundante para o próximo programador, que deve ler a instrução, pois na maioria dos casos, variáveis nomeadas serão usadas.

copy(sourceDir, destinationDir); // "...makes sense"

A clareza disto irá conquistar a maioria dos programadores e eu pessoalmente acho mais fácil de ler.

EDIT: Como o @Blrfl apontou, os parâmetros de codificação não são tão 'intuitivos' afinal desde que você precisa lembrar o nome da função em primeiro lugar. Isso requer procurar referências de função ou obter ajuda de um IDE, o que provavelmente fornecerá informações de pedido de parâmetros de qualquer maneira.

    
por 30.06.2018 / 16:42
fonte

Tags