Évitez les méthodes trop complexes - Complexité cyclomatique

23

Je ne sais pas comment procéder pour réduire la complexité cyclomatique. Sonar rapporte 13 alors que 10 est attendu. Je suis sûr que rien de mal à laisser cette méthode car elle me met au défi de respecter la règle de Sonar. Toute réflexion serait grandement appréciée.

 public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        long millis;
        if (sValue.endsWith("S")) {
            millis = new ExtractSecond(sValue).invoke();
        } else if (sValue.endsWith("ms")) {
            millis = new ExtractMillisecond(sValue).invoke();
        } else if (sValue.endsWith("s")) {
            millis = new ExtractInSecond(sValue).invoke();
        } else if (sValue.endsWith("m")) {
            millis = new ExtractInMinute(sValue).invoke();
        } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
            millis = new ExtractHour(sValue).invoke();
        } else if (sValue.endsWith("d")) {
            millis = new ExtractDay(sValue).invoke();
        } else if (sValue.endsWith("w")) {
            millis = new ExtractWeek(sValue).invoke();
        } else {
            millis = Long.parseLong(sValue);
        }

        return millis;

    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

Toutes les méthodes ExtractXXX sont définies comme des staticclasses internes. Par exemple, comme celui ci-dessous -

    private static class ExtractHour {
      private String sValue;

      public ExtractHour(String sValue) {
         this.sValue = sValue;
      }

      public long invoke() {
         long millis;
         millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
         return millis;
     }
 }

MISE À JOUR 1

Je vais m'installer avec un mélange de suggestions ici pour satisfaire le gars Sonar. Certainement place pour des améliorations et simplification.

La goyave Functionn'est qu'une cérémonie indésirable ici. Je voulais mettre à jour la question sur l'état actuel. Rien n'est définitif ici. Donnez votre avis s'il vous plaît ..

public class DurationParse {

private static final Logger LOGGER = LoggerFactory.getLogger(DurationParse.class);
private static final Map<String, Function<String, Long>> MULTIPLIERS;
private static final Pattern STRING_REGEX = Pattern.compile("^(\\d+)\\s*(\\w+)");

static {

    MULTIPLIERS = new HashMap<>(7);

    MULTIPLIERS.put("S", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("s", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInSecond(input).invoke();
        }
    });

    MULTIPLIERS.put("ms", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractMillisecond(input).invoke();
        }
    });

    MULTIPLIERS.put("m", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractInMinute(input).invoke();
        }
    });

    MULTIPLIERS.put("H", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractHour(input).invoke();
        }
    });

    MULTIPLIERS.put("d", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractDay(input).invoke();
        }
    });

    MULTIPLIERS.put("w", new Function<String, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable String input) {
            return new ExtractWeek(input).invoke();
        }
    });

}

public static long parseTimeValue(String sValue) {

    if (isNullOrEmpty(sValue)) {
        return 0;
    }

    Matcher matcher = STRING_REGEX.matcher(sValue.trim());

    if (!matcher.matches()) {
        LOGGER.warn(String.format("%s is invalid duration, assuming 0ms", sValue));
        return 0;
    }

    if (MULTIPLIERS.get(matcher.group(2)) == null) {
        LOGGER.warn(String.format("%s is invalid configuration, assuming 0ms", sValue));
        return 0;
    }

    return MULTIPLIERS.get(matcher.group(2)).apply(matcher.group(1));
}

private static class ExtractSecond {
    private String sValue;

    public ExtractSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = Long.parseLong(sValue);
        return millis;
    }
}

private static class ExtractMillisecond {
    private String sValue;

    public ExtractMillisecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue));
        return millis;
    }
}

private static class ExtractInSecond {
    private String sValue;

    public ExtractInSecond(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 1000);
        return millis;
    }
}

private static class ExtractInMinute {
    private String sValue;

    public ExtractInMinute(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 1000);
        return millis;
    }
}

private static class ExtractHour {
    private String sValue;

    public ExtractHour(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractDay {
    private String sValue;

    public ExtractDay(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 24 * 60 * 60 * 1000);
        return millis;
    }
}

private static class ExtractWeek {
    private String sValue;

    public ExtractWeek(String sValue) {
        this.sValue = sValue;
    }

    public long invoke() {
        long millis;
        millis = (long) (Double.parseDouble(sValue) * 7 * 24 * 60 * 60 * 1000);
        return millis;
    }
}

}


MISE À JOUR 2

Bien que j'aie ajouté ma mise à jour, cela ne vaut que le temps. Je vais continuer car Sonar ne se plaint plus. Ne vous inquiétez pas beaucoup et j'accepte la réponse de mattnz car c'est la voie à suivre et je ne veux pas donner le mauvais exemple à ceux qui se heurtent à cette question. Bottom line - Ne pas trop ingénieur pour le bien de Sonar (ou Half Baked Project Manager) se plaint de CC. Faites juste ce qui vaut un sou pour le projet. Merci à tous.

asyncwait
la source
4
Réponse OO la plus simple: private static Dictionary<string,Func<string,long>> _mappingStringToParser;je vais laisser le reste comme un exercice pour vous (ou quelqu'un avec plus de temps libre que moi). Il y a une API plus propre à trouver si vous avez une certaine familiarité avec les analyseurs monadiques, mais je ne vais pas y aller tout de suite ..
Jimmy Hoffa
J'adorerais si vous pouviez épargner quelque temps sur les "analyseurs monadiques" et comment cela peut être appliqué à une fonction assez petite comme celle-ci. Et, ce morceau de code est de Java.
asyncwait
comment les ExtractBlahclasses sont-elles définies? viennent-ils d'une bibliothèque ou d'un homebrew?
moucher
4
Le code annexé m'amène un peu plus loin vers une implémentation plus simple: votre variance réelle est le multiplicateur. Créez une carte de ceux-ci: tirez les caractères alpha de la fin de votre sValue, utilisez-les comme clé, puis tirez tous jusqu'à ce que les alphas de l'avant pour la valeur que vous multipliez par le multiplicateur mappé.
Jimmy Hoffa
2
Re Update: Suis-je le seul à avoir "Over Engineered" dans ma tête?
mattnz

Réponses:

46

Réponse en génie logiciel:

Ce n'est là qu'un des nombreux cas où le simple fait de compter des haricots simples à compter vous fera faire la mauvaise chose. Ce n'est pas une fonction complexe, ne la changez pas. La complexité cyclomatique n'est qu'un guide de la complexité, et vous l'utilisez mal si vous modifiez cette fonction en fonction de celle-ci. Son simple, son lisible, son maintenable (pour l'instant), s'il grossit à l'avenir le CC montera en flèche de façon exponentielle et il obtiendra l'attention dont il a besoin quand il en a besoin, pas avant.

Minion travaillant pour une grande multinationale Réponse:

Les organisations sont remplies d'équipes de compteurs de haricots surpayées et improductives. Garder les compteurs de haricots heureux est plus facile, et certainement plus sage, que de faire la bonne chose. Vous devez changer la routine pour ramener le CC à 10, mais soyez honnête sur la raison pour laquelle vous le faites - pour garder les compteurs de haricots loin de votre dos. Comme suggéré dans les commentaires - des "analyseurs monadiques" pourraient aider

mattnz
la source
14
+1 pour avoir respecté la raison de la règle, pas la règle elle
Radu Murzea
5
Bien dit! Cependant, dans d'autres cas où vous avez CC = 13 avec plusieurs niveaux d'imbrication et une logique compliquée (de branchement), il serait préférable d'essayer au moins de le simplifier.
grizwako
16

Merci à @JimmyHoffa, @MichaelT et @ GlenH7 pour leur aide!

Python

Tout d'abord, vous ne devriez vraiment accepter que les préfixes connus, c'est-à-dire «H» ou «h». Si vous devez accepter les deux, vous devez effectuer certaines opérations pour rendre cohérent la sauvegarde de l'espace sur votre carte.

En python, vous pouvez créer un dictionnaire.

EXTRACTION_MAP = {
    'S': ExtractSecond,
    'ms': ExtractMillisecond,
    'm': ExtractMinute,
    'H': ExtractHour,
    'd': ExtractDay,
    'w': ExtractWeek
}

Ensuite, nous voulons que la méthode utilise ceci:

def parseTimeValue(sValue)
    ending = ''.join([i for i in sValue if not i.isdigit()])
    return EXTRACTION_MAP[ending](sValue).invoke()

Devrait avoir une meilleure complexité cyclomatique.


Java

Nous n'avons besoin que d'un (un) de chaque multiplicateur. Permet de les mettre sur une carte comme l'ont suggéré d'autres réponses.

Map<String, Float> multipliers = new HashMap<String, Float>();
    map.put("S", 60 * 60);
    map.put("ms", 60 * 60 * 1000);
    map.put("m", 60);
    map.put("H", 1);
    map.put("d", 1.0 / 24);
    map.put("w", 1.0 / (24 * 7));

Ensuite, nous pouvons simplement utiliser la carte pour saisir le bon convertisseur

Pattern foo = Pattern.compile(".*(\\d+)\\s*(\\w+)");
Matcher bar = foo.matcher(sValue);
if(bar.matches()) {
    return (long) (Double.parseDouble(bar.group(1)) * multipliers.get(bar.group(2);
}
Ampt
la source
Oui, mapper les chaînes à un facteur de conversion serait une solution beaucoup plus simple. S'ils n'ont pas besoin des objets, alors ils devraient s'en débarrasser, mais je ne peux pas voir le reste du programme, alors peut-être que ces objets sont plus utilisés comme objets ailleurs ...?
FrustratedWithFormsDesigner
@FrustratedWithFormsDesigner ils le peuvent, mais dans le cadre de cette méthode, son retour juste un long et l'objet instancié tombe hors de portée. En passant, cela a pour effet secondaire que si ce code est appelé plus fréquemment, le nombre d'objets de courte durée fréquemment utilisés sans état est réduit.
Aucune de ces réponses ne peut être considérée comme fournissant la même solution que le programme d'origine car elles reposent sur des hypothèses qui peuvent ne pas être valides. Code Java: Comment êtes-vous certain que la seule chose que les méthodes font est d'appliquer un multiplicateur? Code Python: Comment êtes-vous certain que la chaîne n'est pas autorisée à contenir des caractères de début (ou milieu) autres que des chiffres (par exemple "123.456s").
mattnz
@mattnz - jetez un autre coup d'œil aux noms des variables dans les exemples fournis. Il est clair que l'OP reçoit une unité de temps sous forme de chaîne et doit ensuite la convertir en un autre type d'unité de temps. Les exemples fournis dans cette réponse concernent donc directement le domaine du PO. Ignorant cet aspect, la réponse fournit toujours une approche générique qui pourrait être utilisée pour un domaine différent. Cette réponse résout le problème qui a été présenté pas le problème qui peut avoir été présenté.
5
@mattnz - 1) OP ne spécifie jamais cela dans leur exemple et peut ne pas s'en soucier. Comment savez-vous que les hypothèses sont invalides? 2) La méthode générale fonctionnerait toujours, nécessitant potentiellement une expression régulière plus compliquée. 3) Le but de la réponse est de fournir une voie conceptuelle pour résoudre la complexité cyclomatique, et pas nécessairement une réponse spécifique et compilable. 4) bien que cette réponse ignore l'aspect plus large de «la complexité importe-t-elle», elle répond indirectement au problème en montrant une forme alternative pour le code.
3

Étant donné que vous return millisà la fin de cette horrible ifelseifelse de toute façon, la première chose qui vous vient à l'esprit est de renvoyer la valeur immédiatement depuis l'intérieur des if-blocks. Cette approche suit une qui est répertoriée dans le catalogue des modèles de refactorisation comme Remplacer le conditionnel imbriqué par des clauses de garde .

Une méthode a un comportement conditionnel qui ne précise pas quel est le chemin normal d'exécution

Utilisez des clauses de garde pour tous les cas spéciaux

Cela vous aidera à vous débarrasser des autres, à aplatir le code et à rendre Sonar heureux:

    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } // no need in else after return, code flattened

    if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    }

    // and so on...
    return Long.parseLong(sValue); // forget millis, these aren't needed anymore

Une autre chose à considérer est de supprimer le bloc try-catch. Cela réduira également la complexité cyclomatique, mais la principale raison pour laquelle je recommande c'est qu'avec ce bloc, il n'y ait aucun moyen pour le code de l'appelant de distinguer légalement l'analyse 0 de l'exception de format numérique.

À moins que vous ne soyez sûr à 200% que le retour à 0 pour les erreurs d'analyse est ce dont le code de l'appelant a besoin, vous feriez mieux de propager cette exception et de laisser le code de l'appelant décider comment la traiter. Il est généralement plus pratique de décider à l'appelant s'il faut interrompre l'exécution ou réessayer d'obtenir l'entrée, ou revenir à une valeur par défaut comme 0 ou -1 ou autre.


Votre extrait de code pour un exemple ExtractHour me fait sentir que la fonctionnalité ExtractXXX est conçue d'une manière loin d'être optimale. Je parie que toutes les autres classes inconsidérément répète même parseDoubleet substring, et la multiplication des choses comme 60 et 1000 et encore et encore.

C'est parce que vous avez manqué l' essentiel de ce qui doit être fait en fonction sValue- à savoir, il définit la quantité de caractères à couper à la fin de la chaîne et quelle serait la valeur du multiplicateur. Si vous concevez votre objet "central" autour de ces fonctionnalités essentielles, il ressemblerait à ceci:

private static class ParseHelper {
    // three things you need to know to parse:
    final String source;
    final int charsToCutAtEnd;
    final long multiplier;

    ParseHelper(String source, int charsToCutAtEnd, long multiplier) {
        this.source = source == null ? "0" : source; // let's handle null here
        this.charsToCutAtEnd = charsToCutAtEnd;
        this.multiplier = multiplier;
    }

    long invoke() {
        // NOTE consider Long.parseLong instead of Double.parseDouble here
        return (long) (Double.parseDouble(cutAtEnd()) * multiplier);
    }

    private String cutAtEnd() {
        if (charsToCutAtEnd == 0) {
            return source;
        }
        // write code that cuts 'charsToCutAtEnd' from the end of the 'source'
        throw new UnsupportedOperationException();
    }
}

Après cela, vous auriez besoin d'un code qui configure les objets ci-dessus par condition particulière si elle est remplie, ou "contourne" autrement. Cela pourrait se faire comme suit:

private ParseHelper setupIfInSecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("s") && !sValue.endsWith("ms")
            ? new ParseHelper(sValue, 1, 1000)
            :  original; // bypass
}

private ParseHelper setupIfMillisecond(ParseHelper original) {
    final String sValue = original.source;
    return sValue.endsWith("ms")
            ? new ParseHelper(sValue, 2, 1)
            : original; // bypass
}

// and so on...

Basé sur les blocs de construction ci-dessus , le code de votre méthode pourrait ressembler à ceci:

public long parseTimeValue(String sValue) {

   return setupIfSecond(
           setupIfMillisecond(
           setupIfInSecond(
           setupIfInMinute(
           setupIfHour(
           setupIfDay(
           setupIfWeek(
           new ParseHelper(sValue, 0, 1))))))))
           .invoke();
}

Vous voyez, il n'y a plus de complexité, pas d'accolades à l'intérieur de la méthode (ni de retours multiples comme dans ma suggestion initiale de force brute sur l'aplatissement du code). Il vous suffit de vérifier séquentiellement l'entrée et d'ajuster le traitement selon vos besoins.

moucheron
la source
1

Si vous voulez vraiment le refactoriser, vous pouvez faire quelque chose comme ceci:

// All of your Extract... classes will have to implement this interface!
public Interface TimeExtractor
{
    public long invoke();
}

private static class ExtractHour implements TimeExtractor
{
  private String sValue;


  /*Not sure what this was for - might not be necessary now
  public ExtractHour(String sValue)
  {
     this.sValue = sValue;
  }*/

  public long invoke(String s)
  {
     this.sValue = s;
     long millis;
     millis = (long) (Double.parseDouble(sValue.substring(0, sValue.length() - 1)) * 60 * 60 * 1000);
     return millis;
 }
}

private static HashMap<String, TimeExtractor> extractorMap= new HashMap<String, TimeExtractor>();

private void someInitMethod()
{
   ExtractHour eh = new ExtractorHour;
   extractorMap.add("H",eh);
   /*repeat for all extractors */
}

public static long parseTimeValue(String sValue)
{
    if (sValue == null)
    {
        return 0;
    }
    String key = extractKeyFromSValue(sValue);
    long millis;
    TimeExtractor extractor = extractorMap.get(key);
    if (extractor!=null)
    {
      try
      {
         millis= extractor.invoke(sValue);
      }
        catch (NumberFormatException e)
      {
          LOGGER.warn("Number format exception", e);
      }
    }
    else
       LOGGER.error("NO EXTRACTOR FOUND FOR "+key+", with sValue: "+sValue);

    return millis;
}

L'idée est que vous ayez une carte de clés (ce que vous utilisez dans "se termine avec" tout le temps) qui correspondent à des objets spécifiques qui effectuent le traitement que vous souhaitez.

C'est un peu rude ici mais j'espère que c'est assez clair. Je n'ai pas rempli les détails extractKeyFromSValue()parce que je ne connais pas suffisamment ces chaînes pour le faire correctement. Il semble que ce soient les 1 ou 2 derniers caractères non numériques (une expression régulière pourrait probablement l'extraire assez facilement, peut .*([a-zA-Z]{1,2})$- être que cela fonctionnerait), mais je ne suis pas sûr à 100% ...


Réponse originale:

Tu pourrais changer

else if (sValue.endsWith("H") || sValue.endsWith("h")) {

à

else if (sValue.toUpper().endsWith("H")) {

Cela pourrait vous faire économiser un peu, mais honnêtement, je ne m'en inquiéterais pas trop. Je suis d'accord avec vous que je ne pense pas qu'il y ait beaucoup de mal à laisser la méthode telle qu'elle est. Au lieu d'essayer d '"obéir aux règles du Sonar", essayez de "rester proche des directives du Sonar, autant qu'il est raisonnablement possible".

Vous pouvez vous rendre fou en essayant de suivre toutes les règles que ces outils d'analyse auront en eux, mais vous devez également décider si les règles ont du sens pour votre projet et pour des cas spécifiques où le temps passé à la refactorisation pourrait ne pas en valoir la peine .

FrustratedWithFormsDesigner
la source
3
Pas très utile, le sonar se plaint encore. Je suis en train de l'essayer pour le plaisir, au moins permet d'en apprendre un ou deux. Le code est cependant déplacé vers la mise en scène.
asyncwait
@asyncwait: Ah, je pensais que vous preniez ce rapport du sonar plus au sérieux que cela. Oui, le changement que j'ai suggéré ne ferait pas une énorme différence - je ne pense pas que cela vous prendrait de 13 à 10, mais dans certains outils, j'ai vu ce genre de chose faire une différence quelque peu perceptible.
FrustratedWithFormsDesigner
L'ajout d'une autre branche IF a augmenté CC à +1.
asyncwait
0

Vous pouvez envisager d'utiliser une énumération pour stocker tous vos cas et prédicats disponibles pour les valeurs correspondantes. Comme mentionné précédemment, votre fonction est suffisamment lisible pour ne pas la modifier. Ces mesures sont là pour vous aider et non l'inverse.

//utility class for matching values
private static class ValueMatchingPredicate implements Predicate<String>{
    private final String[] suffixes;

    public ValueMatchingPredicate(String[] suffixes) {      
        this.suffixes = suffixes;
    }

    public boolean apply(String sValue) {
        if(sValue == null) return false;

        for (String suffix : suffixes) {
            if(sValue.endsWith(suffix)) return true;
        }

        return false;
    }

    public static Predicate<String> withSuffix(String... suffixes){         
        return new ValueMatchingPredicate(suffixes);
    }       
}

//enum containing all possible options
private static enum TimeValueExtractor {                
    SECOND(
        ValueMatchingPredicate.withSuffix("S"), 
        new Function<String, Long>(){ 
            public Long apply(String sValue) {  return new ExtractSecond(sValue).invoke(); }
        }),

    MILISECOND(
        ValueMatchingPredicate.withSuffix("ms"), 
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractMillisecond(sValue).invoke(); }
        }),

    IN_SECOND(
        ValueMatchingPredicate.withSuffix("s"),
        new Function<String, Long>(){
            public Long apply(String sValue) { return new ExtractInSecond(sValue).invoke(); }
        }),

    IN_MINUTE(
        ValueMatchingPredicate.withSuffix("m"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractInMinute(sValue).invoke(); }
        }),

    HOUR(
        ValueMatchingPredicate.withSuffix("H", "h"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractHour(sValue).invoke(); }
        }),

    DAY(
        ValueMatchingPredicate.withSuffix("d"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractDay(sValue).invoke(); }
        }),

    WEEK(
        ValueMatchingPredicate.withSuffix("w"),
        new Function<String, Long>(){
            public Long apply(String sValue) {  return new ExtractWeek(sValue).invoke(); }
        });

    private final Predicate<String>      valueMatchingRule;
    private final Function<String, Long> extractorFunction;

    public static Long DEFAULT_VALUE = 0L;

    private TimeValueExtractor(Predicate<String> valueMatchingRule, Function<String, Long> extractorFunction) {
        this.valueMatchingRule = valueMatchingRule;
        this.extractorFunction = extractorFunction;
    }

    public boolean matchesValueSuffix(String sValue){
        return this.valueMatchingRule.apply(sValue);
    }

    public Long extractTimeValue(String sValue){
        return this.extractorFunction.apply(sValue);
    }

    public static Long extract(String sValue) throws NumberFormatException{
        TimeValueExtractor[] extractors = TimeValueExtractor.values();

        for (TimeValueExtractor timeValueExtractor : extractors) {
            if(timeValueExtractor.matchesValueSuffix(sValue)){
                return timeValueExtractor.extractTimeValue(sValue);
            }
        }

        return DEFAULT_VALUE;
    }
}

//your function
public static long parseTimeValue(String sValue){
    try{
        return TimeValueExtractor.extract(sValue);
    } catch (NumberFormatException e) {
        //LOGGER.warn("Number format exception", e);
        return TimeValueExtractor.DEFAULT_VALUE;
    }
}
Gwozdziu
la source
0

Relatif à votre commentaire de:

Bottom line - Ne pas trop ingénieur pour le bien de Sonar (ou Half Baked Project Manager) se plaint de CC. Faites juste ce qui vaut un sou pour le projet.

Une autre option à considérer est de modifier les normes de codage de votre équipe pour des situations comme celle-ci. Vous pouvez peut-être ajouter une sorte de vote d'équipe pour fournir une mesure de la gouvernance et éviter les situations de raccourci.

Mais changer les normes de l'équipe en réponse à des situations qui n'ont pas de sens est le signe d'une bonne équipe avec la bonne attitude à l'égard des normes. Les normes sont là pour aider l'équipe, pas pour gêner l'écriture de code.


la source
0

Pour être honnête, toutes les réponses techniques ci-dessus semblent terriblement compliquées pour la tâche à accomplir. Comme cela a déjà été écrit, le code lui-même est propre et bon, donc j'opterais pour le plus petit changement possible pour satisfaire le compteur de complexité. Que diriez-vous de la refactorisation suivante:

public static long parseTimeValue(String sValue) {

    if (sValue == null) {
        return 0;
    }

    try {
        return getMillis(sValue);
    } catch (NumberFormatException e) {
        LOGGER.warn("Number format exception", e);
    }

    return 0;
}

private static long getMillis(String sValue) {
    if (sValue.endsWith("S")) {
        return new ExtractSecond(sValue).invoke();
    } else if (sValue.endsWith("ms")) {
        return new ExtractMillisecond(sValue).invoke();
    } else if (sValue.endsWith("s")) {
        return new ExtractInSecond(sValue).invoke();
    } else if (sValue.endsWith("m")) {
        return new ExtractInMinute(sValue).invoke();
    } else if (sValue.endsWith("H") || sValue.endsWith("h")) {
        return new ExtractHour(sValue).invoke();
    } else if (sValue.endsWith("d")) {
        return new ExtractDay(sValue).invoke();
    } else if (sValue.endsWith("w")) {
        return new ExtractWeek(sValue).invoke();
    } else {
        return Long.parseLong(sValue);
    }
}

Si je compte correctement, la fonction extraite devrait avoir une complexité de 9, ce qui répond toujours aux exigences. Et c'est essentiellement le même code qu'avant , ce qui est une bonne chose, car le code était bon au départ.

De plus, les lecteurs de Clean Code pourraient apprécier le fait que la méthode de niveau supérieur est maintenant simple et courte, tandis que celle extraite traite des détails.

Arides
la source