Pourquoi ne pas utiliser la directive 'using' en C #?

71

Les normes de codage existantes pour un grand projet C # incluent une règle selon laquelle tous les noms de type doivent être pleinement qualifiés, interdisant ainsi l’utilisation de la directive "using". Donc, plutôt que le familier:

using System.Collections.Generic;

.... other stuff ....

List<string> myList = new List<string>();

(Ce n'est probablement pas une surprise si cela varest également interdit.)

Je me retrouve avec:

System.Collections.Generic.List<string> myList = new System.Collections.Generic.List<string>();

Cela représente une augmentation de 134% du nombre de saisies, sans que cette augmentation fournisse des informations utiles. À mon avis, 100% de l'augmentation est du bruit (encombrement) qui empêche la compréhension.

En plus de 30 ans de programmation, j'ai vu une telle norme proposée une ou deux fois, mais jamais mise en œuvre. La raison derrière cela m'échappe. La personne qui impose la norme n’est pas stupide et je ne pense pas qu’elle soit méchante. Ce qui laisse à tort comme la seule autre alternative à moins que je manque quelque chose.

Avez-vous déjà entendu parler d'une telle norme imposée? Si oui, quelle était la raison derrière cela? Pouvez-vous penser à des arguments autres que "c'est stupide" ou "tout le monde emploie using" qui pourraient convaincre cette personne de lever cette interdiction?

Raisonnement

Les raisons de cette interdiction étaient:

  1. Il est fastidieux de passer la souris sur le nom pour obtenir le type pleinement qualifié. Il est préférable de toujours avoir le type pleinement qualifié visible tout le temps.
  2. Les extraits de code envoyés par courrier électronique n'ont pas le nom complet et peuvent donc être difficiles à comprendre.
  3. Lors de l'affichage ou de la modification du code en dehors de Visual Studio (Notepad ++, par exemple), il est impossible d'obtenir le nom de type qualifié complet.

Mon argument est que les trois cas sont rares et que faire payer à chacun le prix d'un code encombré et moins compréhensible juste pour tenir compte de quelques cas rares est erroné.

Les problèmes potentiels de conflit dans les espaces de noms, que je pensais être la principale préoccupation, n'ont même pas été mentionnés. C'est particulièrement surprenant, car nous avons un espace de noms MyCompany.MyProject.Core, ce qui est une très mauvaise idée. J'ai appris il y a longtemps que nommer n'importe quoi Systemou Coreen C # est un chemin rapide vers la folie.

Comme d'autres l'ont souligné, les conflits d'espaces de noms sont facilement résolus par le refactoring, les alias d'espaces de noms ou la qualification partielle.

Jim Mischel
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
1
Un exemple concret de pourquoi cela pourrait être une bonne idée (mais dans le monde C ++): une réponse à Pourquoi l'utilisation de namespace std est-elle considérée comme une mauvaise pratique? , près de "que les directives et déclarations" utilisant "sont interdites".
Peter Mortensen
Avant de lire la section Raisonnement, j'ai en quelque sorte deviné les raisons peu pratiques, car mon entreprise a des règles similaires.
Gqqnbig

Réponses:

69

La question plus large:

Avez-vous déjà entendu parler d'une telle norme imposée? Si oui, quelle était la raison derrière cela?

Oui, j'en ai entendu parler et l'utilisation de noms d'objet pleinement qualifiés évite les conflits de noms. Bien que rares, quand ils se produisent, ils peuvent être exceptionnellement épineux à comprendre.


Un exemple: Ce type de scénario est probablement mieux expliqué avec un exemple.

Disons que nous avons deux Lists<T>appartenant à deux projets distincts.

System.Collections.Generic.List<T>
MyCorp.CustomCollections.Optimized.List<T>

Lorsque nous utilisons le nom d'objet qualifié complet, il est clairement indiqué lequel List<T>est utilisé. Cette clarté est évidemment au détriment de la verbosité.

Et vous vous disputerez peut-être: "Attendez! Personne n’utilisera jamais deux listes de ce genre!" C'est là que je signalerai le scénario de maintenance.

Vous êtes un module écrit Foopour votre société qui utilise la société approuvée, optimisée List<T>.

using MyCorp.CustomCollections.Optimized;

public class Foo {
    List<object> myList = ...;
}

Plus tard, un nouveau développeur décide d'étendre le travail que vous avez fait. Ne connaissant pas les normes de l'entreprise, ils mettent à jour le usingblocage:

using MyCorp.CustomCollections.Optimized;
using System.Collections.Generic;

Et vous pouvez voir comment les choses vont mal à la hâte.

Il devrait être trivial de souligner que vous pouvez avoir deux classes propriétaires du même nom mais dans des espaces de noms différents au sein du même projet. Il ne s’agit donc pas uniquement de la collision avec les classes fournies par .NET Framework.

MyCorp.WeightsAndLengths.Measurement();
MyCorp.TimeAndSpace.Measurement();

La réalité:
est-ce que cela va probablement se produire dans la plupart des projets? Non, pas vraiment. Mais lorsque vous travaillez sur un grand projet avec beaucoup d’intrants, vous faites de votre mieux pour minimiser les risques d’explosion.

Les grands projets avec plusieurs équipes contributives constituent un type particulier de bête dans le monde des applications. Les règles qui semblent déraisonnables pour d'autres projets deviennent plus pertinentes en raison des flux d'entrées vers le projet et de la probabilité que ceux qui contribuent ne lisent pas les directives du projet.

Cela peut également se produire lorsque deux grands projets sont fusionnés. Si les deux projets ont des noms similaires, vous verrez des collisions lorsque vous commencez à référencer d'un projet à l'autre. Et les projets sont peut-être trop importants pour être restructurés ou la direction n’approuve pas les dépenses engagées pour financer le temps consacré à la refactorisation.


Alternatives:
Bien que vous ne l'ayez pas demandé, cela vaut la peine de souligner que ce n'est pas une bonne solution au problème. Ce n'est pas une bonne idée de créer des classes qui vont entrer en collision sans leurs déclarations d'espace de noms.

List<T>en particulier, doit être traité comme un mot réservé et non utilisé comme nom pour vos classes.

De même, les espaces de noms individuels au sein du projet doivent s'efforcer d'avoir des noms de classe uniques. Devoir essayer de se rappeler avec quel espace de noms Foo()vous travaillez est une surcharge mentale qu'il vaut mieux éviter. Dit autrement: avoir MyCorp.Bar.Foo()et MyCorp.Baz.Foo()va faire trébucher vos développeurs et les éviter au mieux.

Si rien d'autre, vous pouvez utiliser des espaces de noms partiels afin de résoudre l'ambiguïté. Par exemple, si vous ne pouvez absolument pas renommer l'une ou l'autre Foo()classe, vous pouvez utiliser leurs espaces de noms partiels:

Bar.Foo() 
Baz.Foo()

Raisons spécifiques de votre projet actuel:

Vous avez mis à jour votre question avec les raisons spécifiques qui vous ont été données pour votre projet actuel, conformément à cette norme. Jetons un coup d'oeil à eux et détournons vraiment le sentier du lapin.

Il est fastidieux de passer la souris sur le nom pour obtenir le type pleinement qualifié. Il est préférable de toujours avoir le type pleinement qualifié visible tout le temps.

"Lourd?" Um non. Ennuyeux peut-être. Déplacer quelques onces de plastique afin de déplacer un pointeur à l'écran n'est pas encombrant . Mais je m'égare.

Cette ligne de raisonnement semble plus être une dissimulation que toute autre chose. De prime abord, je suppose que les classes de l'application ont un nom faible et que vous devez compter sur l'espace de noms pour obtenir la quantité appropriée d'informations sémantiques entourant le nom de la classe.

Ce n'est pas une justification valable pour les noms de classe pleinement qualifiés, mais peut-être aussi une justification valable pour utiliser des noms de classe partiellement qualifiés.

Les extraits de code envoyés par courrier électronique n'ont pas le nom complet et peuvent donc être difficiles à comprendre.

Ce raisonnement (suite?) Renforce mes soupçons sur le fait que les classes portent actuellement un mauvais nom. Encore une fois, avoir des noms de classe médiocres n'est pas une bonne justification pour exiger que tout utilise un nom de classe pleinement qualifié. Si le nom de classe est difficile à comprendre, il y a beaucoup plus de problèmes que ce que les noms de classe pleinement qualifiés peuvent corriger.

Lors de l'affichage ou de la modification du code en dehors de Visual Studio (Notepad ++, par exemple), il est impossible d'obtenir le nom de type qualifié complet.

De toutes les raisons, celle-ci m'a presque fait cracher mon verre. Mais encore une fois, je m'égare.

Je me demande pourquoi l'équipe modifie ou affiche fréquemment le code en dehors de Visual Studio. Et nous examinons maintenant une justification qui est assez orthogonale à ce que les espaces de noms sont censés fournir. C'est un argument basé sur les outils, alors que les espaces de noms sont là pour fournir une structure organisationnelle au code.

Il semble que votre projet souffre de mauvaises conventions de nommage, ainsi que de développeurs qui ne profitent pas de ce que l’outillage peut leur fournir. Et plutôt que de résoudre ces problèmes réels, ils ont tenté de casser un pansement sur l'un des symptômes et ont demandé des noms de classe complets. Je pense qu'il est prudent de catégoriser cela comme une approche erronée.

Étant donné qu'il existe des classes mal nommées et en supposant que vous ne pouvez pas refactoriser, la bonne réponse consiste à utiliser pleinement l'environnement de développement intégré de Visual Studio. Envisagez éventuellement d’ajouter un plugin tel que le package VS PowerTools. Ensuite, lorsque je regarde, AtrociouslyNamedClass()je peux cliquer sur le nom de la classe, appuyer sur F12 et accéder directement à la définition de la classe afin de mieux comprendre ce qu’il essaie de faire. De même, je peux cliquer sur Shift-F12 pour trouver toutes les taches du code qui doivent actuellement être utilisées AtrociouslyNamedClass().

En ce qui concerne les problèmes d'outillage extérieur - la meilleure chose à faire est simplement de l'arrêter. N'envoyez pas d'e-mails de bout en bout s'ils ne sont pas immédiatement clairs sur ce qu'ils renvoient. N'utilisez pas d'autres outils en dehors de Visual Studio, car ils ne possèdent pas l'intelligence nécessaire au code dont votre équipe a besoin. Notepad ++ est un outil génial, mais il n'est pas conçu pour cette tâche.

Je suis donc d'accord avec votre évaluation concernant les trois justifications spécifiques qui vous ont été présentées. Cela dit, on vous a dit ce qui suit: "Nous avons des problèmes sous-jacents dans ce projet qui ne peuvent pas / ne vont pas être résolus et c'est comme cela que nous les avons" résolus "." Et cela évoque évidemment des problèmes plus profonds au sein de l'équipe qui peuvent vous servir de signaux d'alarme.


la source
1
+1 J'ai également rencontré de vives collisions dans nos espaces de noms internes. Que ce soit pour le portage de code hérité dans un projet "2.0" ou simplement pour sélectionner quelques noms de classe sémantiquement valables dans différents contextes d'espace de noms. Ce qui est vraiment délicat, c'est que deux choses portant le même nom auront souvent des interfaces similaires, de sorte que le compilateur ne se plaindra pas ... votre runtime en sera ainsi!
svidgen
23
Ce problème est résolu en utilisant des alias d'espaces de noms, et non en imposant des règles stupides exigeant du code, encombrées par une utilisation explicite des espaces de noms.
David Arno
4
@ DavidArno - Je ne suis pas en désaccord avec vous. Cependant, les grands projets peuvent ne pas avoir cette option. Je signale simplement pourquoi un grand projet peut imposer cette règle. Je ne préconise cependant pas la règle. Juste que c'est parfois nécessaire parce que l'équipe n'a pas d'autres options.
4
@ GlenH7, vous pouvez spécifier les autres solutions disponibles dans votre réponse. Je détesterais voir les gens trébucher et penser "Oh. C'est une bonne idée!" +1 pour rester objectif et pragmatique si.
RubberDuck
3
@JimMischel - On dirait que la règle est utilisée comme une béquille pour quelque chose . Il n'est peut-être pas possible de déterminer quelle est cette chose. À un niveau élevé, oui, il est parfois justifié de ne pas autoriser, usingsmais il ne semble pas que votre projet soit considéré comme l'un de ces cas.
16

J'ai travaillé dans une entreprise où plusieurs classes avaient le même nom, mais dans des bibliothèques de classes différentes.

Par exemple,

  • Domaine.client
  • Legacy.Client
  • ServiceX.Client
  • ViewModel.Client
  • Base de données. Client

Vous pourriez dire que la conception est mauvaise, mais vous savez comment ces choses se développent de manière organique et quelle est l'alternative: renommer toutes les classes pour inclure l'espace de noms? Refactoring majeur de tout?

Dans tous les cas, il y avait plusieurs endroits où les projets faisant référence à toutes ces bibliothèques différentes devaient utiliser plusieurs versions de la classe.

Avec le usingdirectement au sommet, c’était un problème majeur, ReSharper faisait des choses bizarres pour essayer de le faire fonctionner, réécrivant les usingdirectives à la volée, vous obtiendrez un client qui ne peut pas être moulé comme tel. Erreurs de type client et ne pas savoir quel type était le bon.

Donc, ayant au moins l'espace de noms partiel,

var cust = new Domain.Customer

ou

public void Purchase(ViewModel.Customer customer)

grandement amélioré la lisibilité du code et explicite quel objet vous vouliez.

Ce n'était pas une norme de codage, ce n'était pas tout l'espace de noms, et cela ne s'appliquait pas à toutes les classes en standard.

Ewan
la source
13
"Quelle est l'alternative, renommer toutes les classes pour inclure l'espace de noms? refactoring majeur de tout?" Oui, c'est l'alternative (correcte).
David Arno
2
Seulement à court terme. C'est beaucoup moins cher que d'utiliser des espaces de noms explicites dans le code à long terme.
David Arno
3
Heureux d'accepter cet argument, tant que vous me payez en espèces et non en actions.
Ewan
8
@ David: Non, ce n'est pas la bonne alternative. Qualifier complètement tous les identifiants, ou du moins ceux qui se rencontrent réellement, est le moyen correct et la raison pour laquelle les espaces de noms existent en premier lieu est pour fournir des collisions lorsque le même nom est utilisé dans différents modules. Le fait est que certains modules peuvent provenir de tierces parties, code que vous ne pouvez pas modifier. Alors, que faites-vous lorsque les identifiants de deux bibliothèques tierces différentes se rencontrent et que vous devez utiliser les deux bibliothèques, mais que vous ne pouvez pas les refactoriser? Les espaces de noms existent, car le refactoring n'est pas toujours possible.
Kaiserludi
4
@CarlSixsmith, si l'on souscrit aux vues de Martin Fowler sur le refactoring, alors si le changement concerne l'utilisateur, vous avez raison, ce n'est pas du refactoring; c'est une autre forme de restructuration. Cela soulève cependant deux points: 1. toutes les méthodes publiques font-elles automatiquement partie de l'API? 2. Fowler lui-même reconnaît qu'il se bat pour perdre cette définition, un nombre croissant de personnes utilisant le terme "refactorisation" pour désigner une restructuration généralisée, y compris des modifications publiques.
David Arno
7

Il n’est pas bon d’être trop bavard ou trop court: il faut trouver un juste milieu en étant suffisamment détaillé pour éviter les bugs subtils, tout en évitant d’écrire trop de code.

En C #, les noms des arguments en sont un exemple. J'ai rencontré à plusieurs reprises un problème où je passais des heures à chercher une solution, alors qu'un style d'écriture plus verbeux pourrait empêcher le bogue en premier lieu. Le problème consiste en une erreur dans l'ordre des arguments. Par exemple, cela vous semble-t-il correct?

if (...) throw new ArgumentNullException("name", "The name should be specified.");
if (...) throw new ArgumentException("name", "The name contains forbidden characters.");

Au premier coup d'oeil, ça a l'air parfait, mais il y a un bug. Les signatures des constructeurs des exceptions sont:

public ArgumentException(string message, string paramName)
public ArgumentNullException(string paramName, string message)

Remarqué l'ordre des arguments?

En adoptant un style plus détaillé dans lequel le nom d'un argument est spécifié chaque fois que la méthode accepte deux arguments ou plus du même type , nous évitons que l'erreur ne se reproduise, et donc:

if (...) throw new ArgumentNullException(paramName: "name", message: "The name should be specified.");
if (...) throw new ArgumentException(paramName: "name", message: "The name contains forbidden characters.");

est évidemment plus long à écrire, mais entraîne moins de perte de temps en débogage.

Poussé à l'extrême, on pourrait forcer l'utilisation d'arguments nommés partout , y compris dans des méthodes telles que les doSomething(int, string)cas où il est impossible de mélanger l'ordre des paramètres. Cela va probablement nuire au projet à long terme, résultant en beaucoup plus de code sans l'avantage d'éviter le bogue que j'ai décrit ci-dessus.

Dans votre cas, la motivation derrière la « Aucune usingrègle » est qu'il devrait rendre plus difficile d'utiliser le mauvais type, par exemple par MyCompany.Something.List<T>rapport à System.Collections.Generic.List<T>.

Personnellement, j’éviterais une telle règle car, à mon humble avis, le coût d’un code difficile à lire est bien supérieur au bénéfice d’un risque légèrement réduit d’utilisation abusive du mauvais type, mais au moins, cette règle a une raison valable.

Arseni Mourzenko
la source
Des situations comme celles-ci peuvent être détectées par des outils. ReSharper marque le paramNameavec l' InvokerParameterNameattribut et émet un avertissement si ce paramètre n'existe pas.
Johnbot
2
@Johnbot: ils le peuvent certainement, mais dans un nombre de cas très limité. Et si j'ai un SomeBusiness.Something.DoStuff(string name, string description)? Aucun outil n'est assez intelligent pour comprendre ce qu'est un nom et ce qu'est une description.
Arseni Mourzenko
@ArseniMourzenko dans ce cas, même les humains doivent prendre le temps de déterminer si l'invocation est correcte ou non.
Gqqnbig
6

Les espaces de noms donnent au code une structure hiérarchique, permettant des noms plus courts.

   MyCompany.Headquarters.Team.Leader
   MyCompany.Warehouse.Team.Leader

Si vous autorisez le mot-clé "using", il y a une chaîne d'événements ...

  • Tout le monde utilise réellement un seul espace de noms global
    (car ils incluent tous les espaces de noms qu'ils pourraient éventuellement souhaiter)
  • Les gens commencent à avoir des conflits de noms, ou
  • vous inquiétez pas pour avoir des conflits de noms.

Donc, pour éviter les risques, tout le monde commence à utiliser des noms très longs:

   HeadquartersTeamLeader
   WarehouseTeamLeader

La situation dégénère ...

  • Quelqu'un a peut-être déjà choisi un nom, vous en utiliserez donc un plus long.
  • Les noms deviennent de plus en plus longs.
  • Les longueurs peuvent dépasser 60 caractères, sans maximum - cela devient ridicule.

J'ai vu cela se produire, alors je peux sympathiser avec les entreprises qui interdisent "l'utilisation".


la source
11
L'interdiction usingest l'option nucléaire. Les alias d'espace de noms et la qualification partielle sont préférables.
Jim Mischel
1

Le problème usingest que cela ajoute par magie à l'espace de noms sans déclaration explicite. C'est beaucoup plus un problème pour le programmeur de maintenance qui rencontre quelque chose comme, List<>et sans connaître le domaine, ne sait pas quelle usingclause l'a introduit.

Un problème similaire se produit en Java si l’on écrit import Java.util.*;plutôt que import Java.util.List;par exemple; cette dernière déclaration documente le nom qui a été introduit de sorte que, lorsque cela List<>se produit plus tard dans la source, son origine soit connue de la importdéclaration.

Michael Montenero
la source
6
S'il est vrai que quelqu'un qui ne connaît pas bien le domaine aura quelques difficultés, il lui suffit de survoler la souris avec le nom du type pour obtenir le nom complet. Il peut également cliquer avec le bouton droit de la souris et accéder directement à la définition du type. Pour les types .NET Framework, il sait probablement déjà où sont les choses. Pour les types spécifiques à un domaine, il lui faudra peut-être une semaine pour se mettre à l'aise. À ce stade, les noms complets ne sont que du bruit qui empêche la compréhension.
Jim Mischel
Jim .. J'ai juste essayé de passer la souris sur une définition et cela n'a pas fonctionné. Aviez-vous envisagé la possibilité que certains programmeurs dans le monde n'utilisent pas les IDE Microsoft? Dans tous les cas, votre compteur n'invalide pas mon point, que la source avec using n'est plus auto-documentée
Michael Montenero
2
Oui, il y a des personnes travaillant en C # qui se handicapent en n'utilisant pas les meilleurs outils disponibles. Et, alors que l'argument selon lequel la qualification complète est "auto-documentée" a du mérite, un tel code a un coût énorme en lisibilité. Tout ce code étranger crée du bruit qui masque les parties du code qui importent vraiment.
Jim Mischel