Considérez la méthode suivante:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
}
Et l'appel suivant:
var ids = ReturnEmployeeIds(true);
Pour un développeur nouveau dans le système, il serait assez difficile de deviner ce qui true
s'est passé. La première chose que vous feriez serait de survoler le nom de la méthode ou d'aller à la définition (qui ne sont en aucun cas de grosses tâches). Mais, par souci de lisibilité, est-il logique d'écrire:
var ids = ReturnEmployeeIds(includeManagement: true);
Y a-t-il quelque part qui, officiellement, discute de la spécification explicite ou non des paramètres facultatifs lorsque le compilateur n'en a pas besoin?
L'article suivant décrit certaines conventions de codage: https://msdn.microsoft.com/en-gb/library/ff926074.aspx
Quelque chose de similaire à l'article ci-dessus serait formidable.
c#
coding-style
readability
parameters
JᴀʏMᴇᴇ
la source
la source
boolean
arguments de méthode, et comment?Réponses:
Je dirais que dans le monde C #, une énumération serait l'une des meilleures options ici.
Avec cela, vous seriez obligé de préciser ce que vous faites et tout utilisateur verrait ce qui se passe. Il est également sûr pour les futures extensions;
Donc, à mon avis ici:
énum> option énum> option bool
Edit: En raison de la discussion avec LeopardSkinPillBoxHat ci-dessous concernant une
[Flags]
énumération qui dans ce cas spécifique pourrait être un bon ajustement (car nous parlons spécifiquement d'inclure / exclure des choses), je propose plutôt d'utiliserISet<WhatToInclude>
comme paramètre.C'est un concept plus "moderne" avec plusieurs avantages, provenant principalement du fait qu'il s'intègre dans la famille de collections LINQ, mais aussi qui
[Flags]
comprend un maximum de 32 groupes. Le principal inconvénient de l'utilisationISet<WhatToInclude>
est la mauvaise prise en charge des ensembles dans la syntaxe C #:Certains d'entre eux peuvent être atténués par des fonctions d'assistance, à la fois pour générer l'ensemble et pour créer des "ensembles de raccourcis" tels que
la source
enum
approche, mais je ne suis pas un grand fan de mixageInclude
etExclude
de mêmeenum
qu'il est plus difficile de comprendre ce qui se passe. Si vous voulez que cela soit vraiment extensible, vous pouvez en faire un[Flags]
enum
, les faire tousIncludeXxx
et fournir unIncludeAll
qui est défini surInt32.MaxValue
. Ensuite, vous pouvez fournir 2ReturnEmployeeIds
surcharges - une qui renvoie tous les employés et une qui prend unWhatToInclude
paramètre de filtre qui peut être une combinaison d'indicateurs enum.[Flags]
c'est une relique et ne devrait être utilisé que pour des raisons d'héritage ou de performance. Je pense queISet<WhatToInclude>
c'est un concept bien meilleur (malheureusement, C # manque de sucre syntaxique pour le rendre agréable à utiliser).Flags
approche car elle permet à l'appelant de passerWhatToInclude.Managers | WhatToInclude.Cleaners
et au niveau du bit ou exprime exactement comment l'appelant la traitera (c'est-à-dire les gestionnaires ou les nettoyeurs). Sucre syntaxique intuitif :-)Il est discutable s'il s'agit d'un "bon style", mais à mon humble avis, ce n'est pas du moins un mauvais style, et utile, tant que la ligne de code ne devient pas "trop longue". Sans paramètres nommés, c'est également un style courant d'introduire une variable explicative:
Ce style est probablement plus courant chez les programmeurs C # que la variante de paramètre nommé, car les paramètres nommés ne faisaient pas partie du langage avant la version 4.0. L'effet sur la lisibilité est presque le même, il a juste besoin d'une ligne de code supplémentaire.
Donc, ma recommandation: si vous pensez que l'utilisation d'une énumération ou de deux fonctions avec des noms différents est "excessive", ou si vous ne voulez pas ou ne pouvez pas changer la signature pour une raison différente, allez-y et utilisez le paramètre nommé.
la source
includeManagement
tant que localconst
et éviter d'utiliser du tout de mémoire supplémentaire.Si vous rencontrez du code comme:
vous pouvez à peu près garantir que ce qui se trouve à l'intérieur
{}
sera quelque chose comme:En d'autres termes, le booléen permet de choisir entre deux modes d'action: la méthode a deux responsabilités. C'est à peu près garanti une odeur de code . Nous devons toujours nous efforcer de garantir que les méthodes ne font qu'une seule chose; ils n'ont qu'une seule responsabilité. Par conséquent, je dirais que vos deux solutions sont la mauvaise approche. L'utilisation de paramètres facultatifs est un signe que la méthode fait plus d'une chose. Les paramètres booléens en sont également le signe. Les deux devraient définitivement sonner l'alarme. Par conséquent, vous devez refactoriser les deux ensembles de fonctionnalités différents en deux méthodes distinctes. Donnez aux méthodes des noms qui indiquent clairement ce qu'elles font, par exemple:
Cela améliore à la fois la lisibilité du code et garantit que vous suivez mieux les principes SOLID.
EDIT Comme cela a été souligné dans les commentaires, le cas ici que ces deux méthodes auront probablement des fonctionnalités partagées, et que les fonctionnalités partagées seront probablement mieux enveloppées dans une fonction privée qui aura besoin d'un paramètre booléen pour contrôler certains aspects de ce qu'elle fait .
Dans ce cas, le nom du paramètre doit-il être fourni, même si le compilateur n'en a pas besoin? Je dirais qu'il n'y a pas de réponse simple à cela et qu'un jugement est nécessaire au cas par cas:
Le fichier source et les méthodes sont-ils petits et faciles à comprendre? Si oui, le paramètre nommé équivaut probablement à du bruit. Sinon, cela pourrait être très utile. Appliquez donc la règle selon les circonstances.
la source
public List<Guid> ReturnEmployeeIds(bool includeManagement) { calculateSomethingHereForBothBranches; if (includeManagement) return something else return something else }
, donc le fractionnement que simplement en deux méthodes signifiera probablement que ces deux méthodes auront besoin du résultat du premier calcul comme paramètre.Oui c'est vrai. Le code d'auto-documentation est une belle chose. Comme d'autres réponses l'ont souligné, il existe des moyens de lever l'ambiguïté de l'appel à
ReturnEmployeeIds
en utilisant une énumération, des noms de méthode différents, etc. Cependant, parfois, vous ne pouvez pas vraiment éviter le cas, mais vous ne voulez pas vous en aller et utiliser les partout (sauf si vous aimez la verbosité de Visual Basic).Par exemple, cela peut aider à clarifier un appel mais pas nécessairement un autre.
Un argument nommé peut être utile ici:
N'ajoute pas beaucoup de clarté supplémentaire (je suppose que cela analyse une énumération):
Cela peut en fait réduire la clarté (si une personne ne comprend pas la capacité d'une liste):
Ou là où cela n'a pas d'importance parce que vous utilisez de bons noms de variables:
Pas AFAIK. Abordons les deux parties cependant: la seule fois où vous devez utiliser explicitement les paramètres nommés est parce que le compilateur vous oblige à le faire (généralement c'est pour supprimer l'ambiguïté des instructions). Donc, à part ça, vous pouvez les utiliser quand vous le souhaitez. Cet article de MS contient des suggestions sur le moment où vous pouvez les utiliser, mais ce n'est pas particulièrement définitif https://msdn.microsoft.com/en-us/library/dd264739.aspx .
Personnellement, je trouve que je les utilise le plus souvent lorsque je crée des attributs personnalisés ou que je stoppe une nouvelle méthode où mes paramètres peuvent changer ou être réorganisés (les attributs nommés b / c peuvent être répertoriés dans n'importe quel ordre). De plus, je ne les utilise que lorsque je fournis une valeur statique en ligne, sinon si je passe une variable, j'essaie d'utiliser de bons noms de variables.
En fin de compte, cela se résume à la préférence personnelle. Bien sûr, il existe quelques cas d'utilisation lorsque vous devez utiliser des paramètres nommés, mais après cela, vous devez effectuer un appel de jugement. IMO - Utilisez-le partout où vous pensez qu'il vous aidera à documenter votre code, à réduire l'ambiguïté ou à vous permettre de protéger les signatures de méthode.
la source
Si le paramètre est évident d'après le nom (complet) de la méthode et que son existence est naturelle à ce que la méthode est censée faire, vous ne devez pas utiliser la syntaxe de paramètre nommée. Par exemple, dans
ReturnEmployeeName(57)
c'est assez sacrément évident que57
c'est l'ID de l'employé, il est donc redondant de l'annoter avec la syntaxe de paramètre nommée.Avec
ReturnEmployeeIds(true)
, comme vous l' avez dit, il est impossible à moins que vous regardez la déclaration / documentation de la fonction à la figure ce que destrue
moyens - de sorte que vous devez utiliser la syntaxe de paramètre nommé.Maintenant, considérons ce troisième cas:
La syntaxe du paramètre nommé n'est pas utilisée ici, mais puisque nous transmettons une variable à
ReturnEmployeeIds
, et que cette variable a un nom, et que ce nom signifie la même chose que le paramètre auquel nous le transmettons (c'est souvent le cas - mais pas toujours!) - nous n'avons pas besoin de la syntaxe de paramètre nommée pour comprendre sa signification à partir du code.Donc, la règle est simple - si vous pouvez facilement comprendre à partir de l' appel de méthode ce que le paramètre est censé signifier, n'utilisez pas le paramètre nommé. Si vous ne pouvez pas - c'est une lacune dans la lisibilité du code, et vous voulez probablement les corriger (pas à n'importe quel prix, bien sûr, mais vous voulez généralement que le code soit lisible). Le correctif n'a pas besoin d'être nommé paramètres - vous pouvez également mettre la valeur dans une variable en premier, ou utiliser les méthodes suggérées dans les autres réponses - tant que le résultat final permet de comprendre facilement ce que fait le paramètre.
la source
ReturnEmployeeName(57)
rend immédiatement évident qu'il57
s'agit de la pièce d'identité.GetEmployeeById(57)
d'autre part ...ReturnEmployeeName
pour montrer des cas où même sans mentionner explicitement le paramètre dans le nom de la méthode, il est assez raisonnable d'attendre des gens qu'ils le comprennent. Quoi qu'il en soit, il est à noter que contrairement àReturnEmployeeName
, vous ne pouvez pas raisonnablement renommerReturnEmployeeIds
quelque chose qui indique la signification des paramètres.Oui, je pense que c'est du bon style.
Cela a plus de sens pour les constantes booléennes et entières.
Si vous passez une variable, le nom de la variable devrait clarifier la signification. Si ce n'est pas le cas, vous devriez donner à la variable un meilleur nom.
Si vous passez une chaîne, la signification est souvent claire. Comme si je vois un appel à GetFooData ("Oregon"), je pense qu'un lecteur peut à peu près deviner que le paramètre est un nom d'état.
Bien sûr, toutes les chaînes ne sont pas évidentes. Si je vois GetFooData ("M"), sauf si je connais la fonction, je n'ai aucune idée de ce que signifie "M". S'il s'agit d'une sorte de code, comme M = "employés de niveau de gestion", nous devrions probablement avoir une énumération plutôt qu'un littéral, et le nom de l'énumération doit être clair.
Et je suppose qu'une chaîne pourrait être trompeuse. Peut-être que "Oregon" dans mon exemple ci-dessus fait référence, non pas à l'État, mais à un produit ou à un client ou autre.
Mais GetFooData (true) ou GetFooData (42) ... cela pourrait signifier n'importe quoi. Les paramètres nommés sont une merveilleuse façon de le rendre auto-documenté. Je les ai utilisés exactement à cette fin à plusieurs reprises.
la source
Il n'y a pas de raisons très claires pour lesquelles utiliser ce type de syntaxe en premier lieu.
En général, essayez d'éviter d'avoir des arguments booléens qui à distance peuvent sembler arbitraires.
includeManagement
affectera (très probablement) le résultat. Mais l'argument semble avoir «peu de poids».L'utilisation d'une énumération a été discutée, non seulement elle ressemblera à l'argument "porte plus de poids", mais elle fournira également une évolutivité à la méthode. Cela pourrait cependant ne pas être la meilleure solution dans tous les cas, car votre
ReturnEmployeeIds
méthode devra évoluer parallèlement auWhatToInclude
-enum (voir la réponse de NiklasJ ). Cela pourrait vous donner mal à la tête plus tard.Considérez ceci: si vous
WhatToInclude
modifiez l'échelle -enum, mais pas laReturnEmployeeIds
méthode -méthode. Il pourrait alors jeter unArgumentOutOfRangeException
(meilleur cas) ou retourner quelque chose de complètement indésirable (null
ou un videList<Guid>
). Ce qui dans certains cas peut dérouter le programmeur, surtout si vousReturnEmployeeIds
êtes dans une bibliothèque de classes, où le code source n'est pas facilement disponible.On supposera que
WhatToInclude.Trainees
cela fonctionnera siWhatToInclude.All
cela fonctionne, étant donné que les stagiaires sont un "sous-ensemble" de tous.Cela dépend (bien sûr) de la façon dont il
ReturnEmployeeIds
est mis en œuvre.Dans les cas où un argument booléen pourrait être transmis, j'essaie plutôt de le diviser en deux méthodes (ou plus, si nécessaire), dans votre cas; on pourrait extraire
ReturnAllEmployeeIds
,ReturnManagementIds
etReturnRegularEmployeeIds
. Cela couvre toutes les bases et est absolument explicite à quiconque le met en œuvre. Cela n'aura pas non plus le problème de "mise à l'échelle" mentionné ci-dessus.Puisqu'il n'y a que deux résultats pour quelque chose qui a un argument booléen. La mise en œuvre de deux méthodes nécessite très peu d'efforts supplémentaires.
Moins de code, c'est rarement mieux.
Cela dit, il existe certains cas où la déclaration explicite de l'argument améliore la lisibilité. Considérez par exemple.
GetLatestNews(max: 10)
.GetLatestNews(10)
est encore assez explicite, la déclaration explicite demax
contribuera à dissiper toute confusion.Et si vous devez absolument avoir un argument booléen, quel usage ne peut pas être déduit en lisant simplement
true
oufalse
. Alors je dirais que:.. est absolument meilleur et plus lisible que:
Puisque dans le deuxième exemple, le
true
pourrait signifier absolument n'importe quoi . Mais j'essaierais d'éviter cet argument.la source