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 static
classes 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 Function
n'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.
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 ..ExtractBlah
classes sont-elles définies? viennent-ils d'une bibliothèque ou d'un homebrew?Réponses:
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
la source
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.
Ensuite, nous voulons que la méthode utilise ceci:
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.
Ensuite, nous pouvons simplement utiliser la carte pour saisir le bon convertisseur
la source
É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 .Cela vous aidera à vous débarrasser des autres, à aplatir le code et à rendre Sonar heureux:
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
parseDouble
etsubstring
, 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: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:
Basé sur les blocs de construction ci-dessus , le code de votre méthode pourrait ressembler à ceci:
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.
la source
Si vous voulez vraiment le refactoriser, vous pouvez faire quelque chose comme ceci:
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
à
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 .
la source
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.
la source
Relatif à votre commentaire de:
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
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:
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.
la source