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 I
pré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? Parser
et ConcreteParser
? Parser
et 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?
c#
naming
clean-code
naming-standards
Vinko Vrsalovic
la source
la source
Réponses:
Bien que beaucoup, y compris "Uncle Bob", conseillent de ne pas utiliser
I
comme 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.la source
L'interface est le concept logique important; l'interface doit donc porter le nom générique. Donc, je préfère avoir
que
Ce dernier a plusieurs aspects:
la source
Default
niReal
ouImpl
dans plusieurs de mes noms de classeIList<T>
sans se soucier de la manière dont il est stocké en interne; pouvoir lopperI
et 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 ordinaireList<T>
devrait être utiliséArrayList<T>
.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 : Foo
maisSqlStore : Store
. C'est un problème que j'ai avecImpl
, qui est essentiellement juste un marqueur plus laid. Peut-être que s'il y avait une langue avec la convention de toujours ajouter unC
(ou autre chose), cela pourrait être mieux queI
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...
... est préférable à cela ...
OMI
la source
inherits
vsimplements
.extends
.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
I
pré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
I
nom dans ce nom, le client s'en fiche. S'il n'y en a pasI
, le client s'en fiche. Ce à quoi il parle peut être concret. Il se peut que non. Comme il ne fait pas appelnew
de 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
I
tradition, 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.
la source
I
s. 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.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 queStream
(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 leBase
suffixe 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 etEnumerable
comme 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
I
pré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:
IBanana
est la notion abstraite de banane. S'il peut y avoir une classe d'implémentation qui n'aurait pas de meilleur nom queBanana
, 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,LongBanana
ouAppleBanana
), il n'y a aucune raison de ne pas utiliserBanana
le nom de l'interface. Par conséquent, l’utilisation duI
pré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 leI
préfixe d'un type serait un constructeur - un bruit inutile.Si vous appliquez cela à votre exemple d'
IParser
interface, 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 exempleJsonParser
,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 codeclass 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.
la source
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 :)
la source
Comme le mentionnent les autres réponses, la préfixe de vos noms d'interface
I
fait 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,IParser
pour l'interface etParser
pour 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
I
devrait être supprimé et que les interfaces devraient avoir les noms les plus simples -Parser
pour votre interface d'analyse, par exemple. Mais au lieu d’un nom de type rôleParserDefault
, la classe devrait avoir un nom décrivant comment l’analyseur est implémenté :Ceci est, par exemple, comment
Set<T>
,HashSet<T>
etTreeSet<T>
sont nommés.la source
sealed
par défaut et vous devez les définir explicitementvirtual
. Ê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 parI
), 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é.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.
la source
intCounter
ouboolFlag
. Ce sont les mauvais "types". Au lieu de cela, vous écririezpszName
(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.xControl
àcchName
. Ils sont tous deux de typeint
, 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, leI
pré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).