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 sur le système, il serait assez difficile de deviner ce que true
a fait. 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.
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;
public enum WhatToInclude
{
IncludeAll,
ExcludeManagement
}
var ids = ReturnEmployeeIds(WhatToInclude.ExcludeManagement);
Donc, à mon avis ici:
énum> option énum> option bool
Edit: En raison de la discussion avec LeopardSkinPillBoxHat ci-dessous concernant un [Flags]
enum qui dans ce cas spécifique pourrait être un bon ajustement (puisque nous parlons spécifiquement d'inclure/exclure des choses), je propose plutôt d'utiliser ISet<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 que [Flags]
a un maximum de 32 groupes. Le principal inconvénient de l'utilisation de ISet<WhatToInclude>
est la façon dont les ensembles sont mal pris en charge dans la syntaxe C #:
var ids = ReturnEmployeeIds(
new HashSet<WhatToInclude>(new []{
WhatToInclude.Cleaners,
WhatToInclude.Managers});
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
var ids = ReturnEmployeeIds(WhatToIncludeHelper.IncludeAll)
est-il logique d'écrire:
var ids=ReturnEmployeeIds(includeManagement: true);
Il est discutable s'il s'agit d'un "bon style", mais à mon humble avis, il s'agit du moins d'un mauvais style, et utile, tant que la ligne de code ne devient pas "trop longue". Sans paramètres nommés, il est également courant d'introduire une variable explicative:
bool includeManagement=true;
var ids=ReturnEmployeeIds(includeManagement);
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 "exagérée", 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é.
Si vous rencontrez du code comme:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
}
vous pouvez à peu près garantir ce qu'il y a à l'intérieur du {}
sera quelque chose comme:
public List<Guid> ReturnEmployeeIds(bool includeManagement = false)
{
if (includeManagement)
return something
else
return something else
}
En d'autres termes, le booléen permet de choisir entre deux pistes: la méthode a deux responsabilités. C'est à peu près garanti une odeur de code . Nous devons toujours nous efforcer de nous assurer 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:
public List<Guid> GetNonManagementEmployeeIds()
{
}
public List<Guid> GetAllEmployeeIds()
{
}
Cela améliore à la fois la lisibilité du code et garantit que vous suivez mieux les principes SOLID.
[~ # ~] modifier [~ # ~] Comme cela a été souligné dans les commentaires, le cas ici que ces deux méthodes auront probablement des fonctionnalités partagées , et cette fonctionnalité partagée sera probablement mieux enveloppée 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:
public List<Guid> GetNonManagementEmployeeIds()
{
return ReturnEmployeeIds(true);
}
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.
Pour un développeur nouveau dans le système, il serait assez difficile de deviner ce qui 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)
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 le faites pas veulent partir et les utiliser 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:
var ids = ReturnEmployeeIds(includeManagement: true);
N'ajoute pas beaucoup de clarté supplémentaire (je vais supposer que cela analyse une énumération):
Enum.Parse(typeof(StringComparison), "Ordinal", ignoreCase: true);
Cela peut en fait réduire la clarté (si une personne ne comprend pas la capacité d'une liste):
var employeeIds = new List<int>(capacity: 24);
Ou là où cela n'a pas d'importance parce que vous utilisez de bons noms de variables:
bool includeManagement = true;
var ids = ReturnEmployeeIds(includeManagement);
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?
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.
TL; DR
En fin de compte, cela se résume à des préférences personnelles. 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.
Si le paramètre est évident à partir du nom (complet) de la méthode et que son existence est naturelle à ce que la méthode est censée faire, vous devez pas utiliser la syntaxe de paramètre nommée. Par exemple, dans ReturnEmployeeName(57)
, il est assez évident que 57
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, à moins que vous ne regardiez la déclaration/documentation de la fonction, il est impossible de comprendre ce que true
signifie - donc vous devriez utiliser la syntaxe de paramètre nommée .
Maintenant, considérons ce troisième cas:
void PrintEmployeeNames(bool includeManagement) {
foreach (id; ReturnEmployeeIds(includeManagement)) {
Console.WriteLine(ReturnEmployeeName(id));
}
}
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 (ce 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 juste 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.
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 voyais 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.
Il n'y a aucune raison très claire pour laquelle 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) considérablement 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 méthode ReturnEmployeeIds
- devra évoluer à côté de l'énumération WhatToInclude
voir --- (NiklasJ 's answer) . Cela pourrait vous donner mal à la tête plus tard.
Considérez ceci: Si vous mettez à l'échelle l'énumération WhatToInclude
-, mais pas la méthode ReturnEmployeeIds
-. Il pourrait alors lancer un ArgumentOutOfRangeException
(dans le meilleur des cas) ou retourner quelque chose de complètement indésirable (null
ou un List<Guid>
Vide). Ce qui dans certains cas pourrait dérouter le programmeur, surtout si votre ReturnEmployeeIds
se trouve dans une bibliothèque de classes, où le code source n'est pas facilement disponible.
On supposera que WhatToInclude.Trainees
Fonctionnera si WhatToInclude.All
Fonctionne, vu que les stagiaires sont un "sous-ensemble" de tous.
Cela dépend (bien sûr) de la façon dont ReturnEmployeeIds
est implémenté.
Dans les cas où un argument booléen pourrait être transmis, j'essaie de le diviser en deux méthodes (ou plus, si nécessaire), dans votre cas; on pourrait extraire ReturnAllEmployeeIds
, ReturnManagementIds
et ReturnRegularEmployeeIds
. 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 jamais 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 toujours assez explicite, la déclaration explicite de max
aidera à clarifier toute confusion .
Et si vous devez absolument avoir un argument booléen, dont l'utilisation ne peut pas être déduite en lisant simplement true
ou false
. Alors je dirais que:
var ids = ReturnEmployeeIds(includeManagement: true);
.. est absolument meilleur et plus lisible que:
var ids = ReturnEmployeeIds(true);
Puisque dans le deuxième exemple, le true
peut signifier absolument n'importe quoi . Mais j'essaierais d'éviter cet argument.