JavaRush /Java Blog /Random EN /What are antipatterns? Let's look at examples (part 1)

What are antipatterns? Let's look at examples (part 1)

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

1. Analytical paralysis

Analysis paralysis is considered a classic organizational anti-pattern. It involves overanalyzing a situation when planning so that no decision or action is taken, essentially paralyzing development. This often happens when the goal is to achieve perfection and complete completion of the analysis period. This anti-pattern is characterized by walking in circles (a kind of closed loop), revising and creating detailed models, which in turn interferes with the workflow. For example, you are trying to predict things like: what if the user suddenly wants to create a list of employees based on the fourth and fifth letters of their name, including in the list the projects to which they devoted the most working hours between the New Year and the Eighth of March in the four previous years ? In essence, this is an overabundance of analysis. A good real-life example is how analysis paralysis led Kodak to bankruptcy . Here are a couple of small tips to combat analysis paralysis:
  1. You need to define a long-term goal as a beacon for decision-making, so that every decision you make brings you closer to your goal, and does not force you to mark time.
  2. Don’t concentrate on trifles (why make a decision on a minor nuance as if it were the last in your life?)
  3. Set a deadline for making a decision.
  4. Don't try to do a task perfectly: it's better to do it very well.
We will not go too deep and consider other management antipatterns now. Therefore, without preamble, let’s move on to some architectural antipatterns, because most likely, this article is being read by future developers, not managers.

2.God object

Divine object is an anti-pattern that describes the 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 kind of large class that does everything at once. Contains queries to the Database, contains some data, we also see a facade method findAllWithoutPageEnwith business logic. Such a divine object becomes huge and cumbersome to adequately support. We have to tinker 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 becoming more and more difficult. In such cases, it needs to be divided into separate classes, each of which will have only one purpose (goal). In this example, we can break it 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());
   }

                               ........
}
A class containing data and methods to access it:
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 move the method with business logic into 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 that guarantees that there will be a single instance of some class in a single-threaded application and provides a global access point to that object. You can read more about it here . But is this a pattern or an antipattern? What are antipatterns?  Let's look at examples (part 1) - 3Let's look at the disadvantages of this template:
  1. Global state. When we access an instance of a class, we don't know the current state of that class or who changed it or when, and that state may not be what we expect. In other words, the correctness of working with a singleton depends on the order of calls to it, which causes the subsystems to depend 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 performing its immediate responsibilities, also controls the number of its instances.

  3. The dependency of a regular class on a singleton is not visible in the class interface. Since usually an instance of a singleton is not passed in the parameters of a method, but is obtained directly, through getInstance(), to identify the dependency of a class on a singleton, you need to delve into the implementation of each method - simply viewing the public contract of the object 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, you cannot put a Mock object in place of a singleton, and secondly, if a 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 lies in the attempt 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 naturally, since they, in fact, are “factories on steroids”.

    Now there is a lot of holivar on this topic, so it’s up to you to decide whether a singleton is a pattern or an antipattern.

    And we won’t dwell on it and move on to the last design pattern for today - poltergeist.

4. Poltergeist

Poltergeist is a non-useful class antipattern that is used to call methods of another class or simply adds an unnecessary layer of abstraction. The antipattern manifests itself in the form of short-lived objects devoid of state. 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 move the small functionality that it implements into long-lived objects. Next, we move on to the patterns that are of the greatest interest to us (as ordinary developers) - development antipatterns .

5.Hard code

So we got to this terrible word - hardcode. The essence of this antipattern is that the code is strongly tied to a specific hardware configuration and/or system environment, which makes it very difficult to port it to other configurations. This antipattern 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, 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 put the configs in 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
Another option is to move it to constants.

6. Boat anchor

A boat anchor in the context of antipatterns means storing unused parts of a system that are left over after some optimization or refactoring. Also, some parts of the code could be left “for the future”, in case you have to use them again. This essentially turns the code into a trash can. What are antipatterns?  Let's look at 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 data of the user from the database and the one who came for the update (if the person who came for the update has an empty field, then it is written as the old one from the database). And for example, there was a requirement that records should not be merged with old ones, but overwritten, 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, essentially not providing any practical value at all. We must not forget that such 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, deleting these sections of code (alas, alas). Also, when planning development, you need to take into account the occurrence of such anchors (allow time for cleaning up tailings).

7.Object cesspool

To describe this antipattern, you first need to get acquainted with the object pool pattern . An object pool (resource pool) is a generative design pattern , a set of objects initialized and ready for use. When an application requires an object, it is not created anew, but is taken from this pool. When an object is no longer needed, it is not destroyed, but returned to the pool. Typically used for heavy objects that are resource-intensive to create every time, such as a database connection. Let's look at a small and simple example for the sake of example. So we have a class that represents 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);
   }
}
We present this class in the form of the above-described singleton pattern/antipattern , that is, there can be only one object of this type, it operates on certain objects Resource, by default in the constructor the pool is filled with 4 copies; when such an object is taken, it is removed from the pool (if it is not there, it is created and immediately given away), and at the end there is a method to put the object back. The 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 the key and links to them as the value, as well as methods for accessing the map. Let's 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 here is also clear: we take a pool object, pull out an object with resources from it, take a map from it, do something with it and put it all back in the pool for further reuse. Voila: here you have the object pool pattern. But we were talking about antipatterns, weren't we? Let's look at this case 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 back to the object pool, the map is cleaned and filled with incomprehensible data that makes this Resource object unsuitable for reuse. One of the main nuances of an object pool is that after an object is returned, it must be returned to a state suitable for further reuse. If objects are in an incorrect or undefined state after being returned to the pool, this construct is called an object cesspool. Does it make sense for us to store objects that are not suitable 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 the content will fall along with UnsupportedOperationException). Antipatterns are traps that developers often fall 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 mistakes need to be known and avoided in advance. What are antipatterns?  Let's look at examples (part 1) - 6With this, the first part of the article has come to an end: to be continued .
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION