JavaRush /Java Blog /Random-TL /Ano ang mga antipattern? Tingnan natin ang mga halimbawa ...

Ano ang mga antipattern? Tingnan natin ang mga halimbawa (bahagi 1)

Nai-publish sa grupo
What такое антипаттерны? Разбираем примеры (часть 1) - 1Magandang araw sa lahat! Noong isang araw ay nainterbyu ako, at tinanong ako tungkol sa mga antipattern: anong uri ng hayop ito, ano ang kanilang mga uri at halimbawa sa pagsasanay. Siyempre, sinagot ko ang tanong, ngunit napakababaw, dahil hindi ako masyadong malalim sa pag-aaral ng isyung ito. Pagkatapos ng panayam, nagsimula akong magsaliksik sa Internet, na naging mas lalo pang nahuhulog sa paksang ito. Ngayon nais kong gumawa ng isang maikling pagsusuri ng mga pinakasikat na antipattern at ang kanilang mga halimbawa, pagbabasa na maaaring magbigay sa iyo ng kinakailangang kaalaman sa isyung ito. Magsimula na tayo! Kaya, bago talakayin kung ano ang isang antipattern, tandaan natin kung ano ang isang pattern. Ang isang pattern ay isang paulit-ulit na disenyo ng arkitektura para sa paglutas ng mga karaniwang problema o sitwasyon na lumitaw kapag nagdidisenyo ng isang aplikasyon. Ngunit ngayon hindi namin pinag-uusapan ang tungkol sa kanila, ngunit tungkol sa kanilang mga kabaligtaran - mga antipattern. Ang isang anti-pattern ay isang karaniwang diskarte sa paglutas ng isang klase ng mga karaniwang nakakaharap na problema na hindi epektibo, peligroso, o hindi produktibo. Sa madaling salita, ito ay isang error pattern (tinatawag ding bitag). What такое антипаттерны? Разбираем примеры (часть 1) - 2Bilang isang patakaran, ang mga antipattern ay nahahati sa mga sumusunod na uri:
  1. Mga antipattern ng arkitektura - mga antipattern ng arkitektura na lumitaw kapag nagdidisenyo ng istraktura ng isang sistema (karaniwan ay ng isang arkitekto).
  2. Pamamahala ng Anti Pattern - mga antipattern sa larangan ng pamamahala, na kadalasang nakatagpo ng iba't ibang mga tagapamahala (o mga grupo ng mga tagapamahala).
  3. Development Anti Pattern - ang mga antipattern ay mga problema sa pag-unlad na lumitaw kapag ang mga ordinaryong programmer ay sumulat ng isang sistema.
Ang exoticism ng mga antipattern ay mas malawak, ngunit hindi namin isasaalang-alang ang mga ito ngayon, dahil para sa mga ordinaryong developer ito ay napakalaki. Upang magsimula, kumuha tayo ng isang halimbawa ng isang antipattern sa larangan ng pamamahala.

1. Analytical paralysis

Аналитический паралич — считается классическим организационным антипаттерном. Его суть заключается в чрезмерном анализировании ситуации при планировании, так что решение or действие не предпринимаются, по сути парализуя разработку. Зачастую это случается в тех случаях, когда цель состоит в достижении совершенства и полной завершенности периода анализа. Этот антипаттерн характеризуется хождением по кругу (такой себе замкнутый цикл), пересмотром и созданием детальных моделей, что в свою очередь мешает рабочему процессу. К примеру, вы пытаетесь предугадать вещи уровня: а что если вдруг пользователь захочет создать список сотрудников на основе четвертых и пятых букв их имени, с включением в список проектов, которым они уделor больше всего рабочих часов между Новым Годом и Восьмым марта за четыре предыдущих года? По сути это переизбыток анализа. Хороший пример из жизни — How аналитический паралич привел Kodak к банкротству. Вот парочка небольших советов для борьбы аналитическим параличом:
  1. Нужно определить долгосрочную цель в качестве маяка для принятия решений, чтобы каждое ваше решение приближало к цели, а не заставляло топтаться на месте.
  2. Не концентрироваться на пустяках (зачем принимать решение по незначительному нюансу так, словно оно последнее в жизни?)
  3. Задайте крайний срок для принятия решения.
  4. Не старайтесь сделать задачу совершенно: лучше сделать очень хорошо.
Слишком углубляться и рассматривать другие управленческие антипаттерны мы сейчас не будем. Поэтому без предисловий переходим к некоторым архитектурным антипаттернам, ведь скорее всего, эту статью читают будущие разработчики, а не менеджеры.

2. God object

Божественный an object — антипаттерн, который описывает излишнюю концентрацию слишком большого количества разношерстных функций, хранения большого количества разнообразных данных (an object, вокруг которого вращается приложение). Возьмем небольшой пример:
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();
   }
}
Тут мы видим Howой-то большой класс, который делает всё и сразу. Содержит requestы к Базе данных, содержит в себе Howие-то данные, также видим фасадный метод findAllWithoutPageEn с бизнес-логикой. Такой божественный an object становится огромным и неповоротливым для адекватного поддержания. Нам приходится возиться с ним в каждом кусочке codeа: многие узлы системы полагаются на него и жёстко с ним связаны. Поддерживать такой code становится труднее и труднее. В таких случаях его нужно разрубить на отдельные классы, у каждого из которых будет лишь одно преднаmeaning (цель). В данном примере, можно разбить на класс дао:
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());
   }

                               ........
}
Класс, содержащий данные и методы доступа к ним:
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;
   }
                    ....
И метод с бизнес-логикой будет уместней вынести в сервис:
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

Одиночка — это самый простой паттерн, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому an objectу. Подробнее о нём можно почитать вот тут. Но паттерн ли это or антипаттерн? What такое антипаттерны? Разбираем примеры (часть 1) - 3Давайте рассмотрим недостатки данного шаблона:
  1. Глобальное состояние. Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, How ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает зависимость подсистем друг от друга и, How следствие, серьезно повышает сложность разработки.

  2. Синглтон нарушает один из принципов SOLID — Single Responsibility Principle — класс синглтона, помимо выполнения своих непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.

  3. Зависимость обычного класса от синглтона не видна в интерфейсе класса. Так How обычно экземпляр синглтона не передается в параметрах метода, а получается напрямую, через getInstance(), для выявления зависимости класса от синглтона надо залезть в реализацию каждого метода — просто просмотреть публичный контракт an object недостаточно.

    Наличие синглтона снижает тестируемость applications в целом и классов, которые используют синглтон, в частности. Во-первых, instead of синглтона нельзя подложить Mock-an object, а во-вторых, если у синглтона есть интерфейс для изменения своего состояния, тесты будут зависеть друг от друга.

    Другими словами, синглтон повышает связность, и все вышеперечисленное есть ничто иное How следствие повышения связности.

    И если задуматься, использования синглтона можно избежать. Например, для контроля количества экземпляров an object вполне можно (да и нужно) использовать различного рода фабрики.

    Наибольшая же опасность подстерегает при попытке построить на основе синглтонов всю архитектуру applications. Такому подходу существует масса замечательных альтернатив. Самый главный пример — это Spring, а именно — его IoC контейнеры: там проблема контроля создания сервисов решается естественным образом, так How они, по факту, являются "фабриками на стероидах".

    Now существует много много холивара на эту тему, ну и решать, синглтон — это паттерн or антипаттерн, уже вам.

    А мы на нём не будем задерживаться и перейдём к последнему на сегодня паттерну проектирования — полтергейсту.

4. Poltergeist

Полтергейст — это антипаттерн класса, не несущего пользы, который используется для вызова методов другого класса or просто добавляет ненужный слой абстракции. Антипаттерн проявляется в виде короткоживущих an objectов, лишенных состояния. Эти an objectы часто используются для инициализации другие, более устойчивых an objectов.
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);
   }
}
Зачем нам нужен an object, который всего лишь посредник и делегирует свою работу кому-то другому? Удаляем его, а тот небольшой функционал, который он реализует, выносим в an objectы-долгожители. Далее мы переходим к паттернам, которые предоставляют наибольший интерес для нас (How рядовых разработчиков) — к антипаттернам разработки.

5. Hard code

Вот мы и добрались до этого страшного слова — хардcode. Суть данного антипаттерна в том, что code сильно привязан к конкретной аппаратной конфигурации и/or системному окружению, что сильно усложняет перенос его на другие конфигурации. Данный антипаттерн тесно связан с магическими числами (они часто переплетаются). Пример:
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;
}
Прибито гвоздями, не правда ли? Тут мы непосредственно задаем конфигурацию нашего соединения, по итогу code будет исправно работать только с MySQL, и для смены базы данных нужно будет залезть в code и ручками всё менять. Хорошим решением будет вынести конфиги в отдельный файл:
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Как вариант ещё — вынос в константы.

6. Boat anchor

Лодочный якорь в контексте антипаттернов означает хранение неиспользуемых частей системы, которые остались после Howой-то оптимизации or рефакторинга. Также некоторые части codeа могли быть оставлены «на будущее», вдруг придётся ещё их использовать. По сути это делает из codeа мусорное ведро. What такое антипаттерны? Разбираем примеры (часть 1) - 4Пример:
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());
}
У нас есть метод обновления, который использует отдельный метод для слияния данных пользователя из базы данных и пришедшего на обновление (если у пришедшего на обновление пустое поле, то оно записывается старым из базы данных). И к примеру, появилось требование, что записи не должны объединяться со старыми, а перезаписываться поверх, даже если есть пустые поля:
public User update(Long id, User request) {
   return userDAO.update(user);
}
Как итог, mergeUser уже не используется и жалко его удалять: вдруг он (or его идея) ещё пригодится? Такой code только усложняет и путает системы, по сути не неся вовсе ниHowой практической ценности. Нужно не забывать, что подобный code с «мертвыми кусками» будет тяжело передавать коллеге, когда вы уйдете на другой проект. Лучшим методом борьбы с лодочными якорями является рефакторинг codeа, именно — удаление данных участков codeа (увы, увы). Также при планировании разработки нужно учитывать возникновение подобных якорей (выделять время на зачистку хвостов).

7.Object cesspool

Для описания данного антипаттерна для начала нужно познакомиться с паттерном пул an objectов. Пул an objectов (пул ресурсов) — порождающий шаблон проектирования, набор инициализированных и готовых к использованию an objectов. Когда приложению требуется an object, он не создается заново, а берётся из этого пула. Когда an object уже не нужен, он не уничтожается, а возвращается в пул. Обычно используется для тяжёлых an objectов, которые ресурсозатратно каждый раз создавать, How например соединение с базой данных. Давайте разберем небольшой и простой пример ради примера. Итак, у нас есть класс, представляющий данный паттерн:
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);
   }
}
Данный класс у нас представлен в виде вышеописанного паттерна/антипаттерна синглтона, то есть может быть только один an object данного типа, оперирует некими an objectми Resource, по умолчанию в конструкторе пул заполняется 4-мя экземплярами; при взятии такого an object он удаляется из пула (если его нет, то создается и сразу отдается), и в конце — метод, чтобы положить an object обратно. Объекты Resource выглядят так:
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;
   }
}
Тут у нас небольшой an object, содержащий мапу с названиями паттернов в качестве ключа и linkми на них How meaning, а также методов доступа к мапе. Смотрим 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);
   }
}
Тут всё тоже понятно: мы берём an object пула, вытягиваем из него an object с ресурсами, берём из него мапу, что-то с ней делаем и кладем всё это на место в пул для дальнейшего переиспользования. Вуаля: вот вам и паттерн пул an objectов. Но мы же говорor об антипатернах, не так ли? Давайте рассмотрим такой случай в 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);
Тут опять же берется an object ресурсов, берётся его map с паттернами и что-то с ним делается, но перед сохранением назад в пул an objectов мапа чистится и забивается непонятными данными, делающими данный an object Resource непригодным для переиспользования. Один из главных нюансов пула an objectов — после того, How an object возвращен, он должен вернуться в состояние, пригодное для дальнейшего переиспользования. Если an objectы после возвращения в пул оказываются в неправильном or неопределенном состоянии, такая конструкция и называется an objectной клоакой. Смысл нам хранить an objectы, непригодные для переиспользования? В данной ситуации можно сделать в конструкторе внутреннюю мапу неизменяемой:
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);
}
(попытки и желание поменять содержимое упадут вместе с UnsupportedOperationException). Антипаттерны — ловушки, в которые разработчик часто встревает из-за острой нехватки времени, невнимательности, неопытности or пинков со стороны менеджеров. Обычная нехватка времени и спешка может вылиться в большие проблемы для applications в будущем, поэтому эти ошибки нужно нужно знать и заранее их избегать. What такое антипаттерны? Разбираем примеры (часть 1) - 6На этом, первая часть статьи подошла концу: продолжение следует.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION