JavaRush /Blog Java /Random-FR /Que sont les anti-modèles ? Regardons des exemples (parti...

Que sont les anti-modèles ? Regardons des exemples (partie 1)

Publié dans le groupe Random-FR
Que sont les anti-modèles ?  Regardons des exemples (partie 1) - 1Bonne journée à tous! L'autre jour, j'ai été interviewé et on m'a posé une question sur les anti-modèles : de quel genre de bête s'agit-il, quels sont leurs types et leurs exemples dans la pratique. Bien sûr, j'ai répondu à la question, mais de manière très superficielle, puisque je n'ai pas approfondi l'étude de cette question. Après l’interview, j’ai commencé à parcourir Internet, m’immergeant de plus en plus dans ce sujet. Aujourd'hui, je voudrais faire un bref examen des anti-modèles les plus populaires et de leurs exemples, une lecture qui pourra vous donner les connaissances nécessaires sur cette question. Commençons! Donc, avant de discuter de ce qu’est un anti-modèle, rappelons-nous ce qu’est un modèle. Un modèle est une conception architecturale reproductible destinée à résoudre des problèmes ou des situations courants qui surviennent lors de la conception d'une application. Mais aujourd'hui, nous ne parlons pas d'eux, mais de leurs opposés - les anti-modèles. Un anti-modèle est une approche courante pour résoudre une classe de problèmes couramment rencontrés qui est inefficace, risquée ou improductive. En d’autres termes, il s’agit d’un modèle d’erreur (parfois appelé piège). Que sont les anti-modèles ?  Regardons des exemples (partie 1) - 2En règle générale, les anti-modèles sont divisés dans les types suivants :
  1. Anti-modèles architecturaux - anti-modèles architecturaux qui surviennent lors de la conception de la structure d'un système (généralement par un architecte).
  2. Management Anti Pattern - anti-modèles dans le domaine de la gestion, qui sont généralement rencontrés par divers managers (ou groupes de managers).
  3. Anti-modèle de développement - les anti-modèles sont des problèmes de développement qui surviennent lorsque des programmeurs ordinaires écrivent un système.
L'exotisme des anti-modèles est beaucoup plus large, mais nous ne les considérerons pas aujourd'hui, car pour les développeurs ordinaires, cela sera écrasant. Pour commencer, prenons un exemple d'anti-modèle dans le domaine du management.

1. Paralysie analytique

La paralysie de l’analyse est considérée comme un anti-modèle organisationnel classique. Cela implique de suranalyser une situation lors de la planification afin qu’aucune décision ou action ne soit prise, paralysant essentiellement le développement. Cela se produit souvent lorsque l’objectif est d’atteindre la perfection et d’achever la période d’analyse. Cet anti-modèle se caractérise par le fait de tourner en rond (une sorte de boucle fermée), de réviser et de créer des modèles détaillés, ce qui à son tour interfère avec le flux de travail. Par exemple, vous essayez de prédire des choses comme : que se passerait-il si l'utilisateur souhaitait soudainement créer une liste d'employés basée sur les quatrième et cinquième lettres de leur nom, en incluant dans la liste les projets auxquels ils ont consacré le plus d'heures de travail entre le Nouvel An et le 8 mars au cours des quatre années précédentes ? Il s’agit essentiellement d’une surabondance d’analyses. Un bon exemple concret est la façon dont la paralysie de l’analyse a conduit Kodak à la faillite . Voici quelques petits conseils pour lutter contre la paralysie de l’analyse :
  1. Vous devez définir un objectif à long terme comme un phare pour la prise de décision, afin que chaque décision que vous prenez vous rapproche de votre objectif et ne vous oblige pas à marquer le pas.
  2. Ne vous concentrez pas sur des bagatelles (pourquoi prendre une décision sur une nuance mineure comme si c'était la dernière de votre vie ?)
  3. Fixez une date limite pour prendre une décision.
  4. N'essayez pas de faire une tâche parfaitement : mieux vaut la faire très bien.
Nous n’irons pas trop loin et n’examinerons pas maintenant d’autres anti-modèles de gestion. Par conséquent, sans préambule, passons à quelques anti-modèles architecturaux, car très probablement, cet article est lu par les futurs développeurs et non par les gestionnaires.

2. Dieu objet

L'objet divin est un anti-modèle qui décrit la concentration excessive de trop de fonctions disparates, stockant une grande quantité de données diverses (l'objet autour duquel tourne l'application). Prenons un petit exemple :
public class SomeUserGodObject {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";
   private static final String FIND_ALL_CUSTOMERS = "SELECT id, u.email, u.phone, u.first_name_en, u.middle_name_en, u.last_name_en, u.created_date" +
           "  WHERE u.id IN (SELECT up.user_id FROM user_permissions up WHERE up.permission_id = ?)";
   private static final String FIND_BY_EMAIL = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_dateFROM users WHERE email = ?";
   private static final String LIMIT_OFFSET = " LIMIT ? OFFSET ?";
   private static final String ORDER = " ORDER BY ISNULL(last_name_en), last_name_en, ISNULL(first_name_en), first_name_en, ISNULL(last_name_ru), " +
           "last_name_ru, ISNULL(first_name_ru), first_name_ru";
   private static final String CREATE_USER_EN = "INSERT INTO users(id, phone, email, first_name_en, middle_name_en, last_name_en, created_date) " +
           "VALUES (?, ?, ?, ?, ?, ?, ?)";
   private static final String FIND_ID_BY_LANG_CODE = "SELECT id FROM languages WHERE lang_code = ?";
                                  ........
   private final JdbcTemplate jdbcTemplate;
   private Map<String, String> firstName;
   private Map<String, String> middleName;
   private Map<String, String> lastName;
   private List<Long> permission;
                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query( FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }
   @Override
   public Optional<List<User>> findAllEnByEmail(String email) {
       var query = FIND_ALL_USERS_EN + FIND_BY_EMAIL + ORDER;
       return Optional.ofNullable(jdbcTemplate.query(query, userRowMapper(), email));
   }
                              .............
   private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
       switch (type) {
           case USERS:
               return findAllEnUsers(permissionId);
           case CUSTOMERS:
               return findAllEnCustomers(permissionId);
           default:
               return findAllEn();
       }
   }
                              ..............private RowMapper<User> userRowMapperEn() {
       return (rs, rowNum) ->
               User.builder()
                       .id(rs.getLong("id"))
                       .email(rs.getString("email"))
                       .accessFailed(rs.getInt("access_counter"))
                       .createdDate(rs.getObject("created_date", LocalDateTime.class))
                       .firstName(rs.getString("first_name_en"))
                       .middleName(rs.getString("middle_name_en"))
                       .lastName(rs.getString("last_name_en"))
                       .phone(rs.getString("phone"))
                       .build();
   }
}
Ici, nous voyons une sorte de grande classe qui fait tout en même temps. Contient des requêtes vers la base de données, contient des données, nous voyons également une méthode de façade findAllWithoutPageEnavec une logique métier. Un tel objet divin devient énorme et encombrant à soutenir adéquatement. Nous devons le bricoler dans chaque morceau de code : de nombreux nœuds du système en dépendent et y sont étroitement couplés. La maintenance d'un tel code devient de plus en plus difficile. Dans de tels cas, il doit être divisé en classes distinctes, chacune ayant un seul objectif (objectif). Dans cet exemple, nous pouvons le décomposer en une classe dao :
public class UserDaoImpl {
   private static final String FIND_ALL_USERS_EN = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users;
   private static final String FIND_BY_ID = "SELECT id, email, phone, first_name_en, access_counter, middle_name_en, last_name_en, created_date FROM users WHERE id = ?";

                                   ........
   private final JdbcTemplate jdbcTemplate;

                                   ........
   @Override
   public List<User> findAllEnCustomers(Long permissionId) {
       return jdbcTemplate.query(FIND_ALL_CUSTOMERS + ORDER, userRowMapper(), permissionId);
   }
   @Override
   public List<User> findAllEn() {
       return jdbcTemplate.query(FIND_ALL_USERS_EN + ORDER, userRowMapper());
   }

                               ........
}
Une classe contenant des données et des méthodes pour y accéder :
public class UserInfo {
   private Map<String, String> firstName;..
   public Map<String, String> getFirstName() {
       return firstName;
   }
   public void setFirstName(Map<String, String> firstName) {
       this.firstName = firstName;
   }
                    ....
Et il serait plus approprié de déplacer la méthode avec la logique métier dans le service :
private List<User> findAllWithoutPageEn(Long permissionId, Type type) {
   switch (type) {
       case USERS:
           return findAllEnUsers(permissionId);
       case CUSTOMERS:
           return findAllEnCustomers(permissionId);
       default:
           return findAllEn();
   }
}

3.Célibataire

Un singleton est le modèle le plus simple qui garantit qu'il y aura une seule instance d'une classe dans une application monothread et fournit un point d'accès global à cet objet. Vous pouvez en savoir plus ici . Mais est-ce un modèle ou un anti-modèle ? Que sont les anti-modèles ?  Regardons des exemples (partie 1) - 3Examinons les inconvénients de ce modèle :
  1. État mondial. Lorsque nous accédons à une instance d'une classe, nous ne connaissons pas l'état actuel de cette classe, ni qui l'a modifiée ni quand, et cet état peut ne pas correspondre à ce à quoi nous nous attendons. En d'autres termes, l'exactitude du travail avec un singleton dépend de l'ordre des appels, ce qui fait dépendre les sous-systèmes les uns des autres et, par conséquent, augmente considérablement la complexité du développement.

  2. Singleton viole l'un des principes SOLID - le principe de responsabilité unique - la classe Singleton, en plus d'exercer ses responsabilités immédiates, contrôle également le nombre de ses instances.

  3. La dépendance d'une classe normale sur un singleton n'est pas visible dans l'interface de classe. Étant donné que généralement une instance d'un singleton n'est pas transmise dans les paramètres d'une méthode, mais est obtenue directement, via getInstance(), pour identifier la dépendance d'une classe sur un singleton, vous devez vous plonger dans l'implémentation de chaque méthode - en visualisant simplement le public le contrat de l'objet ne suffit pas.

    La présence d'un singleton réduit la testabilité de l'application en général et des classes qui utilisent le singleton en particulier. Premièrement, vous ne pouvez pas mettre un objet Mock à la place d'un singleton, et deuxièmement, si un singleton possède une interface pour changer d'état, les tests dépendront les uns des autres.

    En d’autres termes, un singleton augmente la connectivité, et tout ce qui précède n’est rien d’autre qu’une conséquence d’une connectivité accrue.

    Et si vous y réfléchissez, l’utilisation d’un singleton peut être évitée. Par exemple, pour contrôler le nombre d’instances d’un objet, il est tout à fait possible (et nécessaire) d’utiliser différents types d’usines.

    Le plus grand danger réside dans la tentative de construire l’intégralité de l’architecture applicative basée sur des singletons. Il existe de nombreuses alternatives intéressantes à cette approche. L’exemple le plus important est Spring, à savoir ses conteneurs IoC : là, le problème du contrôle de la création de services est résolu naturellement, puisqu’il s’agit en fait d’« usines sous stéroïdes ».

    Maintenant, il y a beaucoup de holivar sur ce sujet, c'est donc à vous de décider si un singleton est un modèle ou un anti-modèle.

    Et nous ne nous y attarderons pas et passerons au dernier modèle de conception d'aujourd'hui - le poltergeist.

4. Poltergeist

Poltergeist est un anti-modèle de classe non utile qui est utilisé pour appeler des méthodes d'une autre classe ou ajoute simplement une couche d'abstraction inutile. L’anti-modèle se manifeste sous la forme d’objets éphémères dépourvus d’état. Ces objets sont souvent utilisés pour initialiser d’autres objets plus durables.
public class UserManager {
   private UserService service;
   public UserManager(UserService userService) {
       service = userService;
   }
   User createUser(User user) {
       return service.create(user);
   }
   Long findAllUsers(){
       return service.findAll().size();
   }
   String findEmailById(Long id) {
       return service.findById(id).getEmail();}
   User findUserByEmail(String email) {
       return service.findByEmail(email);
   }
   User deleteUserById(Long id) {
       return service.delete(id);
   }
}
Pourquoi avons-nous besoin d’un objet qui ne soit qu’un intermédiaire et délègue son travail à quelqu’un d’autre ? Nous le supprimons et déplaçons les petites fonctionnalités qu'il implémente dans des objets à longue durée de vie. Passons ensuite aux modèles qui nous intéressent le plus (en tant que développeurs ordinaires) - les anti-modèles de développement .

5.Code dur

Nous sommes donc arrivés à ce mot terrible : code en dur. L’essence de cet anti-modèle est que le code est fortement lié à une configuration matérielle et/ou à un environnement système spécifique, ce qui rend très difficile son portage vers d’autres configurations. Cet anti-modèle est étroitement lié aux nombres magiques (ils sont souvent entrelacés). Exemple:
public Connection buildConnection() throws Exception {
   Class.forName("com.mysql.cj.jdbc.Driver");
   connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8&characterSetResults=UTF-8&serverTimezone=UTC", "user01", "12345qwert");
   return connection;
}
Cloué, n'est-ce pas ? Ici, nous définissons directement la configuration de notre connexion ; par conséquent, le code ne fonctionnera correctement qu'avec MySQL, et pour modifier la base de données, vous devrez entrer dans le code et tout modifier manuellement. Une bonne solution serait de mettre les configurations dans un fichier séparé :
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Une autre option consiste à le déplacer vers des constantes.

6. Ancre de bateau

Une ancre de bateau dans le contexte des anti-modèles signifie stocker les parties inutilisées d'un système qui restent après une optimisation ou une refactorisation. De plus, certaines parties du code pourraient être laissées « pour le futur », au cas où vous deviez les réutiliser. Cela transforme essentiellement le code en poubelle. Que sont les anti-modèles ?  Regardons des exemples (partie 1) - 4Exemple:
public User update(Long id, User request) {
   User user = mergeUser(findById(id), request);
   return userDAO.update(user);
}
private User mergeUser(User findUser, User requestUser) {
   return new User(
           findUser.getId(),
           requestUser.getEmail() != null ? requestUser.getEmail() : findUser.getEmail(),
           requestUser.getFirstName() != null ? requestUser.getFirstName() : findUser.getFirstNameRu(),
           requestUser.getMiddleName() != null ? requestUser.getMiddleName() : findUser.getMiddleNameRu(),
           requestUser.getLastName() != null ? requestUser.getLastName() : findUser.getLastNameEn(),
           requestUser.getPhone() != null ? requestUser.getPhone() : findUser.getPhone());
}
Nous avons une méthode de mise à jour qui utilise une méthode distincte pour fusionner les données de l'utilisateur de la base de données et de celui qui est venu pour la mise à jour (si la personne qui est venue pour la mise à jour a un champ vide, alors il est écrit comme l'ancien de la base de données). Et par exemple, il y avait une exigence selon laquelle les enregistrements ne devaient pas être fusionnés avec les anciens, mais écrasés, même s'il y avait des champs vides :
public User update(Long id, User request) {
   return userDAO.update(user);
}
Du coup, mergeUseril n’est plus utilisé et il est dommage de le supprimer : et si il (ou son idée) était encore utile ? Un tel code ne fait que compliquer et confondre les systèmes, n’apportant essentiellement aucune valeur pratique. Il ne faut pas oublier qu'un tel code avec des « pièces mortes » sera difficile à transférer à un collègue lorsque vous partirez pour un autre projet. La meilleure méthode pour gérer les ancres de bateau est la refactorisation du code, à savoir la suppression de ces sections de code (hélas, hélas). De plus, lors de la planification du développement, vous devez prendre en compte la présence de tels ancrages (prévoir du temps pour nettoyer les résidus).

7.Pool d'objets

Pour décrire cet anti-modèle, vous devez d'abord vous familiariser avec le modèle de pool d'objets . Un pool d'objets (pool de ressources) est un modèle de conception génératif , un ensemble d'objets initialisés et prêts à l'emploi. Lorsqu'une application nécessite un objet, celui-ci n'est pas créé à nouveau, mais est extrait de ce pool. Lorsqu’un objet n’est plus nécessaire, il n’est pas détruit, mais remis dans la réserve. Généralement utilisé pour les objets lourds qui nécessitent beaucoup de ressources à créer à chaque fois, comme une connexion à une base de données. Regardons un petit et simple exemple à titre d'exemple. Nous avons donc une classe qui représente ce modèle :
class ReusablePool {
   private static ReusablePool pool;
   private List<Resource> list = new LinkedList<>();
   private ReusablePool() {
       for (int i = 0; i < 3; i++)
           list.add(new Resource());
   }
   public static ReusablePool getInstance() {
       if (pool == null) {
           pool = new ReusablePool();
       }
       return pool;
   }
   public Resource acquireResource() {
       if (list.size() == 0) {
           return new Resource();
       } else {
           Resource r = list.get(0);
           list.remove(r);
           return r;
       }
   }
   public void releaseResource(Resource r) {
       list.add(r);
   }
}
Nous présentons cette classe sous la forme du modèle/antimodèle singleton décrit ci-dessus , c'est-à-dire qu'il ne peut y avoir qu'un seul objet de ce type, il opère sur certains objets Resource, par défaut dans le constructeur le pool est rempli de 4 copies ; lorsqu'un tel objet est pris, il est retiré du pool (s'il n'y est pas, il est créé et immédiatement donné), et à la fin il existe une méthode pour remettre l'objet. Les objets Resourceressemblent à ceci :
public class Resource {
   private Map<String, String> patterns;
   public Resource() {
       patterns = new HashMap<>();
       patterns.put("заместитель", "https://studfile.net/preview/3676297/page:3/");
       patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
       patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
       patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   }
   public Map<String, String> getPatterns() {
       return patterns;
   }
   public void setPatterns(Map<String, String> patterns) {
       this.patterns = patterns;
   }
}
Nous avons ici un petit objet contenant une carte avec les noms des modèles comme clé et des liens vers eux comme valeur, ainsi que des méthodes pour accéder à la carte. Regardons main:
class SomeMain {
   public static void main(String[] args) {
       ReusablePool pool = ReusablePool.getInstance();

       Resource firstResource = pool.acquireResource();
       Map<String, String> firstPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(firstResource);

       Resource secondResource = pool.acquireResource();
       Map<String, String> secondPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(secondResource);

       Resource thirdResource = pool.acquireResource();
       Map<String, String> thirdPatterns = firstResource.getPatterns();
       // ......Howим-то образом используем нашу мапу.....
       pool.releaseResource(thirdResource);
   }
}
Tout ici est également clair : nous prenons un objet du pool, en retirons un objet avec des ressources, en prenons une carte, faisons quelque chose avec et remettons le tout dans le pool pour une réutilisation ultérieure. Voila : vous avez ici le modèle de pool d'objets. Mais nous parlions d’anti-modèles, n’est-ce pas ? Regardons ce cas main:
Resource fourthResource = pool.acquireResource();
   Map<String, String> fourthPatterns = firstResource.getPatterns();
// ......Howим-то образом используем нашу мапу.....
fourthPatterns.clear();
firstPatterns.put("first","blablabla");
firstPatterns.put("second","blablabla");
firstPatterns.put("third","blablabla");
firstPatterns.put("fourth","blablabla");
pool.releaseResource(fourthResource);
Ici encore, un objet ressource est pris, sa carte avec des modèles est prise et quelque chose est fait avec, mais avant de la sauvegarder dans le pool d'objets, la carte est nettoyée et remplie de données incompréhensibles qui rendent cet objet ressource impropre à la réutilisation. L'une des principales nuances d'un pool d'objets est qu'une fois qu'un objet est restitué, il doit être remis dans un état approprié pour une réutilisation ultérieure. Si les objets sont dans un état incorrect ou indéfini après avoir été renvoyés dans le pool, cette construction est appelée un puisard d'objets. Quel est l’intérêt de stocker des objets qui ne sont pas réutilisables ? Dans cette situation, vous pouvez rendre la carte interne immuable dans le constructeur :
public Resource() {
   patterns = new HashMap<>();
   patterns.put("заместитель", "https://studfile.net/preview/3676297/page:3/");
   patterns.put("мост", "https://studfile.net/preview/3676297/page:4/");
   patterns.put("фасад", "https://studfile.net/preview/3676297/page:5/");
   patterns.put("строитель", "https://studfile.net/preview/3676297/page:6/#16");
   patterns = Collections.unmodifiableMap(patterns);
}
(les tentatives et le désir de modifier le contenu tomberont avec UnsupportedOperationException). Les anti-modèles sont des pièges dans lesquels tombent souvent les développeurs en raison d'un manque aigu de temps, d'inattention, d'inexpérience ou de coups de pied de la part des managers. Le manque de temps et la précipitation habituels peuvent entraîner de gros problèmes pour la candidature à l'avenir. Ces erreurs doivent donc être connues et évitées à l'avance. Que sont les anti-modèles ?  Regardons des exemples (partie 1) - 6Ainsi s'achève la première partie de l'article : à suivre .
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION