JavaRush /Blogue Java /Random-PT /O que são antipadrões? Vejamos exemplos (parte 1)

O que são antipadrões? Vejamos exemplos (parte 1)

Publicado no grupo Random-PT
O que são antipadrões?  Vejamos exemplos (parte 1) - 1Bom Dia a todos! Outro dia fui entrevistado e me fizeram uma pergunta sobre antipadrões: que tipo de besta é essa, quais são seus tipos e exemplos na prática. Claro que respondi à pergunta, mas de forma muito superficial, pois não me aprofundei muito no estudo do assunto. Após a entrevista, comecei a vasculhar a Internet, ficando cada vez mais imerso neste assunto. Hoje gostaria de fazer uma breve revisão dos antipadrões mais populares e seus exemplos, cuja leitura poderá lhe dar o conhecimento necessário sobre o assunto. Vamos começar! Portanto, antes de discutir o que é um antipadrão, vamos lembrar o que é um padrão. Um padrão é um projeto arquitetônico repetível para resolver problemas ou situações comuns que surgem ao projetar um aplicativo. Mas hoje não estamos falando sobre eles, mas sobre seus opostos - antipadrões. Um antipadrão é uma abordagem comum para resolver uma classe de problemas comumente encontrados que é ineficaz, arriscada ou improdutiva. Em outras palavras, é um padrão de erro (às vezes também chamado de armadilha). O que são antipadrões?  Vejamos exemplos (parte 1) - 2Via de regra, os antipadrões são divididos nos seguintes tipos:
  1. Antipadrões arquitetônicos - antipadrões arquitetônicos que surgem ao projetar a estrutura de um sistema (geralmente por um arquiteto).
  2. Management Anti Pattern - antipadrões no campo da gestão, que geralmente são encontrados por vários gestores (ou grupos de gestores).
  3. Antipadrão de desenvolvimento - antipadrões são problemas de desenvolvimento que surgem quando programadores comuns escrevem um sistema.
O exotismo dos antipadrões é muito mais amplo, mas não os consideraremos hoje, pois isso será opressor para desenvolvedores comuns. Para começar, tomemos um exemplo de antipadrão na área de gestão.

1. Paralisia analítica

A paralisia da análise é considerada um antipadrão organizacional clássico. Envolve analisar excessivamente uma situação durante o planejamento, de modo que nenhuma decisão ou ação seja tomada, paralisando essencialmente o desenvolvimento. Isso geralmente acontece quando o objetivo é atingir a perfeição e completar o período de análise. Esse antipadrão se caracteriza por andar em círculos (uma espécie de circuito fechado), revisando e criando modelos detalhados, o que por sua vez interfere no fluxo de trabalho. Por exemplo, você está tentando prever coisas como: e se o usuário de repente quiser criar uma lista de funcionários com base na quarta e quinta letras de seus nomes, incluindo os projetos aos quais ele dedicou mais horas de trabalho entre o Ano Novo e o 8 de março nos quatro anos anteriores? Em essência, esta é uma superabundância de análise. Um bom exemplo da vida real é como a paralisia da análise levou a Kodak à falência . Aqui estão algumas dicas rápidas para combater a paralisia da análise:
  1. Você precisa definir uma meta de longo prazo como um farol para a tomada de decisões, para que cada decisão que você tome o aproxime de seu objetivo e não o obrigue a perder tempo.
  2. Não se concentre em ninharias (por que tomar uma decisão sobre uma nuance menor como se fosse a última da sua vida?)
  3. Estabeleça um prazo para tomar uma decisão.
  4. Não tente fazer uma tarefa perfeitamente: é melhor fazê-la muito bem.
Não iremos muito fundo e consideraremos outros antipadrões de gestão agora. Portanto, sem preâmbulos, vamos passar para alguns antipadrões arquitetônicos, porque muito provavelmente este artigo está sendo lido por futuros desenvolvedores, não por gerentes.

2. Objeto de Deus

Objeto divino é um antipadrão que descreve a concentração excessiva de muitas funções díspares, armazenando uma grande quantidade de dados diversos (o objeto em torno do qual gira a aplicação). Vejamos um pequeno exemplo:
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();
   }
}
Aqui vemos uma espécie de classe grande que faz tudo de uma vez. Contém consultas ao Banco de Dados, contém alguns dados, vemos também um método fachada findAllWithoutPageEncom lógica de negócio. Tal objeto divino torna-se enorme e difícil de suportar adequadamente. Temos que mexer com ele em cada pedaço de código: muitos nós do sistema dependem dele e estão fortemente acoplados a ele. Manter esse código está se tornando cada vez mais difícil. Nesses casos, precisa ser dividido em classes distintas, cada uma das quais terá apenas uma finalidade (objetivo). Neste exemplo, você pode dividi-lo em uma 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());
   }

                               ........
}
Uma classe contendo dados e métodos para acessá-los:
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;
   }
                    ....
E seria mais apropriado mover o método com lógica de negócios para o serviço:
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

Um singleton é o padrão mais simples que garante que haverá uma única instância de alguma classe em um aplicativo de thread único e fornece um ponto de acesso global para esse objeto. Você pode ler mais sobre isso aqui . Mas isso é um padrão ou um antipadrão? O que são antipadrões?  Vejamos exemplos (parte 1) - 3Vejamos as desvantagens deste modelo:
  1. Estado global. Quando acessamos uma instância de uma classe, não sabemos o estado atual dessa classe ou quem a alterou ou quando, e esse estado pode não ser o que esperamos. Em outras palavras, a correção de trabalhar com um singleton depende da ordem das chamadas para ele, o que faz com que os subsistemas dependam uns dos outros e, como resultado, aumenta seriamente a complexidade do desenvolvimento.

  2. Singleton viola um dos princípios do SOLID – Princípio da Responsabilidade Única – a classe Singleton, além de exercer suas responsabilidades imediatas, também controla o número de suas instâncias.

  3. A dependência de uma classe regular em um singleton não é visível na interface da classe. Como normalmente uma instância de um singleton não é passada nos parâmetros de um método, mas é obtida diretamente, através de getInstance(), para identificar a dependência de uma classe em um singleton, é necessário se aprofundar na implementação de cada método - bastando visualizar o público contrato do objeto não é suficiente.

    A presença de um singleton reduz a testabilidade da aplicação em geral e das classes que utilizam o singleton em particular. Em primeiro lugar, você não pode colocar um objeto Mock no lugar de um singleton e, em segundo lugar, se um singleton tiver uma interface para alterar seu estado, os testes dependerão uns dos outros.

    Em outras palavras, um singleton aumenta a conectividade, e tudo isso nada mais é do que uma consequência do aumento da conectividade.

    E se você pensar bem, o uso de um singleton pode ser evitado. Por exemplo, para controlar o número de instâncias de um objeto, é bem possível (e necessário) utilizar vários tipos de fábricas.

    O maior perigo reside na tentativa de construir toda a arquitetura da aplicação baseada em singletons. Existem muitas alternativas excelentes para essa abordagem. O exemplo mais importante é o Spring, nomeadamente os seus contentores IoC: aí o problema de controlo da criação de serviços é resolvido naturalmente, pois são, na verdade, “fábricas de esteróides”.

    Agora há muito holivar sobre esse assunto, então cabe a você decidir se um singleton é um padrão ou um antipadrão.

    E não vamos insistir nisso e passar para o último padrão de design de hoje - poltergeist.

4. Poltergeist

Poltergeist é um antipadrão de classe inútil usado para chamar métodos de outra classe ou simplesmente adicionar uma camada desnecessária de abstração. O antipadrão se manifesta na forma de objetos de vida curta, desprovidos de estado. Esses objetos são frequentemente usados ​​para inicializar outros objetos mais duráveis.
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);
   }
}
Por que precisamos de um objeto que seja apenas um intermediário e delegue seu trabalho a outra pessoa? Nós o excluímos e movemos a pequena funcionalidade que ele implementa para objetos de longa duração. A seguir, passamos para os padrões que são de maior interesse para nós (como desenvolvedores comuns) - antipadrões de desenvolvimento .

5. Código rígido

Então chegamos a essa palavra terrível - hardcode. A essência desse antipadrão é que o código está fortemente vinculado a uma configuração de hardware e/ou ambiente de sistema específico, o que torna muito difícil portá-lo para outras configurações. Esse antipadrão está intimamente relacionado aos números mágicos (eles geralmente estão interligados). Exemplo:
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;
}
Acertado, não é? Aqui definimos diretamente a configuração da nossa conexão, com isso o código funcionará corretamente apenas com MySQL, e para alterar o banco de dados você precisará entrar no código e alterar tudo manualmente. Uma boa solução seria colocar as configurações em um arquivo separado:
spring:
  datasource:
    jdbc-url:jdbc:mysql://localhost:3306/someDb?characterEncoding=UTF-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username:  user01
    password:  12345qwert
Outra opção é movê-lo para constantes.

6. Âncora de barco

Uma âncora de barco no contexto de antipadrões significa armazenar partes não utilizadas de um sistema que sobraram após alguma otimização ou refatoração. Além disso, algumas partes do código podem ser deixadas “para o futuro”, caso você precise utilizá-las novamente. Isso essencialmente transforma o código em uma lata de lixo. O que são antipadrões?  Vejamos exemplos (parte 1) - 4Exemplo:
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());
}
Temos um método de atualização que usa um método separado para mesclar os dados do usuário do banco de dados e daquele que veio para a atualização (se a pessoa que veio para a atualização tiver um campo vazio, então ele é escrito como o antigo do banco de dados). E, por exemplo, havia a exigência de que os registros não fossem mesclados com os antigos, mas sobrescritos, mesmo que houvesse campos vazios:
public User update(Long id, User request) {
   return userDAO.update(user);
}
Com isso, mergeUserele não é mais utilizado e é uma pena excluí-lo: e se ele (ou sua ideia) ainda for útil? Esse código apenas complica e confunde os sistemas, essencialmente não fornecendo nenhum valor prático. Não devemos esquecer que tal código com “peças mortas” será difícil de transferir para um colega quando você partir para outro projeto. O melhor método para lidar com âncoras de barco é a refatoração de código, ou seja, a exclusão dessas seções de código (infelizmente). Além disso, ao planejar o desenvolvimento, é necessário levar em consideração a ocorrência de tais âncoras (reserve tempo para a limpeza dos rejeitos).

7. Fossa de objetos

Para descrever esse antipadrão, primeiro você precisa se familiarizar com o padrão do pool de objetos . Um pool de objetos (pool de recursos) é um padrão de design generativo , um conjunto de objetos inicializados e prontos para uso. Quando um aplicativo requer um objeto, ele não é criado novamente, mas retirado desse pool. Quando um objeto não é mais necessário, ele não é destruído, mas devolvido ao pool. Normalmente usado para objetos pesados ​​que exigem muitos recursos para serem criados sempre, como uma conexão de banco de dados. Vejamos um exemplo pequeno e simples para exemplificar. Então temos uma classe que representa esse padrão:
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);
   }
}
Apresentamos esta classe na forma do padrão/antipadrão singleton descrito acima , ou seja, só pode haver um objeto deste tipo, ele opera em determinados objetos Resource, por padrão no construtor o pool é preenchido com 4 cópias; quando tal objeto é retirado, ele é removido do pool (se não estiver lá, é criado e imediatamente doado), e no final existe um método para colocar o objeto de volta. Os objetos Resourceficam assim:
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;
   }
}
Aqui temos um pequeno objeto contendo um mapa com os nomes dos padrões como chave e links para eles como valor, bem como métodos para acessar o mapa. Vamos olhar 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);
   }
}
Tudo aqui também é claro: pegamos um objeto pool, retiramos dele um objeto com recursos, pegamos um mapa dele, fazemos algo com ele e colocamos tudo de volta no pool para reutilização posterior. Voila: aqui você tem o padrão do pool de objetos. Mas estávamos falando sobre antipadrões, não é? Vejamos este caso 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);
Aqui, novamente, um objeto de recurso é obtido, seu mapa com padrões é obtido e algo é feito com ele, mas antes de salvá-lo novamente no conjunto de objetos, o mapa é limpo e preenchido com dados incompreensíveis que tornam esse objeto de recurso inadequado para reutilização. Uma das principais nuances de um pool de objetos é que depois que um objeto é retornado, ele deve ser retornado a um estado adequado para reutilização posterior. Se os objetos estiverem em um estado incorreto ou indefinido após serem retornados ao pool, essa construção é chamada de fossa de objetos. Qual é o sentido de armazenar objetos que não são reutilizáveis? Nesta situação, você pode tornar o mapa interno imutável no construtor:
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);
}
(as tentativas e o desejo de alterar o conteúdo cairão junto com UnsupportedOperationException). Antipadrões são armadilhas nas quais os desenvolvedores muitas vezes caem devido a uma grave falta de tempo, desatenção, inexperiência ou incentivos dos gerentes. A habitual falta de tempo e pressa pode resultar em grandes problemas para a aplicação no futuro, por isso esses erros precisam ser conhecidos e evitados com antecedência. O que são antipadrões?  Vejamos exemplos (parte 1) - 6Com isso, a primeira parte do artigo chegou ao fim: continua .
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION