Problèmes de dénomination: Faut-il renommer "Quelque chose"? Quelque chose? [fermé]

68

Le chapitre d'Oncle Bob sur les noms dans Clean Code vous recommande d'éviter les encodages dans les noms, principalement en ce qui concerne la notation hongroise. Il mentionne aussi spécifiquement la suppression du Ipréfixe des interfaces, mais n'en montre pas d'exemples.

Supposons ce qui suit:

  • L'utilisation de l'interface est principalement destinée à permettre la testabilité via l'injection de dépendance
  • Dans de nombreux cas, cela conduit à avoir une seule interface avec un seul implémenteur

Ainsi, par exemple, comment nommer ces deux personnes? Parseret ConcreteParser? Parseret ParserImplementation?

public interface IParser {
    string Parse(string content);
    string Parse(FileInfo path);
}

public class Parser : IParser {
     // Implementations
}

Ou devrais-je ignorer cette suggestion dans les cas de mise en œuvre unique comme celui-ci?

Vinko Vrsalovic
la source
30
Cela n'en fait pas moins une religion. Tu dois chercher des raisons, pas seulement quelqu'un X dit Y.
Mike Dunlavey
35
@ MikeDunlavey Et c'est la raison de la question!
Vinko Vrsalovic
14
S'il existe un corps de code existant (que vous n'allez pas réécrire), respectez la convention utilisée. Si vous avez un nouveau code, utilisez ce que la majorité des gens qui vont y travailler sont plus heureux / habitués Ce n'est que si ni ce qui précède ne s'applique que cela deviendra une question philosophique.
TripeHound
9
La règle de majorité @TripeHound n'est pas toujours la meilleure option. S'il y a des raisons pour lesquelles certaines choses sont meilleures que d'autres, une seule personne peut convaincre le reste de l'équipe de défendre ses arguments. Se soumettre aveuglément à la majorité sans évaluer les choses à travers conduit à la stagnation. Je vois dans les réponses que dans ce cas concret, il n’ya aucune bonne raison de retirer l’Is, mais cela n’annule pas avoir demandé au départ.
Vinko Vrsalovic
35
Le livre de "Uncle Bob" se concentre sur JAVA, pas sur C #. La plupart des paradigmes sont les mêmes avec C #, mais certaines conventions de dénomination diffèrent (regardez également les noms des fonctions lowerCamelCase en Java). Lorsque vous écrivez en C #, utilisez les conventions de nommage C #. Quoi qu’il en soit, suivez l’essentiel de son chapitre sur la dénomination: des noms lisibles qui communiquent le sens de l’article!
Bernhard Hiller

Réponses:

190

Bien que beaucoup, y compris "Uncle Bob", conseillent de ne pas utiliser Icomme préfixe pour les interfaces, cela est une tradition bien établie avec C #. En termes généraux, cela devrait être évité. Mais si vous écrivez en C #, vous devriez vraiment suivre les conventions de ce langage et l'utiliser. Ne pas le faire provoquera une énorme confusion avec les personnes connaissant C # et essayant de lire votre code.

David Arno
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
9
Vous ne fournissez aucune base pour "En termes généraux, cela devrait être évité". En Java, je devrais être évité. En C #, il devrait être adopté. Les autres langues suivent leurs propres conventions.
Basic
4
Le principe "en termes généraux" est simple: un client devrait avoir le droit de ne pas savoir s'il parle à une interface ou à une implémentation. Les deux langues se sont trompées. C # s'est trompé dans la convention de nommage. Java s'est trompé dans le bytecode. Si je bascule une implémentation sur une interface portant le même nom en Java, je dois recompiler tous les clients, même si le nom et les méthodes n'ont pas changé. Ce n'est pas un "choisissez votre poison". Nous venons de le faire mal. S'il vous plaît apprendre de cela si vous créez une nouvelle langue.
candied_orange
39

L'interface est le concept logique important; l'interface doit donc porter le nom générique. Donc, je préfère avoir

interface Something
class DefaultSomething : Something
class MockSomething : Something

que

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Ce dernier a plusieurs aspects:

  1. Quelque chose n'est qu'une implémentation d'ISomething, mais c'est celle qui porte le nom générique, comme si c'était quelque chose de spécial.
  2. MockSomething semble dériver de Something, mais implémente ISomething. Peut-être faudrait-il l'appeler MockISomething, mais je ne l'ai jamais vu dans la nature.
  3. Le refactoring est plus difficile. Si Quelque chose est une classe maintenant et que vous ne découvrez que plus tard qu'il est nécessaire d'introduire une interface, cette interface doit porter le même nom car elle doit être transparente pour les clients, qu'il s'agisse d'une classe concrète, d'une classe abstraite ou d'une interface. . Quelque chose casse ça.
Wallenborn
la source
59
Pourquoi ne pas suivre la norme bien établie fournie par C #? Quel avantage cela confère-t-il au-delà du coût de confusion et d'irritation pour chaque programmeur C # qui vous suit?
Robert Harvey
6
@wallenborn C'est bien, mais de manière réaliste, vous n'avez souvent qu'une seule implémentation légitime d'une interface, car il est courant d'utiliser des interfaces pour la mocabilité dans les tests unitaires. Je ne veux pas mettre Defaultni Realou Impldans plusieurs de mes noms de classe
Ben Aaronson
4
Je suis d'accord avec vous, mais si vous empruntez cette route, vous devrez vous battre chaque linter jamais fait pour C #.
RubberDuck
3
Je ne vois aucun problème à avoir une classe instanciable avec le même nom qu'une interface si la grande majorité des instances qui implémentent l'interface seront cette classe. Par exemple, beaucoup de code nécessite une implémentation simple et générale, IList<T>sans se soucier de la manière dont il est stocké en interne; pouvoir lopper Iet avoir un nom de classe utilisable semble plus agréable que de devoir savoir par magie (comme en Java) que le code souhaitant une implémentation ordinaire List<T>devrait être utilisé ArrayList<T>.
Supercat
2
@ArtB Quelques raisons. Un, il n'y a pas de langue où la convention est un joli marqueur court sur la classe comme CFoo : Foo. Donc, cette option n'est pas vraiment disponible. Deux, vous obtenez une incohérence bizarre alors parce que certaines classes auraient le marqueur et d' autres ne seraient pas, selon qu'il ya peut - être d' autres implémentations de la même interface. CFoo : Foomais SqlStore : Store. C'est un problème que j'ai avec Impl, qui est essentiellement juste un marqueur plus laid. Peut-être que s'il y avait une langue avec la convention de toujours ajouter un C(ou autre chose), cela pourrait être mieux queI
Ben Aaronson
27

Cela ne concerne pas seulement les conventions de nommage. C # ne prenant pas en charge l'héritage multiple, cette utilisation héritée de la notation hongroise présente un petit avantage, quoique utile, lorsque vous héritez d'une classe de base et implémentez une ou plusieurs interfaces. Donc ça...

class Foo : BarB, IBarA
{
    // Better...
}

... est préférable à cela ...

class Foo : BarB, BarA
{
    // Dafuq?
}

OMI

Robbie Dee
la source
7
Il convient de noter que Java évite soigneusement ce problème en utilisant des mots - clés pour l' héritage de classe et la mise en œuvre de l' interface, à savoir inheritsvs implements.
Konrad Rudolph
6
@ KonradRudolph vous voulez dire extends.
Cessez de nuire à Monica
2
@Andy La valeur ajoutée est que nous pouvons éviter la notation hongroise, tout en préservant toute la clarté présentée dans cette réponse.
Konrad Rudolph
2
Il ne devrait vraiment y avoir aucune confusion; si vous héritez d'une classe, elle DOIT être en premier dans la liste, avant la liste des interfaces implémentées. Je suis à peu près sûr que cela est imposé par le compilateur.
Andy
1
@Andy ... si vous héritez d' une classe, il DOIT être le premier de la liste. C'est bien, mais sans le préfixe, vous ne sauriez pas si vous avez affaire à quelques interfaces ou à une classe de base et à une interface ...
Robbie Dee
15

Non non Non.

Les conventions de dénomination ne sont pas une fonction de votre fandom Oncle Bob. Ils ne sont pas fonction du langage, c # ou autre.

Ils sont une fonction de votre base de code. Dans une moindre mesure de votre magasin. En d'autres termes, le premier dans la base de code définit la norme.

C'est seulement à ce moment-là que vous décidez. Après cela, soyez juste cohérent. Si quelqu'un commence à être incohérent, retirez son arme pour nerf et faites-lui mettre à jour la documentation jusqu'à ce qu'il se repente.

Lorsque je lis une nouvelle base de code si je ne vois jamais de Ipréfixe, tout va bien, quelle que soit la langue. Si j'en vois un sur chaque interface, je vais bien. Si j'en vois parfois, quelqu'un va payer.

Si vous vous trouvez dans le contexte raréfié de l'établissement de ce précédent pour votre base de code, je vous prie instamment de considérer ceci:

En tant que client, je me réserve le droit de ne pas se soucier de quoi je parle. Tout ce dont j'ai besoin, c'est d'un nom et d'une liste de choses que je peux appeler contre ce nom. Ceux-ci ne peuvent pas changer à moins que je le dise. Je possède cette partie. Ce à quoi je parle, interface ou pas, n’est pas mon département.

Si vous insérez un Inom dans ce nom, le client s'en fiche. S'il n'y en a pas I, le client s'en fiche. Ce à quoi il parle peut être concret. Il se peut que non. Comme il ne fait pas appel newde toute façon, cela ne fait aucune différence. En tant que client, je ne sais pas. Je ne veux pas savoir.

Maintenant, en tant que singe de code regardant ce code, même en Java, cela pourrait être important. Après tout, si je dois remplacer une implémentation par une interface, je peux le faire sans en informer le client. S'il n'y a pas de Itradition, je ne pourrais même pas le renommer. Ce qui pourrait être un problème parce que maintenant nous devons recompiler le client car même si la source ne se soucie pas du binaire. Quelque chose de facile à manquer si vous n'avez jamais changé le nom.

Peut-être que ce n'est pas un problème pour vous. Peut être pas. Si c'est le cas, c'est une bonne raison de s'en soucier. Mais pour l'amour des jouets de bureau, ne prétendez pas que nous devrions le faire à l'aveuglette, car c'est la même chose dans certaines langues. Soit eu une sacrée bonne raison ou ne s'en soucie pas.

Il ne s'agit pas de vivre avec pour toujours. Il s’agit de ne pas me faire chier au sujet de la base de code qui ne correspond pas à votre mentalité particulière, à moins que vous ne soyez prêt à résoudre le problème partout. À moins que vous n'ayez une bonne raison pour que je ne prenne pas le chemin de la moindre résistance à l'uniformité, ne me parlez pas de prêcher la conformité. J'ai de meilleures choses à faire.

confits_orange
la source
6
La cohérence est essentielle, mais dans un langage typé de manière statique et avec des outils de refactoring modernes, il est assez facile et sûr de changer de nom. Donc, si le premier développeur a fait un mauvais choix en nommant, vous ne devez pas vivre avec pour toujours. Mais vous pouvez changer le nom dans votre propre base de code, mais vous ne pouvez pas le changer dans le framework ni dans les bibliothèques tierces. Puisque le préfixe I (qu’il le veuille ou non) soit une convention établie dans le monde .net, il est préférable de la suivre que de ne pas la suivre.
JacquesB
Si vous utilisez C #, vous verrez ces Is. Si votre code les utilise, vous les verrez partout. Si votre code ne les utilise pas, vous les verrez dans le code de la structure, ce qui conduit exactement au mélange que vous ne voulez pas.
Sebastian Redl
8

Il y a une toute petite différence entre Java et C # qui est pertinente ici. En Java, chaque membre est virtuel par défaut. En C #, chaque membre est scellé par défaut - à l'exception des membres d'interface.

Les principes qui accompagnent cette influence ont une influence sur la ligne directrice - en Java, tout type public doit être considéré comme non final, conformément au principe de substitution de Liskov [1]. Si vous n'avez qu'une seule implémentation, vous nommerez la classe Parser. Si vous constatez que vous avez besoin de plusieurs implémentations, il vous suffira de changer la classe pour une interface du même nom et de renommer l'implémentation concrète en quelque chose de descriptif.

En C #, l’hypothèse principale est que lorsque vous obtenez une classe (le nom ne commence pas par I), c’est la classe que vous voulez. Remarquez que cela n’est pas du tout fiable à 100% - un contre-exemple typique serait des classes telles que Stream(qui aurait vraiment dû être une interface, ou quelques interfaces), et chacun a ses propres directives et arrière-plans d’autres langues. Il existe également d'autres exceptions, comme le Basesuffixe assez largement utilisé pour désigner une classe abstraite - comme avec une interface, vous savez que le type est supposé être polymorphe.

Il existe également une fonctionnalité conviviale permettant de laisser le nom non préfixé par I pour les fonctionnalités liées à cette interface sans avoir à recourir à une classe abstraite (ce qui serait préjudiciable en raison du manque de classe à héritage multiple en C #). Cela a été popularisé par LINQ, qui utilise IEnumerable<T>comme interface et Enumerablecomme référentiel de méthodes s’appliquant à cette interface. Cela n’est pas nécessaire en Java, où les interfaces peuvent également contenir des implémentations de méthodes.

En fin de compte, le Ipréfixe est largement utilisé dans le monde C # et, par extension, dans le monde .NET (étant donné que la majeure partie du code .NET est écrit en C #, il est logique de suivre les directives C # pour la plupart des interfaces publiques). Cela signifie que vous travaillerez presque certainement avec des bibliothèques et du code respectant cette notation. Il est donc logique d'adopter la tradition pour éviter toute confusion inutile - ce n'est pas comme si le préfixe rendrait votre code meilleur :)

Je suppose que le raisonnement d'oncle Bob ressemblait à ceci:

IBananaest la notion abstraite de banane. S'il peut y avoir une classe d'implémentation qui n'aurait pas de meilleur nom que Banana, l'abstraction n'a aucun sens, et vous devriez abandonner l'interface et simplement utiliser une classe. S'il existe un meilleur nom (par exemple, LongBananaou AppleBanana), il n'y a aucune raison de ne pas utiliser Bananale nom de l'interface. Par conséquent, l’utilisation du Ipréfixe signifie que vous avez une abstraction inutile, ce qui rend le code plus difficile à comprendre sans aucun avantage. Et comme la POO stricte vous oblige toujours à coder contre des interfaces, le seul endroit où vous ne verriez pas le Ipréfixe d'un type serait un constructeur - un bruit inutile.

Si vous appliquez cela à votre exemple d' IParserinterface, vous pouvez clairement voir que l'abstraction est entièrement dans le territoire "sans signification". Soit il y a quelque chose de spécifique sur une mise en œuvre concrète d'un analyseur (par exemple JsonParser, XmlParser...), ou vous devez simplement utiliser une classe. "L'implémentation par défaut" n'existe pas (bien que dans certains environnements, cela ait effectivement un sens, notamment COM), il existe une implémentation spécifique ou vous souhaitez une classe abstraite ou des méthodes d'extension pour les "valeurs par défaut". Cependant, en C # I, conservez-le , à moins que votre base de code n'omette déjà le préfixe -prefix. Notez simplement chaque fois que vous voyez du code class Something: ISomething- cela signifie que quelqu'un n'est pas très bon pour suivre YAGNI et créer des abstractions raisonnables.

[1] - Techniquement, ceci n'est pas spécifiquement mentionné dans le papier de Liskov, mais c'est l'un des fondements du papier original de la POO et dans ma lecture de Liskov, elle n'a pas contesté cela. Dans une interprétation moins stricte (celle prise par la plupart des langages POO), cela signifie que tout code utilisant un type public destiné à être substitué (c'est-à-dire non final / scellé) doit fonctionner avec toute implémentation conforme de ce type.

Luaan
la source
3
Nitpick: LSP ne dit pas que chaque type doit être non final. Il dit juste que pour les types qui sont non-finale, les consommateurs doivent être en mesure d'utiliser les sous - classes transparente. - Et bien sûr, Java a aussi des types finaux.
Konrad Rudolph
@ KonradRudolph I a ajouté une note de bas de page à ce sujet; merci pour les commentaires :)
Luaan
4

Ainsi, par exemple, comment nommer ces deux personnes? Analyseur et ConcreteParser? Parser et ParserImplementation?

Dans un monde idéal, vous ne devriez pas préfixer votre nom avec un raccourci ("I ..."), mais simplement laisser le nom exprimer un concept.

J'ai lu quelque part (et ne peux pas en trouver l'origine) l'opinion selon laquelle cette convention ISomething vous amène à définir un concept "IDollar" lorsque vous avez besoin d'une classe "Dollar", mais que la solution correcte serait un concept appelé simplement "devise".

En d’autres termes, la convention donne une solution facile à la difficulté de nommer les choses et la facilité est subtilement fausse .


Dans le monde réel, les clients de votre code (qui peut être simplement "vous du futur") doivent pouvoir le lire plus tard et le connaître le plus rapidement possible (ou avec le moins d'effort possible) (donnez aux clients de votre code ce qu’ils espèrent obtenir).

La convention ISomething, introduit des noms cruels / mauvais. Cela dit, la cruauté n’est pas réelle *, à moins que les conventions et les pratiques utilisées dans l’écriture du code ne soient plus réelles; si la convention dit "utiliser ISomething", il est préférable de l'utiliser, de se conformer (et de mieux travailler) à d'autres développeurs.


*) Je peux me permettre de dire que ceci parce que "cruft" n'est pas un concept exact de toute façon :)

utnapistim
la source
2

Comme le mentionnent les autres réponses, la préfixe de vos noms d'interface Ifait partie des instructions de codage du framework .NET. Etant donné que les classes concrètes interagissent plus souvent que les interfaces génériques en C # et VB.NET, elles sont de premier ordre dans les schémas de dénomination et doivent donc porter les noms les plus simples - par conséquent, IParserpour l'interface et Parserpour l'implémentation par défaut.

Mais dans le cas de Java et de langages similaires, où les interfaces sont préférées aux classes concrètes, Uncle Bob a raison de dire qu'il Idevrait être supprimé et que les interfaces devraient avoir les noms les plus simples - Parserpour votre interface d'analyse, par exemple. Mais au lieu d’un nom de type rôle ParserDefault, la classe devrait avoir un nom décrivant comment l’analyseur est implémenté :

public interface Parser{
}

public class RegexParser implements Parser {
    // parses using regular expressions
}

public class RecursiveParser implements Parser {
    // parses using a recursive call structure
}

public class MockParser implements Parser {
    // BSes the parsing methods so you can get your tests working
}

Ceci est, par exemple, comment Set<T>, HashSet<T>et TreeSet<T>sont nommés.

TheHinsinator
la source
5
Les interfaces sont également préférées en C #. Qui vous a dit qu'il était préférable d'utiliser des types concrets dans dotnet?
RubberDuck
@RubberDuck Il est caché dans certaines des hypothèses que le langage vous impose. Par exemple, le fait que les membres soient sealedpar défaut et vous devez les définir explicitement virtual. Être virtuel est le cas particulier de C #. Bien sûr, comme l'approche recommandée pour l' écriture de code a changé au fil des ans, de nombreux vestiges du passé ont dû rester compatibilité :) Le point clé est que si vous obtenez une interface en C # (qui commence par I), vous savez qu'il est un tout type abstrait; Si vous obtenez une non-interface, l'hypothèse habituelle est que c'est le type que vous voulez, LSP être damné.
Luaan
1
Les membres sont scellés par défaut, ce qui explique clairement ce que les héritiers peuvent utiliser sur @Luaan. Certes, il s’agit parfois d’un PITA, mais cela n’a rien à voir avec une préférence pour les types concrets. Le virtuel n'est certainement pas un cas particulier en C #. On dirait que vous avez eu la malchance de travailler dans une base de code C # écrite par des amateurs. Je suis désolé que ce soit votre expérience avec la langue. Il est préférable de se rappeler que le dev n'est pas la langue et vice versa.
RubberDuck
@RubberDuck Hah, je n'ai jamais dit que je ne l'aimais pas. Je le préfère beaucoup, car la plupart des gens ne comprennent pas l’indirection . Même parmi les programmeurs. Scellé par défaut, c'est mieux à mon avis. Et quand j'ai commencé avec C #, tout le monde était un amateur, y compris l'équipe .NET :) Dans "real OOP", tout est supposé être virtuel - chaque type doit être remplaçable par un type dérivé, qui doit pouvoir remplacer tout comportement . Mais "real OOP" a été conçu pour les spécialistes, pas pour les personnes qui ont cessé de remplacer les ampoules par de la programmation, car il leur
fallait
Non ... Non. Ce n'est pas comme ça que la "vraie POO" fonctionne. En Java, vous devriez prendre le temps de sceller les méthodes qui ne devraient pas être ignorées, sans le laisser comme dans le Far West où tout le monde peut tout faire. LSP ne signifie pas que vous pouvez remplacer tout ce que vous voulez. LSP signifie que vous pouvez remplacer une classe par une autre en toute sécurité. Smh
RubberDuck
-8

Vous avez raison de dire que cette convention I = interface enfreint les (nouvelles) règles

Auparavant, vous préfixiez tout avec son type intCounter boolFlag etc.

Si vous voulez nommer des choses sans le préfixe, mais évitez également 'ConcreteParser', vous pouvez utiliser des espaces de noms.

namespace myapp.Interfaces
{
    public interface Parser {...
}


namespace myapp.Parsers
{
    public class Parser : myapp.Interfaces.Parser {...
}
Ewan
la source
11
Nouveau? vieux? Attendez, je pensais que la programmation était plus une affaire de rationalité que de battage publicitaire. Ou était-ce autrefois?
3
Non, il s'agit de faire des conventions et de bloguer sur la façon dont ils «améliorent la lisibilité»
Ewan
8
Je pense que vous pensez à la notation hongroise, dans laquelle vous avez effectivement préfixé des noms avec des types. Mais il n'y avait pas de temps dans lequel vous écririez quelque chose comme intCounterou boolFlag. Ce sont les mauvais "types". Au lieu de cela, vous écririez pszName(pointeur sur une chaîne contenant un nom, terminée par NUL), cchName(nombre de caractères de la chaîne "name"), xControl(coordonnée x du contrôle), fVisible(indicateur indiquant que quelque chose est visible), pFile(pointeur sur un fichier), hWindow(gérer une fenêtre), etc.
Cody Grey
2
Il y a encore beaucoup de valeur dans cela. Un compilateur ne va pas attraper l'erreur lorsque vous ajoutez xControlà cchName. Ils sont tous deux de type int, mais ils ont une sémantique très différente. Les préfixes rendent cette sémantique évidente pour un humain. Un pointeur sur une chaîne terminée par NUL ressemble exactement à un pointeur sur une chaîne de style Pascal vers un compilateur. De même, le Ipréfixe des interfaces est apparu dans le langage C ++, où les interfaces ne sont en réalité que des classes (et en C, où il n’existe pas de classe ni d’interface au niveau du langage, c’est juste une convention).
Cody Grey
4
@CodyGray Joel Spolsky a écrit un bel article intitulé Making Wrong Code (Rechercher un code erroné) concernant la notation hongroise et la manière dont elle a été (mal) comprise. Il a un avis similaire: le préfixe devrait être un type de niveau supérieur, pas le type de stockage de données brutes. Il utilise le mot "kind", expliquant: "J'utilise le mot kind exprès, là-bas, parce que Simonyi a utilisé à tort le type de mot dans son document et que des générations de programmeurs ont mal compris ce qu'il voulait dire."
Joshua Taylor