- Mga antipattern ng arkitektura - mga antipattern ng arkitektura na lumitaw kapag nagdidisenyo ng istraktura ng isang sistema (karaniwan ay ng isang arkitekto).
- 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).
- Development Anti Pattern - ang mga antipattern ay mga problema sa pag-unlad na lumitaw kapag ang mga ordinaryong programmer ay sumulat ng isang sistema.
1. Analytical paralysis
Аналитический паралич — считается классическим организационным антипаттерном. Его суть заключается в чрезмерном анализировании ситуации при планировании, так что решение or действие не предпринимаются, по сути парализуя разработку. Зачастую это случается в тех случаях, когда цель состоит в достижении совершенства и полной завершенности периода анализа. Этот антипаттерн характеризуется хождением по кругу (такой себе замкнутый цикл), пересмотром и созданием детальных моделей, что в свою очередь мешает рабочему процессу. К примеру, вы пытаетесь предугадать вещи уровня: а что если вдруг пользователь захочет создать список сотрудников на основе четвертых и пятых букв их имени, с включением в список проектов, которым они уделor больше всего рабочих часов между Новым Годом и Восьмым марта за четыре предыдущих года? По сути это переизбыток анализа. Хороший пример из жизни — How аналитический паралич привел Kodak к банкротству. Вот парочка небольших советов для борьбы аналитическим параличом:- Нужно определить долгосрочную цель в качестве маяка для принятия решений, чтобы каждое ваше решение приближало к цели, а не заставляло топтаться на месте.
- Не концентрироваться на пустяках (зачем принимать решение по незначительному нюансу так, словно оно последнее в жизни?)
- Задайте крайний срок для принятия решения.
- Не старайтесь сделать задачу совершенно: лучше сделать очень хорошо.
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 антипаттерн? Давайте рассмотрим недостатки данного шаблона:-
Глобальное состояние. Когда мы получаем доступ к экземпляру класса, мы не знаем текущее состояние этого класса, и кто и когда его менял, и это состояние может быть вовсе не таким, How ожидается. Иными словами, корректность работы с синглтоном зависит от порядка обращений к нему, что вызывает зависимость подсистем друг от друга и, How следствие, серьезно повышает сложность разработки.
-
Синглтон нарушает один из принципов SOLID — Single Responsibility Principle — класс синглтона, помимо выполнения своих непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.
-
Зависимость обычного класса от синглтона не видна в интерфейсе класса. Так 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а мусорное ведро. Пример: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 в будущем, поэтому эти ошибки нужно нужно знать и заранее их избегать. На этом, первая часть статьи подошла концу: продолжение следует.
GO TO FULL VERSION