web-dev-qa-db-fra.com

Refactoring une méthode longue basée sur un grand nombre de cas de commutation

Nous utilisons Java comme langue de développement de backend.

Un an de retour, nous avons écrit une méthode qui utilise des cas de commutation basés sur des valeurs ENUMS. Étant donné que nous ajoutons continuellement des membres Enum et selon l'ajout de cas dans la méthode, la méthode a grandi dans une très large mesure. Actuellement, nous avons environ 100 champs d'énormes et le nombre correspondant de cas de commutation.f.fr.

 class AClass{

   enum option{ o1, o2, o3...on}

   Value method(Option o){

      switch(o){
        case o1:
          value = deriveValue(p1,p2,p3);
        case o2:
          value = deriveValue(p2,p3,p4);
        .
        .
        .
        case on:
          value = deriveValue(p1,p2,p3);
      }
   } 
 }

Ainsi, chaque fois qu'une entreprise est une exigence d'activité, nous ajoutons l'énumération et l'étui de commutation correspondant. Maintenant, la méthode est devenue trop longue et a l'air ingérable, si nous continuons à ajouter la même logique à l'avenir.

Pour nettoyer, nous avons pensé à remplacer un étui de commutation avec polymorphisme en créant des classes pour la même chose, mais encore une fois, nous finirons par créer N nombre de classes.

Nous recherchons une petite solution simple et gérable.

----------------- Mettre à jour ---------------------

Comme suggéré, pour élaborer plus, les valeurs ENUM sont des noms de champs pour lesquels un client a besoin d'une valeur. Ainsi, si un client a besoin de la valeur d'un nouveau champ, nous ajoutons le champ dans Enum et la définition correspondante de la vérification de la valeur pour le champ nouvellement ajouté, en ajoutant le boîtier de commutation correspondant.

Dans le boîtier de commutation, nous avons une méthode courante réutilisable par ex. DERIVEVALUE () (veuillez vous reporter à l'exemple donné) à laquelle nous passons des paramètres requis pour dériver la valeur du champ nouvellement ajouté.

16
Free Coder

Qu'essayez-vous d'atteindre? Avez-vous de la difficulté à comprendre et à maintenir la structure de code que vous avez ou essayez-vous simplement de suivre une idée abstraite de "bonnes pratiques" qui dit que des méthodes doivent être courtes?

Cela me semble que si la solution que vous avez est assez adéquate pour les exigences que vous avez données. S'il y a vraiment des centaines de calculs similaires pouvant être effectués par une seule ligne de code, créant ainsi des centaines de sous-classes avec une seule petite méthode n'ayant pas nécessairement mieux que simplement énumérer toutes ces règles parallèles au même endroit. (Le problème avec de longues méthodes est qu'ils deviendront répétitifs, incohérents et donc déroutants. Mais pour énumérer de nombreux cas parallèles qui doivent être manipulés, une longue liste est parfaitement bien.)

31
Kilian Foth

Vous pouvez créer une table. La première colonne sera la valeur du commutateur et les autres colonnes seront les arguments passés sur la méthode DeriveValue. Par exemple

table OPTIONS
OPTION -  ARGUMENT1 -  ARGUMENT2 -  ARGUMENT3
o1          p1          p2          p3
o2          p2          p3          p4
on          p1          p2          p3

Vous pouvez ensuite écrire une fonction générale qui utilise le tableau ci-dessus pour produire l'appel de fonction approprié en fonction de l'option.

18
DesignerAnalyst

Vous pouvez utiliser des méthodes abstraites dans l'énum, ​​ayant une meilleure localité de code (pour une meilleure lisibilité et une meilleure maintenance, sans parler de performance ici).

class AClass{
    enum Option{
        o1 {
            @Override
            Value deriveValue() {
                return deriveValue(p1, p2, p3);
            }
        }, ... on {
            @Override
            Value deriveValue() {
                return deriveValue(p1, p2, p3);
            }
        };
        abstract Value deriveValue();
    }

    Value method(Option o){
        return o.deriveValue();
    }
}
11
cadrian

Il convient de noter que, si vous avez ce genre de situation où il n'y a qu'une seule fonction dans le système qui effectuera toujours de telles vérifications, et il n'a pas besoin de l'ouverture de l'extension externe, puis de lancer une solution polymorphe à celui-ci. Peut réellement vous laisser constater que vos coûts de maintenance quotidienne ont effectivement augmenté plutôt que diminué.

Ici, à l'exclusion d'un besoin d'extension ouverte, la différence pratique entre polymorphes et les conditionnels peut réduire si vous préférez plus de code centralisé, instable ou plus décentralisé et plus stable.

Un code plus décentralisé et stable pourrait effectivement être favorable dans certains cas si cela conduit à moins de contrôle de la version, par exemple. Un code plus centralisé et instable ici aurait tendance à être plus court, si seulement à la suite d'éviter le plafond associé à la définition de nouvelles classes pour chaque petit cas simple.

Actuellement, nous avons environ 100 champs d'énormes et le nombre correspondant de cas de commutation.f.fr. Ainsi, chaque fois qu'une entreprise est une exigence d'activité, nous ajoutons l'énumération et l'étui de commutation correspondant.

Cela me ressemble le problème si nous pouvons l'éviter. N'y a-t-il aucune sorte de catégorisation qui peut accompagner tant de champs d'énumération? Avec le type d'exemple ordinal que vous avez donné, cela ne trouverait pas une catégorisation beaucoup significative. J'espère que ce n'était qu'un analogique.

Si vous pouvez trouver une sorte de catégorisation significative pour organiser ces champs Enum, vous pouvez potentiellement utiliser plus d'une fonction. Chaque fonction (toujours assez grossière) pourrait gérer une plage des cas, avec une vérification de la fonction externe qui fonctionne à appeler sur la plage.

C'est une solution simple et non si intrusive, à condition que vous puissiez trouver une sorte de catégorisation, pour avoir encore une solution assez centralisée et simple, mais laissez des fonctions stables pour la gamme d'options que vous avez déjà traitées. Il réduira également l'instabilité des implémentations de fonction impliquées, car moins d'options qu'ils gèrent, les moins de raisons qu'ils trouveront à des modifications individuelles.

Vous pouvez également utiliser potentiellement des solutions de table de recherche éventuellement sans sous-type impliqué pour les 100 options, ce qui vous donne un endroit centralisé pour modifier mais éventuellement avec une syntaxe éventuellement plus préférable. Cela dépend de la nature de p1, p2, ..., pn. Par exemple, s'ils sont homogènes, vous pourrez peut-être utiliser un tableau et faire une lutterie basée sur l'index de commande avec option Enum champs modélisant un index dans le lut. Exemple (pseudocode):

lut[o1] = {0, 1, 2};
lut[o2] = {1, 2, 3};
...
lut[on] = {0, 1, 2};

Value method(Option o){
    int i1 = lut[o][0];
    int i2 = lut[o][1];
    int i3 = lut[o][2];
    deriveValue(p[i1], p[i2], p[i3]);
}

Cela ne fait pas vraiment beaucoup d'architecture (bien qu'il soit ouvert à l'extension si vous autorisez des entrées lut à partir de l'extérieur), vous pouvez préférer la syntaxe. Il s'applique si votre solution n'était pas une analogie à distance et appelle en réalité deriveValue en fonction de l'option avec une commande différente des arguments (auquel cas je penserais qu'il y aurait au moins 3 paramètres en réalité. appeler pour tant d'options).

1
user204677

Vous pouvez appliquer la stratégie générale utilisée pour traiter de gros chiffres - structure. Peut-être que le nombre prolifique d'options gérées uniformément est un signe de trop petite hiérarchie? Peut-être que les cas tombent parfaitement en groupes qui semblent appartenir ensemble (comme des E/S, la manipulation, le calcul, etc.)? (Perhap pas - alors s'il vous plaît ignorer ;-)).

Cela pourrait être reflété avec un système d'énumération hiérarchique. One Enum caractérise le groupe, la prochaine opération spécifique.

Les groupes pourraient être codés séparément et seraient chacun plus gérables. Les avoir dans différents fichiers/classes de source permet également d'éditer des erreurs dans des endroits non liés moins probables, peut accélérer la compilation et réduire les dépendances (à partir de bibliothèques système ainsi que d'autres classes personnalisées).

Bien sûr, cette approche est également concevable comme une idée de conception du polymorphpisme: on pourrait avoir des classes d'option/action spécifiques provenant de classes de groupe découlant d'une classe d'options génériques), la possibilité de regrouper le code commun supplémentaire dans la hiérarchie.

Changer en classe ne vous aidera pas. En termes de clarté, de robustesse, de compacité, de maintenabilité et de performance, il n'est pas meilleur que l'énumération et une déclaration de commutation. Ce que vous devriez vous demander, c'est si vous traitez des types du tout. Considérant que vous êtes fréquemment et continuellement enums, il me semble que vous avez affaire à des données, pas de types. Vous pourriez avoir besoin d'une seule classe et de plusieurs instances. C'est ce que la conceptionAnalyste implique avec la proposition de la table. Vous pouvez toujours coder vos données d'entrée pour vos instances d'objet ou gifler l'entrée dans un fichier texte, la grande question reste: Quelles sont ces valeurs d'énumérum vraiment? Comment se classent-ils?

1
Martin Maat

Ajout de plus en plus de classes n'est pas vraiment un problème, s'ils font partie d'une structure bien définie qui vous permet de les ignorer lorsque vous souhaitez: les mettre dans un module et n'accédez que le module lorsque vous devez le modifier.

IMHO, chaque option représente un moyen de calculer une valeur, donc dans OO, chaque opinion doit être représentée par un objet ayant une implémentation spécifique de DERIVEVALUE (). En Java, cela signifie que chaque objet doit avoir une classe spécifique définissant la mise en œuvre. Donc, votre code devient quelque chose comme:

Dans votre package d'option:

public interface Option {
     Value deriveValue(? p1, ? p2, ? p3);
}

public FirstOption implements Option {
    Value deriveValue(? p1, ? p2, ? p3){ ... } 
}

Dans un autre paquet;

class AClass{
   private Option o;

   Value method(){ o.deriveValue(p1,p2,p3); }
}

Si vous avez des comportements différents pour spécifier, chacun d'entre eux peut être représenté par un seul appel au lieu d'un grossieur. CAVEAT: Chaque fois que vous souhaitez ajouter un nouveau comportement, vous devez ouvrir chaque classe d'option pour ajouter une nouvelle méthode.

0
mgoeminne