[JAVA] Agrégation / couplage / complexité cyclique

Aperçu

Voici quelques conseils pour écrire du code facile à maintenir / tester.

Au fait, le langage est écrit en Java (veuillez lire les autres langages)

En outre, vous pouvez comprendre que le degré de connexion est incorrect. Dans ce cas, veuillez le signaler. (Parce que c'est différent des autres choses écrites par des livres et des sites ... (´ ・ ω ・ `))

Description de chacun

Degré de coagulation

Aussi connu sous le nom de force du module. Une mesure de la quantité de code axée sur la division des responsabilités de la classe. ** Les classes qui n'existent que pour un rôle (toutes les méthodes n'existent que pour ce rôle) sont hautement cohérentes. ** ** Au contraire, les classes qui n'ont pas de points communs dans chaque méthode et jouent différents rôles ont un faible degré d'agrégation.

Quel est le problème avec une faible agglomération

Les classes à faible agglomération ont tendance à avoir de nombreuses variables membres

Lorsque le degré d'agrégation est faible, le nombre de variables membres a tendance à être important. Étant donné que la variable membre est l'état de la classe elle-même, s'il existe de nombreuses variables membres, le modèle de l'état que l'instance peut prendre augmente, la complexité augmente et la maintenabilité est considérablement réduite. Cela rend également les tests plus difficiles et plus de modèles.

Exemple de classe hautement cohésive: classe String

Cette classe n'existe que pour la "gestion des chaînes" et n'a aucun autre rôle. Classe de chaîne

Exemple spécifique

Prenons une classe appelée Price. La classe n'a que des méthodes qui gèrent les données de prix.

public class Price {
   private final int value;
   public Price(int value) {
      this.value = value;
   }
   public Price() {
      this(0);
   }

   /**Ajouter un prix*/
   public Price add(Price other) {
       return new Price(this.value + other.value);
   }

   /**Dépenser le prix*/
   public Price multiply(int amount) {
       return new Price(this.value * amount);
   }

   /**Remise*/
   public Price discount(double percentage) {
      return new Price(this.value * percentage);
   }

   /**Formater et retourner le prix*/
   public String format() {
       return NumberFormat.getCurrencyInstance().format(this.value);
   }

   public void print() {
       System.out.println(this.format());
   }
}

//Côté utilisateur
Price price = new Price(1000);
price.print(); // ¥1,000
System.out.println(price.format()); // ¥1,000
System.out.println(price.add(new Price(500)).format());  // ¥1,500
System.out.println(price.discount(0.9).format()); // ¥900
//Supplément:Le prix est immuable et la valeur initialement définie ne change pas
//Valeur lors de l'appel de la dernière remise=Le calcul se fait à 1000

Pour augmenter le degré d'agrégation

Degré de couplage

En termes simples, "** Ce que savent les utilisateurs et les classes et méthodes d'utilisateurs (de combien ils dépendent) **".

Plus le degré de couplage est élevé, moins il est maintenable et plus il est difficile à tester.

Type de liaison

Le degré de couplage augmente dans l'ordre suivant.

Joindre un message

Il appelle uniquement des méthodes sans arguments et n'a pas de valeur de retour. La forme idéale.

public class Main {
   private final Price price;
   
   public Main(Price price) {
      this.price = price;
   }

   public void output() {
      this.price.print();
   }
}

Price price = new Price(1000);
Main main = new Main(price);
main.output(); // ¥1,Est affiché comme 000

Dans ce qui précède, la classe Main et la classe Price ne partagent pas de données, etMain # output ()ne dépend pas de l'implémentation de print ().

La classe Price ne sait rien de la classe Main, pas plus que la classe Main ne se soucie de ce quePrice # print () ʻ fait à l'intérieur. ** Par exemple, même si vous modifiez l'implémentation de la méthodeprint ()pour imprimer une chaîne dans un fichier, même si vous ne faites rien,Main # output` s'en fiche. ** **

De telles jointures sont également appelées ** transmission de messages **, et les interactions entre ces objets sont appelées ** messagerie **. Il a été nommé "messagerie" parce qu'il délègue complètement le travail à l'autre objet, et c'est comme dire à quelqu'un "c'est enfin fait".

Jointure de données

Ne transmettez que des données simples.

//Exemple de jointure de données

public class NameFormatter {
    public String format(String firstName , String familyName) {
        return new StringBuilder().add(familyName).add(" ").add(firstName).toString();
    }
}
public class Main {
   private final String firstName;
   private final String familyName;

   public Main(String firstName, String familyName) {
      this.firstName = firstName;
      this.familyName = familyName;
   }

   public void printName() {
      NameFormatter formatter = new NameFormatter();
      System.out.println(formatter.format(this.firstName, this.familyName)); 
   }
}

Main main = new Main("Kazuki", "Oda");
main.printName(); //Kazuki Oda

La combinaison avec NameFormatter dans` Main # printName () ʻ ci-dessus est une combinaison de données. Dans la liaison de données, ** ne partagez que les «informations nécessaires» **. Les «informations» auxquelles il est fait référence ici correspondent aux suivantes.

La liaison de données aboutit toujours à la même sortie pour la même entrée. De plus, puisque seules les informations nécessaires sont passées à NameFormatter, dans printName (), il est seulement nécessaire d'utiliser la valeur finalement retournée par NameFormatter # format, et quel type d'implémentation est fait en interne. Vous n'avez pas besoin de savoir si vous y êtes.

En général, les jointures de données transmettent les données en tant qu'argument [immuable](https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%9F%E3%83%A5%E3%83 % BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) ou [Type primitif](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83% AA% E3% 83% 9F% E3% 83% 86% E3% 82% A3% E3% 83% 96% E5% 9E% 8B) Ou fait référence à une jointure lors du passage d'une interface (je pense). Cependant, même dans le cas du passage d'un objet de type référence, il peut correspondre à une liaison de données (par exemple, seules les informations nécessaires sont divulguées par le procédé appelé).

Combinaison de timbres

Passez une structure de données (c'est-à-dire une instance avec plusieurs variables membres) comme argument.

public class Item {
    //Dans le corps, il y a respectivement un constructeur, un getter et un setter
    private String name;
    private String group;
}

public class CashRegister {
    public String format(Item item, Price price) {
        return item.getName() + ":" + price.format();
    }
}

CashRegister regi = new CashRegister();
System.out.println(regi.format(new Item("Soutien-gorge Smash", "Jeu"), new Price(6800))); //Soutien-gorge Smash:¥6,800

Dans le cas d'une combinaison de tampons, les données qui ne sont pas utilisées par l'appelé peuvent être référencées dans la structure (c'est-à-dire que l'appelé connaît également des informations inutiles). Dans le cas ci-dessus, «group» n'est pas utilisé, mais si vous voulez le faire, vous pouvez vous référer à l'un d'entre eux au format «CashRegister #».

Peu importe s'il s'agit simplement d'une référence, mais dans certains cas, il est possible de réécrire la valeur des données dans la méthode appelée. Donc ** Dans le cas d'une combinaison de tampons, lorsque l'appelant veut l'utiliser correctement, il y a des cas où il est nécessaire de savoir (savoir) dans une certaine mesure quelles données sont traitées dans la méthode appelée. ** **

Ce qui précède est une combinaison de données avec l'appelant si CashRegister est défini comme suit. Cependant, l'appelant et l'élément, ainsi que l'appelant et le prix sont estampillés ensemble </ font>

public class CashRegister {
    public String format(String item, int price) {
        return item + ":" + price;
    }
}

CashRegister regi = new CashRegister();
Item item = new Item("Soutien-gorge Smash", "Jeu");
Price price = new Price(6800);
System.out.println(regi.format(item.getName(), price.format())); //Soutien-gorge Smash:¥6,800

De cette manière, la combinaison de tampons ne peut pas être facilement éliminée tant que la classe est utilisée. En utilisant l'interface, il est possible de réduire le degré de liaison à la liaison de données ou à la liaison de message, mais ce sera plus long, donc je l'omettre.

  • Personnellement, dans le cas d'un projet de taille moyenne, il est plus réaliste d'autoriser la combinaison de tampons normalement que de fonctionner sur le principe orienté objet.

Contrôle de la jointure

Un cas où la valeur de retour change en fonction du contenu de l'argument donné.

public class Item {
//Dans le corps, il y a respectivement un constructeur, un getter et un setter
   private String name;
   private Price unitPrice;
}

public class SaleService {
   public Price fixPrice(Item item, int amount, boolean discount) {
       Price newPrice = item.getUnitPrice().multiply(amount);
       if (discount == false) {
           return newPrice;
       }
       return newPrice.discount(0.90);
   }
}

Item item = new Item("Soutien-gorge Smash", new Price(6800));
SaleService service = new SaleService();
System.out.prinln(service.fixPrice(item, 2, false)); // ¥13,600
System.out.prinln(service.fixPrice(item, 2, true)); // ¥12,240

Dans cette combinaison de contrôles, la valeur de retour diffère par le nombre de modèles même si une valeur similaire est entrée en tant qu'argument en fonction des modèles qui peuvent être pris. En d'autres termes, ** les modèles qui peuvent être pris dépendent des arguments. ** **

Cette liaison de contrôle peut être réduite à moins que la liaison de timbre en utilisant le polymorphisme. (Cependant, je pense personnellement que le couplage de contrôle peut être toléré s'il s'agit d'environ 3 à 4 motifs)

Jointure externe

La jointure externe fait référence à l'état dans lequel plusieurs modules font référence à une ressource globale. A partir de maintenant, si vous vous sentez libre d'autoriser cette jointure, ce sera très difficile à tester.

Les ressources globales font référence à ce qui suit, par exemple.

  • Variables statiques globales
  • Modifiable dans Singleton Les données
  • Fichier
  • Ressources de base de données
  • Valeur de retour d'un autre serveur (API Web, etc.)

Pour ceux-ci, la valeur modifiée dans d'autres instances, etc. affecte la classe entière référencée par other. De plus, si la structure de ces ressources change, il sera nécessaire de modifier toutes les classes appelantes.

** Pour écrire du code testable, il est important (je pense) de réduire le nombre de liaisons après cette jointure externe. ** **

Jointure commune

Un état dans lequel diverses classes font référence à plusieurs données de ressources globales. Ce sera plus chaotique que la jointure externe.

Combinaison de contenu

Une jointure qui connaît même les méthodes privées et les noms de variables des autres classes et les référence / les appelle directement. En combinaison de contenu, si l'implémentation interne (implémentation non publiée) de la classe dépendante change, elle sera directement affectée, donc même la modification du nom de variable de la dépendance sera affectée.

  • Si la réflexion est utilisée, il y a un risque de crash.

Pour réduire le degré de liaison

En gardant à l'esprit ce qui suit, le degré de couplage peut être maintenu bas.

  • Ne vous référez pas autant que possible aux données directement
  • N'utilisez pas autant que possible setter / getter (en particulier setter)
  • Essayez de créer une interface
  • En particulier, les classes qui coopèrent avec l'extérieur sont à l'extérieur et transformées en interface.
  • Principe de renversement de dépendance
  • Utiliser l'injection de dépendance
  • Il existe différentes bibliothèques DI telles que Guice
  • Plus efficace lorsqu'il est combiné avec des méthodes d'usine
  • Conscient de la loi de Déméter

Complexité circulaire (complexité cyclomatique)

En termes simples, le nombre de "instruction if / instruction for / instruction while / instruction switch / instruction try / instruction catch" et ainsi de suite. S'il n'y en a pas, ce sera 1, et à mesure que le nombre de branches augmente, il augmentera de 1. Par exemple, la complexité cyclique de la méthode suivante est de 4.

public void hogehoge(int test) {
   if (test > 5 ) {
       // doSomething1
   } else if (test < 1) {
       // doSomething2
   }
   for (int i = 0; i < 5; i++) {
       // doSomething3
   }
}

Lorsque le degré de référence cyclique est élevé

Bien sûr, il existe d'autres modèles de test. De plus, la lisibilité est réduite et la maintenance devient difficile.

Comment réduire la référence cyclique

  • Diviser la méthode
  • Rendre le nid peu profond (essayez de revenir tôt)
  • Respectez le principe DRY
  • Diviser le traitement par le polymorphisme
  • Utilisez l'API Stream, etc.

Résumé

  • Une plus grande agglomération est meilleure
  • Plus le degré de liaison est bas, mieux c'est
  • Une faible complexité cyclique est meilleure

Recommended Posts

Agrégation / couplage / complexité cyclique
À propos de la complexité cyclique