Interprétation du principe DRY


10

En ce moment, je lutte avec ce concept de SEC (ne vous répétez pas) dans mon codage. Je crée cette fonction dans laquelle je crains qu'elle ne devienne trop complexe mais j'essaie de suivre le principe DRY.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Cette fonction, j'ai dit, prend 3 paramètres d'entrée, puis la fonction fera quelque chose de légèrement différent compte tenu des combinaisons booléennes doesSomethinget doesSomething2. Cependant, le problème que j'ai est que cette fonction augmente considérablement en complexité avec chaque nouveau paramètre booléen ajouté.

Donc ma question est, est-il préférable d'avoir un tas de fonctions différentes qui partagent beaucoup de la même logique (violant donc le principe DRY) ou une fonction qui se comporte légèrement différemment étant donné un certain nombre de paramètres mais en le rendant beaucoup plus complexe (mais préservant SEC)?


3
La logique partagée / commune peut-elle être intégrée dans des fonctions privées que les différentes createTrajectory...fonctions publiques appellent toutes?
FrustratedWithFormsDesigner

Cela pourrait l'être, mais ces fonctions privées auraient encore besoin d'obtenir ces paramètres booléens
Albinoswordfish

2
Je pense que ce serait / sera beaucoup plus facile de répondre étant donné une sorte d'exemple concret. Ma réaction immédiate est que la dichotomie que vous décrivez n'est pas entièrement réelle - c'est-à-dire que ce ne sont pas les deux seuls choix. En passant, je considérerais toute utilisation d'un booleancomme paramètre quelque peu suspect au mieux.
Jerry Coffin


Euh, pourquoi ne tenez-vous pas compte des choses conditionnelles dans leurs propres fonctions?
Rig

Réponses:


19

Les arguments booléens pour déclencher différents chemins de code dans une seule fonction / méthode sont une terrible odeur de code .

Ce que vous faites viole les principes de Couplage Lâche et de Haute Cohésion et de Responsabilité Unique , qui sont beaucoup plus importants que SEC en priorité.

Cela signifie que les choses ne devraient dépendre d'autres choses que lorsqu'elles le doivent ( Couplage ) et qu'elles devraient faire une chose et une seule (très bien) ( Cohésion ).

Par votre propre omission, cela est trop étroitement couplé (tous les drapeaux booléens sont un type de dépendance de l'État, qui est l'un des pires!) Et a trop de responsabilités individuelles entremêlées (trop complexes).

Ce que vous faites n'est pas dans l'esprit de SEC de toute façon. DRY est plus une question de répétition (les Racronymes REPEAT). Éviter de copier et coller est sa forme la plus élémentaire. Ce que vous faites n'est pas lié à la répétition.

Votre problème est que votre décomposition de votre code n'est pas au bon niveau. Si vous pensez que vous aurez du code en double, alors cela devrait être sa propre fonction / méthode qui est paramétrée de manière appropriée, pas copiée et collée, et les autres devraient être nommés de manière descriptive et déléguer à la fonction / méthode principale.


Ok, il semble que la façon dont j'écris n'est pas très bonne (ma suspicion initiale), je ne suis pas vraiment sûr de ce qu'est ce "Sprit of DRY"
Albinoswordfish


4

Le fait que vous passiez des booléens pour que la fonction fasse des choses différentes est une violation du principe de responsabilité unique. Une fonction doit faire une chose. Il ne devrait faire qu'une chose, et il devrait bien le faire.

Cela sent que vous devez le diviser en plusieurs fonctions plus petites avec des noms descriptifs, séparant les chemins de code correspondant aux valeurs de ces booléens.

Une fois que vous avez fait cela, vous devez rechercher du code commun dans les fonctions résultantes et le factoriser dans ses propres fonctions. Selon la complexité de cette chose, vous pouvez même être en mesure de prendre en compte une classe ou deux.

Bien sûr, cela suppose que vous utilisez un système de contrôle de version et que vous disposez d'une bonne suite de tests, afin de pouvoir refactoriser sans craindre de casser quelque chose.


3

Pourquoi ne créez-vous pas une autre fonction contenant toute la logique de votre fonction avant de décider de faire quelque chose ou quelque chose2 et d'avoir ensuite trois fonctions comme:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

Et maintenant, en passant trois mêmes types de paramètres à trois fonctions différentes, vous vous répéterez à nouveau, vous devez donc définir une structure ou une classe contenant A, B, C.

Alternativement, vous pouvez créer une classe contenant les paramètres A, B, C et une liste d'opérations à effectuer. Ajoutez les opérations (quelque chose, quelque chose2) que vous souhaitez effectuer avec ces paramètres (A, B, C) en enregistrant les opérations avec l'objet. Ensuite, disposez d'une méthode pour appeler toutes les opérations enregistrées sur votre objet.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}

Pour tenir compte des combinaisons possibles de valeurs pour les booléens dosomething et dosomething2, vous auriez besoin de 4 fonctions, pas 2, en plus de la fonction createTrajectoryFromPoint de base. Cette approche n'évolue pas bien à mesure que le nombre d'options augmente, et même nommer les fonctions devient fastidieux.
JGWeissman

2

DRY peut être poussé trop loin, il est préférable d'utiliser le principe de responsabilité unique (SRP) conjointement avec DRY. L'ajout de drapeaux booléens à une fonction pour lui faire faire des versions légèrement différentes du même code peut être un signe que vous en faites trop avec une seule fonction. Dans ce cas, je suggère de créer une fonction distincte pour chaque cas que représentent vos indicateurs, puis lorsque vous avez écrit chaque fonction, il devrait être assez évident s'il existe une section commune qui peut être déplacée vers une fonction privée sans passer tous les indicateurs , s'il n'y a pas de section apparente de code, alors vous ne vous répétez vraiment pas, vous avez plusieurs cas différents mais similaires.


1

Je passe généralement plusieurs étapes avec ce problème, m'arrêtant lorsque je ne peux pas comprendre comment aller plus loin.

Tout d'abord, faites ce que vous avez fait. Allez-y avec DRY. Si vous ne vous retrouvez pas avec un gros désordre poilu, vous avez terminé. Si, comme dans votre cas, vous n'avez pas de code en double mais que chaque booléen a sa valeur vérifiée dans 20 endroits différents, passez à l'étape suivante.

Deuxièmement, divisez le code en blocs. Les booléens sont référencés chacun une seule fois (enfin, peut-être parfois deux fois) pour diriger l'exécution vers le bloc de droite. Avec deux booléens, vous vous retrouvez avec quatre blocs. Chaque bloc est presque identique. DRY est parti. Ne faites pas de chaque bloc une méthode distincte. Ce serait plus élégant, mais mettre tout le code dans une seule méthode rend plus facile, voire possible, pour quiconque fait de la maintenance de voir qu'il doit effectuer chaque changement à quatre endroits. Avec un code bien organisé et un grand écran, les différences et les erreurs seront presque évidentes. Vous avez maintenant du code maintenable et il fonctionnera plus rapidement que le désordre emmêlé d'origine.

Troisièmement, essayez de saisir des lignes de code en double dans chacun de vos blocs et de les transformer en méthodes simples et agréables. Parfois, vous ne pouvez rien faire. Parfois, vous ne pouvez pas faire grand-chose. Mais chaque petit geste que vous faites vous ramène vers SEC et rend le code un peu plus facile à suivre et plus sûr à maintenir. Idéalement, votre méthode d'origine pourrait se retrouver sans code en double. À ce stade, vous souhaiterez peut-être le diviser en plusieurs méthodes sans les paramètres booléens ou vous ne le pouvez pas. La commodité du code d'appel est désormais la principale préoccupation.

J'ai ajouté ma réponse au grand nombre déjà ici à cause de la deuxième étape. Je déteste le code en double, mais si c'est le seul moyen intelligible de résoudre un problème, faites-le de telle manière que quiconque sache d'un coup d'œil ce que vous faites. Utilisez plusieurs blocs et une seule méthode. Rendez les blocs aussi identiques que possible dans les noms, l'espacement, les alignements, ... tout. Les différences devraient alors ressortir du lecteur. Il pourrait être évident de savoir comment le réécrire de manière SÈCHE, et sinon, le maintenir sera raisonnablement simple.


0

Une approche alternative consiste à remplacer les paramètres booléens par des paramètres d'interface, avec du code pour gérer les différentes valeurs booléennes refactorisées dans les implémentations d'interface. Vous auriez donc

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

où vous avez des implémentations de IX et IY qui représentent les différentes valeurs pour les booléens. Dans le corps de la fonction, où que vous soyez

if (doesSomething)
{
     ...
}
else
{
     ...
}

vous le remplacez par un appel à une méthode définie sur IX, avec les implémentations contenant les blocs de code omis.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.