web-dev-qa-db-fra.com

Problèmes de dénomination: "ISomething" doit-il être renommé en "Something"?

Le chapitre de l'oncle Bob sur les noms en Clean Code recommande d'éviter les encodages dans les noms, principalement en ce qui concerne la notation hongroise. Il mentionne également spécifiquement la suppression du préfixe I des interfaces, mais n'en montre aucun exemple.

Supposons ce qui suit:

  • L'interface est principalement utilisée pour atteindre la testabilité grâce à l'injection de dépendances
  • Dans de nombreux cas, cela conduit à avoir une seule interface avec un seul implémenteur

Ainsi, par exemple, comment nommer ces deux-là? Parser et ConcreteParser? Parser et ParserImplementation?

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

public class Parser : IParser {
     // Implementations
}

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

68
Vinko Vrsalovic

Alors que beaucoup, y compris "Oncle Bob", conseillent de ne pas utiliser I comme préfixe pour les interfaces, le faire est une tradition bien établie avec C #. D'une manière générale, il convient de l'éviter. Mais si vous écrivez C #, vous devez vraiment suivre les conventions de ce langage et l'utiliser. Ne pas le faire causera une énorme confusion avec toute autre personne familière avec C # qui essaie de lire votre code.

188
David Arno

L'interface est le concept logique important, par conséquent, l'interface doit porter le nom générique. Je préfère donc

interface Something
class DefaultSomething : Something
class MockSomething : Something

que

interface ISomething
class Something : ISomething
class MockSomething : ISomething

Ce dernier a plusieurs enjeux:

  1. Quelque chose n'est qu'une implémentation de ISomething, mais c'est celle avec le nom générique, comme si elle était en quelque sorte spéciale.
  2. MockSomething semble dériver de Something, mais implémente ISomething. Peut-être que cela devrait s'appeler MockISomething, mais je ne l'ai jamais vu dans la nature.
  3. Le refactoring est plus difficile. Si Something est une classe maintenant, et que vous ne découvrez que plus tard que vous devez introduire une interface, cette interface doit être nommée de la même manière, car elle doit être transparente pour les clients, que le type soit une classe concrète, une classe abstraite ou une interface. . Quelque chose rompt cela.
39
wallenborn

Ce n'est pas juste sur les conventions de nommage. C # ne prend pas en charge l'héritage multiple, donc cette utilisation héritée de la notation hongroise a un petit avantage bien que 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

27
Robbie Dee

Non non Non.

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

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

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

Lors de la lecture d'une nouvelle base de code si je ne vois jamais de préfixe I je vais bien, quelle que soit la langue. Si j'en vois un sur chaque interface, je vais bien. Si j'en vois parfois un, quelqu'un va payer.

Si vous vous trouvez dans le contexte raréfié de la création de ce précédent pour votre base de code, je vous invite à considérer ceci:

En tant que client, je me réserve le droit de ne pas me soucier de ce à 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 peuvent ne pas changer à moins que je ne le dise. JE POSSÈDE cette partie. Ce à quoi je parle, interface ou non, ce n'est pas mon service.

Si vous glissez un I dans ce nom, le client s'en fiche. S'il n'y a pas de I, le client s'en fiche. Ce dont il parle pourrait être concret. Il se pourrait que non. Puisqu'il n'appelle pas new 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 changer une implémentation en une interface, je peux le faire sans en informer le client. S'il n'y a pas de tradition I je ne pourrais même pas la renommer. Ce qui pourrait être un problème car maintenant nous devons recompiler le client parce que même si la source ne se soucie pas du binaire. Quelque chose de facile à manquer si vous n'avez jamais changé de nom.

C'est peut-être 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 simplement le faire aveuglément parce que c'est la façon dont cela se fait dans une langue. Soit vous avez une sacrée bonne raison, soit vous ne vous en souciez pas.

Il ne s'agit pas de vivre avec lui pour toujours. Il s'agit de ne pas me donner de conneries sur la base de code non conforme à votre mentalité particulière, sauf si vous êtes prêt à résoudre le "problème" partout. À moins que vous n'ayez une bonne raison de ne pas emprunter la voie de la moindre résistance à l'uniformité, ne venez pas me prêcher la conformité. J'ai de meilleures choses à faire.

15
candied_orange

Il y a une toute petite différence entre Java et C # qui est pertinent 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 hypothèses qui vont avec cela influencent la directive - en Java, chaque 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, vous allez simplement changer la classe en une interface avec le même nom et 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. Attention, ce n'est pas du tout exact à 100% - un contre-exemple typique serait des classes comme Stream (qui aurait vraiment dû être une interface ou quelques interfaces), et tout le monde a ses propres directives et arrière-plans à partir d'autres langues. Il y a aussi d'autres exceptions comme le suffixe Base assez largement utilisé pour désigner une classe abstraite - tout comme avec une interface, vous savez le type est censé être polymorphe.

Il y a aussi une fonctionnalité agréable à utiliser en laissant le nom non préfixé I pour les fonctionnalités liées à cette interface sans avoir à recourir à faire de l'interface une classe abstraite (ce qui nuirait en raison du manque d'héritage multiple de classe en C #). Cela a été popularisé par LINQ, qui utilise IEnumerable<T> comme interface et Enumerable comme référentiel de méthodes qui s'appliquent à cette interface. Cela n'est pas nécessaire en Java, où les interfaces peuvent également contenir des implémentations de méthode.

En fin de compte, le préfixe I est largement utilisé dans le monde C # et, par extension, dans le monde .NET (car de loin la plupart du code .NET est écrit en C #, il est logique de suivre les directives C # pour la plupart des les interfaces publiques). Cela signifie que vous travaillerez presque certainement avec des bibliothèques et du code qui suivent cette notation, et il est logique d'adopter la tradition pour éviter toute confusion inutile - ce n'est pas comme omettre le préfixe améliorera votre code :)

Je suppose que le raisonnement de l'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 que Banana, l'abstraction n'a aucun sens et vous devez supprimer l'interface et utiliser simplement une classe. S'il existe un meilleur nom (par exemple, LongBanana ou AppleBanana), il n'y a aucune raison de ne pas utiliser Banana comme nom de l'interface. Par conséquent, l'utilisation du préfixe I signifie que vous avez une abstraction inutile, ce qui rend le code plus difficile à comprendre sans aucun avantage. Et puisque strict OOP vous fera toujours coder contre les interfaces, le seul endroit où vous ne verriez pas le préfixe I sur un type serait sur un constructeur - bruit assez inutile .

Si vous appliquez ceci à votre exemple d'interface IParser, vous pouvez clairement voir que l'abstraction est entièrement dans le territoire "vide de sens". Soit il y a quelque chose de spécifique dans une implémentation concrète d'un analyseur (par exemple JsonParser, XmlParser, ...), soit vous devez simplement utiliser une classe. Il n'y a rien de tel que "implémentation par défaut" (bien que dans certains environnements, cela ait effectivement du sens - notamment COM), soit il existe une implémentation spécifique, soit vous voulez une classe abstraite ou des méthodes d'extension pour les "valeurs par défaut". Cependant, en C #, sauf si votre base de code omet déjà le préfixe I-, conservez-le. Faites juste une note mentale chaque fois que vous voyez du code comme class Something: ISomething - cela signifie que quelqu'un n'est pas très bon pour suivre YAGNI et construire des abstractions raisonnables.

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

8
Luaan

Ainsi, par exemple, comment nommer ces deux-là? Analyseur et ConcreteParser? Analyseur et implémentation de l'analyseur?

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 je ne peux pas me l'approvisionner) l'opinion que cette convention ISomething vous conduit à définir un concept "IDollar" lorsque vous avez besoin d'une classe "Dollar", mais la bonne solution serait un concept appelé simplement "Currency".

En d'autres termes, la convention donne une sortie facile à la difficulté de nommer les choses et la sortie facile est subtilement erronée .


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

La convention ISomething, introduit des noms corrompus/mauvais. Cela dit, la cruauté n'est pas une vraie cruauté *), à moins que les conventions et pratiques utilisées pour écrire le code ne soient plus d'actualité; si la convention dit "use ISomething", alors il est préférable de l'utiliser, pour se conformer (et mieux fonctionner) avec d'autres développeurs.


*) Je peux m'en tirer en disant que cette "cruft" n'est pas un concept exact de toute façon :)

3
utnapistim

Comme les autres réponses le mentionnent, le préfixe de vos noms d'interface avec I fait partie des directives de codage pour le framework .NET. Étant donné que les classes concrètes interagissent plus souvent que les interfaces génériques en C # et VB.NET, elles sont de première classe dans les schémas de dénomination et devraient donc avoir les noms les plus simples - d'où, IParser pour l'interface et Parser pour l'implémentation par défaut.

Mais dans le cas de Java et langages similaires, où les interfaces sont préférées au lieu de classes concrètes, l'oncle Bob a raison en ce que le I devrait être supprimé et les interfaces devraient avoir le plus simple names - Parser pour votre interface d'analyseur, par exemple. Mais au lieu d'un nom basé sur un rôle comme ParserDefault, la classe devrait avoir un nom qui décrit 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
}

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

2
TheHansinator