Considere o seguinte método:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
}
E a seguinte chamada:
var ids = ReturnEmployeeIds(true);
Para um desenvolvedor iniciante no sistema, seria muito difícil adivinhar o que true
fez. A primeira coisa a fazer é passar o mouse sobre o nome do método ou ir para a definição (nenhuma das quais são grandes tarefas, no mínimo). Mas, para facilitar a leitura, faz sentido escrever:
var ids = ReturnEmployeeIds(includeManagement: true);
Existe algum lugar que, formalmente, discuta se é necessário especificar explicitamente parâmetros opcionais quando o compilador não precisa de você?
O seguinte artigo discute certas convenções de codificação: https://msdn.microsoft.com/en-gb/library/ff926074.aspx
Algo semelhante ao artigo acima seria ótimo.
c#
coding-style
readability
parameters
JᴀʏMᴇᴇ
fonte
fonte
boolean
argumentos de método e como?Respostas:
Eu diria que no mundo C #, uma enumeração seria uma das melhores opções aqui.
Com isso, você seria forçado a explicar o que está fazendo e qualquer usuário verá o que está acontecendo. Também é seguro para futuras extensões;
Então, na minha opinião aqui:
enum> enum opcional> bool opcional
Edit: Devido à discussão com LeopardSkinPillBoxHat abaixo sobre uma
[Flags]
enumeração que, nesse caso específico, pode ser uma boa opção (já que estamos falando especificamente sobre incluir / excluir coisas), proponho usarISet<WhatToInclude>
como parâmetro.É um conceito mais "moderno", com várias vantagens, principalmente devido ao fato de ele se encaixar na família de coleções LINQ, mas também
[Flags]
com um máximo de 32 grupos. A principal desvantagem do usoISet<WhatToInclude>
é o quão mal os conjuntos são suportados na sintaxe C #:Algumas delas podem ser atenuadas por funções auxiliares, tanto para gerar o conjunto quanto para criar "conjuntos de atalhos", como
fonte
enum
abordagem, mas não sou muito fã de mixagensInclude
eExclude
, da mesmaenum
forma, é mais difícil entender o que está acontecendo. Se você quiser que isso seja realmente extensível, você pode torná-lo um[Flags]
enum
, torná-lo todosIncludeXxx
e fornecer umIncludeAll
que esteja definido comoInt32.MaxValue
. Em seguida, você pode fornecer duasReturnEmployeeIds
sobrecargas - uma que retorna todos os funcionários e outra que aceita umWhatToInclude
parâmetro de filtro que pode ser uma combinação de sinalizadores de enumeração.[Flags]
é uma relíquia e só deve ser usada por motivos de legado ou desempenho. Eu acho que aISet<WhatToInclude>
é um conceito muito melhor (infelizmente o C # está faltando um pouco de açúcar sintático para torná-lo agradável de usar).Flags
abordagem porque permite que o chamador passeWhatToInclude.Managers | WhatToInclude.Cleaners
e o bit a bit ou expressa exatamente como o chamador irá processá-lo (ou seja, gerentes ou limpadores). Intuitivo de açúcar sintático :-)É discutível se este é "bom estilo", mas IMHO este é o estilo menos não ruim e útil, desde que a linha de código não se torne "muito longa". Sem parâmetros nomeados, também é um estilo comum introduzir uma variável explicativa:
Esse estilo é provavelmente mais comum entre os programadores de C # do que a variante de parâmetro nomeado, pois os parâmetros nomeados não faziam parte do idioma anterior à Versão 4.0. O efeito na legibilidade é quase o mesmo, apenas precisa de uma linha de código adicional.
Portanto, minha recomendação: se você acha que usar uma enumeração ou duas funções com nomes diferentes é "exagero" ou não deseja ou não pode alterar a assinatura por um motivo diferente, vá em frente e use o parâmetro named.
fonte
includeManagement
como localconst
e evitar o uso de qualquer memória adicional.Se você encontrar código como:
você pode praticamente garantir o que está dentro do
{}
algo como:Em outras palavras, o booleano é usado para escolher entre dois cursos de ação: o método tem duas responsabilidades. É praticamente garantido um cheiro de código . Devemos sempre nos esforçar para garantir que os métodos façam apenas uma coisa; eles só têm uma responsabilidade. Portanto, eu diria que as duas soluções são a abordagem errada. O uso de parâmetros opcionais é um sinal de que o método está fazendo mais do que uma coisa. Parâmetros booleanos também são um sinal disso. Tendo os dois definitivamente deveria tocar os alarmes. O que você deve fazer, portanto, é refatorar os dois conjuntos diferentes de funcionalidades em dois métodos separados. Dê aos nomes dos métodos que deixem claro o que eles fazem, por exemplo:
Isso melhora a legibilidade do código e garante que você siga melhor os princípios do SOLID.
EDIT Como apontado nos comentários, o caso aqui de que esses dois métodos provavelmente terão funcionalidade compartilhada e que funcionalidade compartilhada provavelmente serão agrupados em uma função privada que precisará de um parâmetro booleano para controlar algum aspecto do que ele faz .
Nesse caso, o nome do parâmetro deve ser fornecido, mesmo que o compilador não precise dele? Eu sugiro que não haja uma resposta simples para isso e que o julgamento seja necessário caso a caso:
O arquivo de origem e os métodos são pequenos e fáceis de entender? Se sim, então o parâmetro nomeado provavelmente equivale a ruído. Se não, pode ser muito útil. Portanto, aplique a regra de acordo com as circunstâncias.
fonte
public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }
uma divisão tão simples que, em dois métodos, provavelmente significará que esses dois métodos precisarão do resultado do primeiro cálculo como parâmetro.Sim, verdade. O código de auto-documentação é uma coisa bonita. Como outras respostas apontaram, existem maneiras de remover a ambiguidade da chamada
ReturnEmployeeIds
usando uma enumeração, nomes de métodos diferentes etc. No entanto, às vezes, você não pode evitar o caso, mas não deseja usar o enum. em todos os lugares (a menos que você goste da verbosidade do visual basic).Por exemplo, pode ajudar a esclarecer uma ligação, mas não necessariamente outra.
Um argumento nomeado pode ser útil aqui:
Não adicione muita clareza adicional (acho que isso está analisando uma enumeração):
Na verdade, pode reduzir a clareza (se uma pessoa não entender qual é a capacidade de uma lista):
Ou onde isso não importa, porque você está usando bons nomes de variáveis:
Não é AFAIK. Vamos abordar as duas partes: o único momento em que você precisa usar explicitamente os parâmetros nomeados é porque o compilador exige que você faça isso (geralmente é para remover a ambiguidade da instrução). Além disso, você pode usá-los sempre que quiser. Este artigo da MS tem algumas sugestões sobre quando você pode usá-los, mas não é particularmente definitivo https://msdn.microsoft.com/en-us/library/dd264739.aspx .
Pessoalmente, acho que os uso com mais frequência quando estou criando atributos personalizados ou criando um novo método no qual meus parâmetros podem ser alterados ou reordenados (os atributos nomeados b / c podem ser listados em qualquer ordem). Além disso, eu os uso apenas quando estou fornecendo um valor estático inline, caso contrário, se estou passando uma variável, tento usar bons nomes de variáveis.
Em última análise, tudo se resume à preferência pessoal. É claro que existem alguns casos de uso em que você precisa usar parâmetros nomeados, mas depois disso você precisa fazer uma chamada de julgamento. IMO - Use-o em qualquer lugar que você acredite que ajude a documentar seu código, reduza a ambiguidade ou permita proteger assinaturas de métodos.
fonte
Se o parâmetro é óbvio a partir do nome (totalmente qualificado) do método e sua existência é natural para o que o método deve fazer, você não deve usar a sintaxe do parâmetro nomeado. Por exemplo,
ReturnEmployeeName(57)
é bastante óbvio que esse57
é o ID do funcionário, portanto é redundante anotá-lo com a sintaxe do parâmetro nomeado.Com
ReturnEmployeeIds(true)
, como você disse, a menos que você observe a declaração / documentação da função, é impossível descobrir o quetrue
significa - portanto, você deve usar a sintaxe do parâmetro nomeado.Agora, considere este terceiro caso:
A sintaxe do parâmetro nomeado não é usada aqui, mas como estamos passando uma variável para
ReturnEmployeeIds
, e essa variável tem um nome, esse nome significa a mesma coisa que o parâmetro para o qual estamos passando (esse geralmente é o caso - mas nem sempre!) - não precisamos da sintaxe do parâmetro nomeado para entender o significado do código.Portanto, a regra é simples - se você puder entender facilmente apenas da chamada do método o que o parâmetro deve significar, não use o parâmetro nomeado. Se você não puder - isso é uma deficiência na legibilidade do código, e você provavelmente deseja corrigi-los (não a qualquer preço, é claro, mas você geralmente deseja que o código seja legível). A correção não precisa ter nomes de parâmetros - você também pode colocar o valor em uma variável primeiro ou usar os métodos sugeridos nas outras respostas - desde que o resultado final facilite a compreensão do que o parâmetro faz.
fonte
ReturnEmployeeName(57)
torna imediatamente óbvio que esse57
é o ID.GetEmployeeById(57)
por outro lado ...ReturnEmployeeName
para demonstrar casos em que, mesmo sem mencionar explicitamente o parâmetro no nome do método, é bastante razoável esperar que as pessoas descubram o que é. De qualquer forma, vale ressaltar queReturnEmployeeName
, diferentemente , você não pode renomear razoavelmenteReturnEmployeeIds
para algo que indique o significado por trás dos parâmetros.Sim, acho que é um bom estilo.
Isso faz mais sentido para constantes booleanas e inteiras.
Se você estiver passando uma variável, o nome da variável deve deixar claro o significado. Caso contrário, você deve dar um nome melhor à variável.
Se você está passando uma string, o significado geralmente é claro. Como se eu vir uma chamada para GetFooData ("Oregon"), acho que um leitor pode adivinhar que o parâmetro é um nome de estado.
Nem todas as cordas são óbvias, é claro. Se eu vir GetFooData ("M"), a menos que conheça a função, não faço ideia do que "M" significa. Se for algum tipo de código, como M = "funcionários em nível de gerenciamento", provavelmente devemos ter uma enumeração em vez de uma literal, e o nome da enumeração deve ser claro.
E suponho que uma string possa ser enganosa. Talvez "Oregon" no meu exemplo acima se refira, não ao estado, mas a um produto ou cliente ou qualquer outra coisa.
Mas GetFooData (true) ou GetFooData (42) ... isso pode significar qualquer coisa. Os parâmetros nomeados são uma maneira maravilhosa de se auto-documentar. Eu os usei exatamente para esse fim em várias ocasiões.
fonte
Não há motivos super claros para usar esse tipo de sintaxe em primeiro lugar.
Em geral, tente evitar ter argumentos booleanos que à distância podem parecer arbitrários.
includeManagement
(provavelmente) afetará bastante o resultado. Mas o argumento parece ter "pouco peso".O uso de um enum foi discutido, não apenas parecerá que o argumento "carrega mais peso", mas também fornecerá escalabilidade para o método. No entanto, essa pode não ser a melhor solução em todos os casos, já que o seu
ReturnEmployeeIds
método-terá que ser escalado ao lado doWhatToInclude
-enum (consulte a resposta de NiklasJ ). Isso pode causar dor de cabeça mais tarde.Considere o seguinte: Se você dimensionar o
WhatToInclude
método -enum, mas não oReturnEmployeeIds
método. Pode então lançar umArgumentOutOfRangeException
(melhor caso) ou retornar algo completamente indesejado (null
ou vazioList<Guid>
). O que, em alguns casos, pode confundir o programador, especialmente se vocêReturnEmployeeIds
estiver em uma biblioteca de classes, onde o código-fonte não está prontamente disponível.Supõe-se que
WhatToInclude.Trainees
funcionará seWhatToInclude.All
funcionar, visto que os trainees são um "subconjunto" de todos.Isso depende, é claro, de como
ReturnEmployeeIds
é implementado.Nos casos em que um argumento booleano pode ser passado, tento dividi-lo em dois métodos (ou mais, se necessário), no seu caso; pode-se extrair
ReturnAllEmployeeIds
,ReturnManagementIds
eReturnRegularEmployeeIds
. Isso cobre todas as bases e é absolutamente auto-explicativo para quem a implementa. Isso também não terá o item "scaling", mencionado acima.Como existem apenas dois resultados para algo que possui um argumento booleano. A implementação de dois métodos exige muito pouco esforço extra.
Menos código, raramente é melhor.
Com isto dito, não são alguns casos onde explicitamente declarar o argumento melhora a legibilidade. Considere, por exemplo.
GetLatestNews(max: 10)
. aindaGetLatestNews(10)
é bastante auto-explicativo, a declaração explícita de ajudará a esclarecer qualquer confusão.max
E se você absolutamente deve ter um argumento booleano, qual uso não pode ser inferido apenas lendo
true
oufalse
. Então eu diria que:.. é absolutamente melhor e mais legível que:
Como no segundo exemplo, isso
true
pode significar absolutamente qualquer coisa . Mas eu tentaria evitar esse argumento.fonte