Une odeur de code est un symptôme qui indique qu'il y a un problème dans la conception qui risque d'augmenter le nombre de bogues: ce n'est pas le cas pour les régions, mais les régions peuvent contribuer à créer des odeurs de code, comme les méthodes longues.
Puisque:
Un anti-modèle (ou anti-modèle) est un modèle utilisé dans les opérations sociales ou commerciales ou dans l'ingénierie logicielle qui peut être couramment utilisé mais qui est inefficace et / ou contre-productif dans la pratique.
les régions sont anti-modèles. Ils nécessitent plus de travail qui n'améliore pas la qualité ou la lisibilité du code, qui ne réduit pas le nombre de bogues, et qui ne peut que rendre le code plus compliqué à refactoriser.
N'utilisez pas de régions dans les méthodes; refactor à la place
Les méthodes doivent être courtes . S'il n'y a que dix lignes dans une méthode, vous n'utiliserez probablement pas de régions pour en cacher cinq lorsque vous travaillez sur les cinq autres.
En outre, chaque méthode doit faire une et une seule chose . Les régions, par contre, sont destinées à séparer différentes choses . Si votre méthode utilise A, puis B, il est logique de créer deux régions, mais c'est une mauvaise approche. au lieu de cela, vous devez reformuler la méthode en deux méthodes distinctes.
L'utilisation de régions dans ce cas peut également rendre la refactorisation plus difficile. Imaginez que vous avez:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
if (!verification)
{
throw new DataCorruptedException();
}
Do(data);
DoSomethingElse(data);
#endregion
#region Audit
var auditEngine = InitializeAuditEngine();
auditEngine.Submit(data);
#endregion
}
Réduire la première région pour se concentrer sur la seconde est non seulement risqué: nous pouvons facilement oublier l'exception qui stoppe le flux (il pourrait y avoir une clause de garde avec une return
substitution, ce qui est encore plus difficile à repérer), mais aurait également un problème. si le code doit être refait de cette façon:
private void DoSomething()
{
var data = LoadData();
#region Work with database
var verification = VerifySomething();
var info = DoSomethingElse(data);
if (verification)
{
Do(data);
}
#endregion
#region Audit
var auditEngine = InitializeAuditEngine(info);
auditEngine.Submit(
verification ? new AcceptedDataAudit(data) : new CorruptedDataAudit(data));
#endregion
}
Désormais, les régions n'ont aucun sens et il est impossible de lire et de comprendre le code de la deuxième région sans consulter le code de la première.
Un autre cas que je vois parfois est celui-ci:
public void DoSomething(string a, int b)
{
#region Validation of arguments
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
#endregion
#region Do real work
...
#endregion
}
Il est tentant d'utiliser des régions lorsque la validation des arguments commence à couvrir des dizaines de LOC, mais il existe un meilleur moyen de résoudre ce problème: celui utilisé par le code source du .NET Framework:
public void DoSomething(string a, int b)
{
if (a == null)
{
throw new ArgumentNullException("a");
}
if (b <= 0)
{
throw new ArgumentOutOfScopeException("b", ...);
}
InternalDoSomething(a, b);
}
private void InternalDoSomething(string a, int b)
{
...
}
N'utilisez pas de régions en dehors des méthodes pour regrouper
Certaines personnes les utilisent pour regrouper des champs, des propriétés, etc. Cette approche est incorrecte: si votre code est conforme à StyleCop, les champs, les propriétés, les méthodes privées, les constructeurs, etc. sont déjà regroupés et faciles à trouver. Si ce n'est pas le cas, il est temps de penser à appliquer des règles qui garantissent l'uniformité de votre code.
D'autres personnes utilisent des régions pour cacher beaucoup d'entités similaires . Par exemple, lorsque vous avez une classe avec cent champs (ce qui crée au moins 500 lignes de code si vous comptez les commentaires et les espaces), vous pouvez être tenté de placer ces champs dans une région, de la réduire et de les oublier. Encore une fois, vous vous trompez: avec autant de champs dans une classe, vous devriez penser à utiliser l’héritage ou découper l’objet en plusieurs objets.
Enfin, certaines personnes sont tentées d'utiliser des régions pour regrouper des éléments liés : un événement avec son délégué, ou une méthode liée à l'IO avec d'autres méthodes liées à l'IO, etc. Dans le premier cas, cela devient un désordre difficile à maintenir. , Lire et comprendre. Dans le second cas, la meilleure solution serait probablement de créer plusieurs classes.
Y a-t-il un bon usage pour les régions?
Non, il y avait une utilisation héritée: le code généré. Néanmoins, les outils de génération de code doivent simplement utiliser des classes partielles. Si C # est pris en charge par les régions, c'est principalement à cause de son utilisation héritée et du fait que maintenant trop de personnes utilisent des régions dans leur code, il serait impossible de les supprimer sans détruire les bases de code existantes.
Pensez-y comme à propos goto
. Le fait que la langue ou l'EDI prenne en charge une fonctionnalité ne signifie pas qu'elle doit être utilisée quotidiennement. La règle StyleCop SA1124 est claire: vous ne devez pas utiliser de régions. Jamais.
Exemples
Je suis en train de réviser le code de mon collègue. La base de code contient beaucoup de régions et est en fait un exemple parfait de la manière dont il est préférable de ne pas utiliser de régions et de la raison pour laquelle les régions génèrent un code incorrect. Voici quelques exemples:
4 000 monstres LOC:
J'ai récemment lu quelque part sur Programmers.SE que lorsqu'un fichier contient trop de using
s (après avoir exécuté la commande "Remove Unused Usings"), c'est un bon signe que la classe à l'intérieur de ce fichier en fait trop. La même chose s'applique à la taille du fichier lui-même.
En examinant le code, je suis tombé sur un fichier de 4 000 lettres LOC. Il est apparu que l'auteur de ce code avait simplement copié-collé la même méthode de 15 lignes des centaines de fois, en modifiant légèrement les noms des variables et de la méthode appelée. Une simple expression régulière permettait de couper le fichier de 4 000 à 500 LOC, en ajoutant simplement quelques génériques; Je suis à peu près sûr qu'avec une refactorisation plus intelligente, cette classe peut être réduite à quelques dizaines de lignes.
En utilisant des régions, l'auteur s'est encouragé à ignorer le fait que le code est impossible à maintenir et mal écrit, et à dupliquer fortement le code au lieu de le refactoriser.
Région “Do A”, Région “Do B”:
Un autre excellent exemple était une méthode d’initialisation de monstre qui consistait simplement à exécuter la tâche 1, puis la tâche 2, puis la tâche 3, etc. Il y avait cinq ou six tâches totalement indépendantes, chacune initialisant quelque chose dans une classe conteneur. Toutes ces tâches ont été regroupées dans une méthode et regroupées par régions.
Cela avait un avantage:
- La méthode était assez claire pour comprendre en regardant les noms de région. Ceci étant dit, la même méthode une fois refactorisée serait aussi claire que l'originale.
Les problèmes, en revanche, étaient multiples:
Ce n'était pas évident s'il y avait des dépendances entre les régions. Espérons qu’il n’ya pas eu de réutilisation des variables; sinon, l'entretien pourrait être un cauchemar encore plus.
La méthode était presque impossible à tester. Comment sauriez-vous facilement si la méthode qui fait vingt choses à la fois les fait correctement?
Champs région, région des propriétés, région du constructeur:
Le code révisé contenait également de nombreuses régions regroupant tous les champs, toutes les propriétés, etc. Cela posait un problème évident: la croissance du code source.
Lorsque vous ouvrez un fichier et voyez une liste énorme de champs, vous êtes plus enclin à refactoriser la classe d'abord, puis à travailler avec du code. Avec les régions, vous prenez l'habitude de faire disparaître des choses et de les oublier.
Un autre problème est que si vous le faites partout, vous allez créer des régions d'un bloc, ce qui n'a aucun sens. C'était en fait le cas dans le code que j'ai examiné, où il y avait beaucoup de #region Constructor
contenant un constructeur.
Enfin, les champs, propriétés, constructeurs, etc. devraient déjà être en ordre . S'ils le sont et s'ils correspondent aux conventions (constantes commençant par une lettre majuscule, etc.), il est déjà clair où le type d'éléments s'arrête et où d'autres commence. Vous n'avez donc pas besoin de créer explicitement des régions pour cela.