Est-il suffisant que les méthodes soient distinguées uniquement par le nom de l'argument (pas le type)?

36

Suffit-il que les méthodes se distinguent uniquement par le nom de l'argument (pas le type) ou vaut-il mieux le nommer plus explicitement?

Par exemple T Find<T>(int id)vs T FindById<T>(int id).

Y a-t-il une bonne raison de le nommer plus explicitement (c'est-à-dire d'ajouter ById) ou de ne garder que le nom de l'argument?

Une des raisons auxquelles je peux penser est lorsque les signatures des méthodes sont identiques mais que leur signification est différente.

FindByFirstName(string name) et FindByLastName(string name)

Konrad
la source
4
Donc, lorsque vous surchargez Trouvez à inclure T Find<T>(string name)ou (int size)comment envisagez-vous de résoudre les problèmes inévitables?
UKMonkey
3
@UKMonkey quels problèmes inévitables?
Konrad
3
dans le premier cas: si plusieurs entrées ont le même nom, vous devrez changer la signature de la fonction; ce qui signifie que les gens vont probablement être confus avec ce qui est censé revenir. Dans ce dernier cas, l'argument est le même - et donc une surcharge illégale. Soit vous commencez à nommer la fonction avec "byX" ou créez un objet pour l’argument afin que vous puissiez avoir l’équivalent de la surcharge avec le même argument. Soit fonctionne bien pour différentes situations.
UKMonkey
2
@UKMonkey vous pouvez poster une réponse avec quelques exemples de code si vous le souhaitez
Konrad
3
Les identifiants devraient probablement être un IDobjet opaque et pas simplement un int. De cette manière, vérifiez à la compilation que vous n'utilisez pas d'identifiant pour un int ou vice-versa dans une partie de votre code. Et avec ça vous pouvez avoir find(int value)et find(ID id).
Bakuriu

Réponses:

68

Bien sûr, il y a une bonne raison de le nommer plus explicitement.

Ce n'est pas principalement la définition de la méthode qui devrait s'expliquer d'elle-même, mais l' utilisation de la méthode . Et tandis findById(string id)que find(string id)les deux se passent d'explication, il y a une énorme différence entre findById("BOB")et find("BOB"). Dans le premier cas, vous savez que le littéral aléatoire est en fait un identifiant. Dans ce dernier cas, vous n'êtes pas sûr - cela pourrait en fait être un prénom ou autre chose.

Kilian Foth
la source
9
Sauf dans les 99% d'autres cas où vous avez un nom de variable ou une propriété est un point de référence: findById(id)vs find(id). Vous pouvez aller dans un sens ou dans l'autre.
Greg Burghardt
16
@ GregBurghardt: Une valeur particulière n'est pas nécessairement nommée de la même manière pour une méthode et son appelant. Par exemple, considérons double Divide(int numerator, int denominator)être utilisé dans une méthode: double murdersPerCapita = Divide(murderCount, citizenCount). Vous ne pouvez pas compter sur deux méthodes utilisant le même nom de variable car il existe de nombreux cas dans lesquels ce n'est pas le cas (ou lorsque c'est le cas, c'est une mauvaise désignation)
Flater
1
@Flater: Etant donné le code dans la question (recherche d'éléments dans une sorte de stockage persistant), j'imagine que vous pourriez appeler cette méthode avec un "murderedCitizenId" ou un "citizenId" ... Je ne pense vraiment pas que l'argument ou les noms de variables soient ambigu ici. Et honnêtement, je pourrais aller dans les deux sens. C'est en fait une question très opinionée.
Greg Burghardt
4
@ GregBurghardt: Vous ne pouvez pas distiller une règle globale à partir d'un seul exemple. La question d'OP est en général non spécifique à l'exemple donné. Oui, il y a des cas où utiliser le même nom est logique, mais il y a aussi des cas où ce n'est pas le cas. D'où cette réponse, il est préférable de rechercher la cohérence, même si cela n'est pas nécessaire dans un sous-ensemble de cas d'utilisation.
Flater
1
Les paramètres de noms résolvent la confusion après que la confusion a déjà existé , car vous devez examiner la définition de méthode, alors que les méthodes explicitement nommées évitent totalement la confusion en ayant le nom présent dans l'appel de méthode. De plus, vous ne pouvez pas avoir deux méthodes avec le même nom et le même type d'argument, mais un nom d'argument différent, ce qui signifie que vous aurez de toute façon besoin de noms explicites.
Darkhogg
36

Avantages de FindById () .

  1. Avenir épreuvage : Si vous commencez avec Find(int), et ont plus tard d'ajouter d' autres méthodes ( FindByName(string), FindByLegacyId(int), FindByCustomerId(int), FindByOrderId(int), etc.), les gens comme moi ont tendance à passer des heures à la recherche pour FindById(int). Pas vraiment un problème si vous pouvez et va changer Find(int)pour FindById(int)une fois qu'il devient nécessaire - épreuvage avenir est au sujet de ces cas s.

  2. Plus facile à lire . Findest parfaitement bien si l'appel ressemble à record = Find(customerId);Yet FindByIdest légèrement plus facile à lire si c'est le cas record = FindById(AFunction());.

  3. La cohérence . Vous pouvez systématiquement appliquer le motif FindByX(int)/ FindByY(int)partout, mais Find(int X)/ Find(int Y)n'est pas possible car ils sont en conflit.

Avantages de la recherche ()

  • BAISER. Findest simple et direct, et operator[]c’est l’un des 2 noms de fonction les plus attendus dans ce contexte. (Certaines alternatives populaires étant get, lookupou fetch, selon le contexte).
  • En règle générale, si vous avez un nom de fonction qui est un seul mot bien connu qui décrit précisément ce que fait la fonction, utilisez-le. Même s'il existe un nom de plusieurs mots plus long, il est légèrement meilleur pour décrire le rôle de la fonction. Exemple: Length vs NumberOfElements . Il y a un compromis, et où tracer la ligne fait l'objet d'un débat en cours.
  • Il est généralement bon d'éviter les redondances. Si nous regardons FindById(int id), nous pouvons facilement supprimer la redondance en la changeant en Find(int id), mais il y a un compromis - nous perdons un peu de clarté.

Sinon, vous pouvez obtenir les avantages des deux en utilisant des identifiants fortement typés:

CustomerRecord Find(Id<Customer> id) 
// Or, depending on local coding standards
CustomerRecord Find(CustomerId id) 

Implémentation de Id<>: Taper fortement les valeurs d'ID en C #

Les commentaires ici, ainsi que dans le lien ci-dessus, ont soulevé de nombreuses préoccupations à propos Id<Customer>desquelles j'aimerais aborder:

  • Préoccupation n ° 1: c'est un abus de génériques. CustomerIdet OrderIDsont de types différents ( customerId1 = customerId2;=> bon,customerId1 = orderId1; => mauvais), mais leur implémentation est presque identique, nous pouvons donc les implémenter avec copier-coller ou avec métaprogrammation. Bien qu'il soit utile dans une discussion de révéler ou de cacher le générique, la métaprogrammation est ce à quoi servent les génériques.
  • Problème 2: Cela n'arrête pas les erreurs simples. / C'est une solution à la recherche d'un problème Le problème principal qui est résolu par l'utilisation d'identifiants fortement typés est le mauvais ordre des arguments dans un appel à DoSomething(int customerId, int orderId, int productId). Les identifiants fortement typés préviennent également d’autres problèmes, y compris celui sur lequel l’OP a été interrogé.
  • Problème 3: Il ne fait que masquer le code. Il est difficile de dire si un identifiant est retenu int aVariable. Il est facile de savoir qu'un identifiant est retenu Id<Customer> aVariableet nous pouvons même dire qu'il s'agit d'un identifiant client.
  • Problème 4: ces identifiants ne sont pas des types forts, mais des wrappers. Stringest juste une enveloppe autour byte[]. L'emballage ou l'encapsulation n'est pas en conflit avec le typage fort.
  • Préoccupation n ° 5: c'est trop technique. Voici la version minimale, bien que je recommande d’ajouter operator==et operator!=aussi, si vous ne voulez pas compter exclusivement sur Equals:

.

public struct Id<T>: {
    private readonly int _value ;
    public Id(int value) { _value = value; }
    public static explicit operator int(Id<T> id) { return id._value; }
}
Peter
la source
10

Une autre façon de penser à cela consiste à utiliser le type safety de la langue.

Vous pouvez implémenter une méthode telle que:

Find(FirstName name);

Où FirstName est un objet simple qui enveloppe une chaîne qui contient le prénom et signifie qu'il ne peut y avoir aucune confusion quant à ce que fait la méthode, ni dans les arguments avec lesquels elle est appelée.

3DPrintScanner
la source
4
Vous ne savez pas quelle est votre réponse à la question des PO. Recommandez-vous d'utiliser un nom comme "Rechercher" en vous basant sur le type de l'argument? Ou recommandez-vous d'utiliser de tels noms uniquement lorsqu'il existe un type explicite pour le ou les arguments et d'utiliser un nom plus explicite que "FindById" ailleurs? Ou recommandez-vous d'introduire des types explicites pour rendre un nom tel que "Rechercher" plus réalisable?
Doc Brown
2
@DocBrown Je pense à ce dernier et je l'aime bien. C'est en fait similaire à la réponse de Peter, iiuc. Si je comprends bien, le raisonnement est double: (1) le type d’argument indique clairement ce que fait la fonction; (2) Vous ne pouvez pas faire d'erreurs du type. Ce string name = "Smith"; findById(name);qui est possible si vous utilisez des types généraux non descriptifs.
Peter - Réintégrez Monica
5
Bien qu'en général, je suis un partisan de la sécurité des types de compilation, soyez prudent avec les types d'emballage. L'introduction de classes wrapper dans un souci de sécurité du type peut parfois compliquer gravement votre API si elle est effectuée de manière excessive. par exemple, tout le problème de "l'artiste anciennement appelé int" que Winapi a; à long terme, je dirais que la plupart des gens se contentent de regarder les interminables DWORD LPCSTRclones, etc. et pensent que "c'est un int / string / etc.", il en arrive au point où vous passez plus de temps à entretenir vos outils qu'à concevoir du code. .
Jrh
1
@jrh True. Mon test décisif pour introduire des types "nominaux" (ne différant que par leur nom) est que les fonctions / méthodes communes n'ont aucun sens dans notre cas d'utilisation, par exemple, les ints sont souvent sommés, multipliés, etc., ce qui n'a pas de sens pour les ID; donc je ferais IDdistinct de int. Cela peut simplifier une API, en limitant ce que nous pouvons faire avec une valeur (par exemple, si nous en avons un ID, cela ne fonctionnera qu'avec find, pas avec ageou par exemple highscore). Inversement, si nous convertissons beaucoup, ou si nous écrivons la même fonction / méthode (par exemple find) pour plusieurs types, cela
indique
1

Je voterai pour une déclaration explicite comme FindByID .... Le logiciel devrait être construit pour le changement. Il devrait être ouvert et fermé (SOLID). La classe est donc ouverte pour ajouter une méthode de recherche similaire, comme par exemple FindByName, etc.

Mais FindByID est fermé et son implémentation est testée.

Je ne proposerai pas de méthodes avec prédicats, celles-ci sont bonnes au niveau générique. Et si basé sur le champ (ByID), vous avez une méthodologie complète différente.

BrightFuture1029
la source
0

Je suis surpris que personne n'ait suggéré d'utiliser un prédicat tel que celui-ci:

User Find(Predicate<User> predicate)

Avec cette approche, vous réduisez non seulement la surface de votre API, mais vous donnez également plus de contrôle à l'utilisateur qui l'utilise.

Si cela ne suffit pas, vous pouvez toujours l'étendre à vos besoins.

Aybe
la source
1
Le problème est qu’elle est moins efficace, car elle ne peut pas tirer parti d’objets tels que les indices.
Solomon Ucko