JavaRush /Java Blog /Random EN /What are antipatterns? Parsing examples (part 1)

What are antipatterns? Parsing examples (part 1)

Published in the Random EN group
What are antipatterns?  Parsing examples (part 1) - 1Good day to all! The other day I had an interview, and I was asked a question about anti-patterns: what kind of animal is this, what are their types and examples in practice. Of course, I answered the question, but very superficially, since I did not go deep into the study of this issue. After the interview, I began to scour the Internet, more and more immersed in this topic. Today I would like to make a short review of the most popular anti-patterns and their examples, reading which, perhaps, will give you the necessary knowledge in this matter. Let's get started! So, before discussing what an antipattern is, let's remember what a pattern is. patternis a repeatable architectural construct for solving common problems or situations that arise when designing an application. But today we are not talking about them, but about their opposites - anti-patterns. An anti-pattern is a common approach to solving a class of common problems that is inefficient, risky, or counterproductive. In other words, it is an error pattern (also sometimes called a trap). What are antipatterns?  Parsing examples (part 1) - 2As a rule, antipatterns are divided into the following types:
  1. Architectural antipatterns are architectural antipatterns that arise when designing the structure of a system (usually by an architect).
  2. Management Anti Pattern - anti-patterns in the field of management, which are usually encountered by various managers (or groups of managers).
  3. Development Anti Pattern - antipatterns of development problems that arise when writing a system by ordinary programmers.
The exoticism of anti-patterns is much wider, but we will not consider them today, since for ordinary developers this will be a big deal. To begin with, let's take an antipattern in the control area as an example.

1. Analytical paralysis

analysis paralysisis considered a classic organizational anti-pattern. Its essence lies in overanalyzing the situation when planning, so that a decision or action is not taken, essentially paralyzing the development. Often this happens in cases where the goal is to achieve perfection and complete completion of the period of analysis. This anti-pattern is characterized by going in circles (a kind of a closed loop), reviewing and creating detailed models, which in turn interferes with the workflow. For example, you are trying to predict level things: what if the user suddenly wants to create a list of employees based on the fourth and fifth letters of their name, with inclusion in the list of projects to which they devoted the most working hours between New Year and March 8 in the previous four years ? In fact, this is an overabundance of analysis.led Kodak to bankruptcy . Here are a couple of small tips for dealing with analysis paralysis:
  1. You need to define a long-term goal as a beacon for decision-making so that each of your decisions brings you closer to the goal, and does not force you to stagnate.
  2. Do not focus on trifles (why make a decision on a minor nuance as if it were the last one in your life?)
  3. Set a deadline for making a decision.
  4. Do not try to do the task perfectly: it is better to do it very well.
We will not go too deep and consider other managerial anti-patterns now. Therefore, without preamble, let's move on to some architectural anti-patterns, because most likely, future developers, not managers, read this article.

2.God object

The divine object is an anti-pattern that describes an excessive concentration of too many disparate functions, storing a large amount of diverse data (the object around which the application revolves). Let's take a small example:
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();
   }
}
Here we see some big class that does everything at once. It contains queries to the Database, contains some data, we also see a facade method findAllWithoutPageEnwith business logic. Such a divine object becomes huge and clumsy to adequately maintain. We have to fiddle with it in every piece of code: many nodes in the system rely on it and are tightly coupled to it. Maintaining such code is getting harder and harder. In such cases, it must be divided into separate classes, each of which will have only one purpose (purpose). In this example, it can be broken down into a dao class:
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());
   }

                               ........
}
Class containing data and access methods to them:
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;
   }
                    ....
And it would be more appropriate to transfer the method with business logic to the 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.Singleton

A singleton is the simplest pattern, guaranteeing that there will be a single instance of a certain class in a single-threaded application, and providing a global access point to this object. You can read more about it here . But is it a pattern or an anti-pattern? What are antipatterns?  Parsing examples (part 1) - 3Let's look at the disadvantages of this pattern:
  1. global state. When we access an instance of a class, we do not know the current state of that class, and who changed it and when, and this state may not be what is expected at all. In other words, the correctness of working with a singleton depends on the order of calls to it, which causes the dependence of subsystems on each other and, as a result, seriously increases the complexity of development.

  2. Singleton violates one of the SOLID principles - Single Responsibility Principle - the singleton class, in addition to fulfilling its immediate duties, also controls the number of its instances.

  3. The dependency of an ordinary class on a singleton is not visible in the class interface. Since usually an instance of a singleton is not passed in the method parameters, but is obtained directly, through getInstance(), to identify the class's dependence on the singleton, you need to get into the implementation of each method - just viewing the object's public contract is not enough.

    The presence of a singleton reduces the testability of the application in general and the classes that use the singleton in particular. Firstly, instead of a singleton, you cannot put a Mock object, and secondly, if the singleton has an interface for changing its state, the tests will depend on each other.

    In other words, a singleton increases connectivity, and all of the above is nothing more than a consequence of increased connectivity.

    And if you think about it, using a singleton can be avoided. For example, to control the number of instances of an object, it is quite possible (and necessary) to use various kinds of factories.

    The greatest danger lurks when trying to build the entire application architecture based on singletons. There are many great alternatives to this approach. The most important example is Spring, namely its IoC containers: there the problem of controlling the creation of services is solved in a natural way, since they, in fact, are "factories on steroids".

    Now there is a lot of holivar on this topic, well, it's up to you to decide whether a singleton is a pattern or an anti-pattern.

    And we will not linger on it and move on to the last design pattern for today - the poltergeist.

4.Poltergeist

A poltergeist is an anti-pattern of a class that is not useful, which is used to call methods of another class or simply adds an unnecessary layer of abstraction. The anti-pattern manifests itself as short-lived, stateless objects. These objects are often used to initialize other, more durable objects.
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);
   }
}
Why do we need an object that is just an intermediary and delegates its work to someone else? We delete it, and we take out the small functionality that it implements into long-lived objects. Next, we move on to the patterns that are of most interest to us (as ordinary developers) - development anti-patterns .

5.Hard code

So we got to this terrible word - hardcode. The essence of this anti-pattern is that the code is strongly tied to a specific hardware configuration and / or system environment, which greatly complicates its porting to other configurations. This anti-pattern is closely related to magic numbers (they are often intertwined). Example:
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;
}
Nailed down, isn't it? Here we directly set the configuration of our connection, as a result, the code will work properly only with MySQL, and to change the database, you will need to get into the code and change everything manually. A good solution would be to move the configs to a separate file:
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
As a variant still - carrying out in constants.

6. Boat anchor

Boat anchor in the context of anti-patterns means storing unused parts of the system that are left after some kind of optimization or refactoring. Also, some parts of the code could be left "for the future", suddenly you have to use them again. In fact, this makes the code a trash can. What are antipatterns?  Parsing examples (part 1) - 4Example:
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());
}
We have an update method that uses a separate method to merge the user data from the database and the user who came to update (if the user who came to update has an empty field, then it is recorded with the old one from the database). And for example, there was a requirement that records should not be merged with old ones, but overwritten on top, even if there are empty fields:
public User update(Long id, User request) {
   return userDAO.update(user);
}
As a result, mergeUserit is no longer used and it is a pity to delete it: what if it (or its idea) is still useful? Such code only complicates and confuses systems, in fact, does not carry any practical value at all. We must not forget that such a code with "dead pieces" will be difficult to transfer to a colleague when you leave for another project. The best method of dealing with boat anchors is code refactoring, namely, the removal of these code sections (alas, alas). Also, when planning the development, it is necessary to take into account the occurrence of such anchors (allocate time to clean up the tails).

7.Object cesspool

To describe this anti-pattern, you first need to get acquainted with the object pool pattern . Object pool (resource pool) - generating design pattern , a set of initialized and ready-to-use objects. When an application needs an object, it is not re-created, but taken from this pool. When an object is no longer needed, it is not destroyed, but returned to the pool. Usually used for heavy objects that are resource intensive to create each time, such as a database connection. Let's break down a small and simple example for the sake of example. So, we have a class representing this pattern:
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);
   }
}
This class is presented in the form of the singleton pattern / anti-pattern described above , that is, there can be only one object of this type, it operates with certain objects Resource, by default in the constructor the pool is filled with 4 instances; when taking such an object, it is removed from the pool (if it does not exist, it is created and immediately given away), and at the end - a method to put the object back. Objects Resourcelook like this:
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;
   }
}
Here we have a small object containing a map with the names of the patterns as a key and references to them as a value, as well as methods for accessing the map. We look 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);
   }
}
Everything is clear here too: we take a pool object, pull an object with resources from it, take a map from it, do something with it and put it all back into the pool for further reuse. Voila: here is the object pool pattern. But we were talking about antipatterns, right? Let's consider such a case in 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);
Here again, a resource object is taken, its map with patterns is taken and something is done with it, but before saving it back to the object pool, the map is cleaned and clogged with obscure data that makes this Resource object unsuitable for reuse. One of the main nuances of the object pool is that after the object is returned, it must return to a state suitable for further reuse. If objects are returned to the pool in an invalid or indeterminate state, this construct is called an object cesspool. What are antipatterns?  Parsing examples (part 1) - 5Does it make sense for us to store objects that are unsuitable for reuse? In this situation, you can make the internal map immutable in the constructor:
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);
}
(attempts and desire to change content will fall with an UnsupportedOperationException). Anti-patterns are traps that a developer often gets into due to an acute lack of time, inattention, inexperience, or kicks from managers. The usual lack of time and haste can result in big problems for the application in the future, so these errors need to be known and avoided in advance. What are antipatterns?  Parsing examples (part 1) - 6On this, the first part of the article came to an end: to be continued .
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION