CodeGym /Blogue Java /Random-PT /O que são antipadrões? Vejamos alguns exemplos (Parte 2)
John Squirrels
Nível 41
San Francisco

O que são antipadrões? Vejamos alguns exemplos (Parte 2)

Publicado no grupo Random-PT
O que são antipadrões? Vejamos alguns exemplos (Parte 1) Hoje continuamos nossa análise dos antipadrões mais populares. Se você perdeu a primeira parte, aqui está. O que são antipadrões?  Vejamos alguns exemplos (Parte 2) - 1Portanto, os padrões de design são práticas recomendadas. Em outras palavras, são exemplos de boas maneiras comprovadas de resolver problemas específicos. Por sua vez, os antipadrões são exatamente o oposto, no sentido de que são padrões de armadilhas ou erros ao resolver vários problemas (padrões malignos). Vamos prosseguir para o próximo antipadrão de desenvolvimento de software.

8. Martelo de ouro

Um martelo de ouro é um antipadrão definido pela confiança de que uma solução específica é universalmente aplicável. Exemplos:
  1. Depois de encontrar um problema e encontrar um padrão para a solução perfeita, um programador tenta colocar esse padrão em todos os lugares, aplicando-o a projetos atuais e futuros, em vez de procurar soluções adequadas para casos específicos.

  2. Alguns desenvolvedores já criaram sua própria variante de cache para uma situação específica (porque nada mais era adequado). Mais tarde, no próximo projeto que não envolvia nenhuma lógica de cache especial, eles usaram sua variante novamente em vez de usar bibliotecas prontas (por exemplo, Ehcache). O resultado foi um monte de bugs e incompatibilidades, além de muito tempo perdido e nervos à flor da pele.

    Qualquer um pode cair nesse antipadrão. Se você for iniciante, talvez não tenha conhecimento sobre padrões de projeto. Isso pode levá-lo a tentar resolver todos os problemas da maneira que você domina. Se estamos falando de profissionais, chamamos isso de deformação profissional ou visão nerd. Você tem seus próprios padrões de design preferidos e, em vez de usar o certo, usa o seu favorito, assumindo que um bom ajuste no passado garante o mesmo resultado no futuro.

    Essa armadilha pode produzir resultados muito tristes — desde uma implementação ruim, instável e difícil de manter até o fracasso total do projeto. Assim como não existe uma pílula para todas as doenças, também não existe um padrão de design para todas as ocasiões.

9. Otimização prematura

A otimização prematura é um antipadrão cujo nome fala por si.
"Os programadores gastam muito tempo pensando e se preocupando com lugares não críticos no código e tentando otimizá-los, o que só afeta negativamente a depuração e o suporte subsequentes. Geralmente devemos esquecer a otimização em, digamos, 97% dos casos. Além disso , a otimização prematura é a raiz de todos os males. Dito isso, devemos prestar toda a atenção aos 3% restantes." —Donald Knuth
Por exemplo, adicionar índices prematuramente a um banco de dados. Por que isso é ruim? Bem, é ruim porque os índices são armazenados como uma árvore binária. Como resultado, cada vez que um novo valor for adicionado e excluído, a árvore será recalculada, o que consome recursos e tempo. Portanto, os índices devem ser adicionados apenas quando houver uma necessidade urgente (se você tiver uma grande quantidade de dados e as consultas demorarem muito) e apenas para os campos mais importantes (os campos mais consultados).

10. Código do espaguete

O código espaguete é um antipadrão definido por um código mal estruturado, confuso e difícil de entender, contendo todos os tipos de ramificação, como exceções de agrupamento, condições e loops. Antes, o operador goto era o principal aliado desse antipadrão. As instruções Goto não são mais usadas, o que felizmente elimina uma série de dificuldades e problemas associados.

public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
Parece horrível, não é? Infelizmente, este é o antipadrão mais comum :( Mesmo a pessoa que escreve esse código não será capaz de entendê-lo no futuro. Outros desenvolvedores que virem o código pensarão: "Bem, se funcionar, tudo bem - é melhor não tocá-lo". Muitas vezes, um método é inicialmente simples e muito transparente, mas à medida que novos requisitos são adicionados, o método é gradualmente sobrecarregado com mais e mais declarações condicionais, tornando-o uma monstruosidade como esta. Se tal método aparecer, você precisa refatorá-lo completamente ou pelo menos as partes mais confusas. Normalmente, ao agendar um projeto, o tempo é alocado para refatoração, por exemplo, 30% do tempo do sprint é para refatoração e testes. Claro, isso pressupõe que não há pressa (mas quando isso acontece).aqui .

11. Números mágicos

Os números mágicos são um antipadrão no qual todos os tipos de constantes são usados ​​em um programa sem nenhuma explicação de seu propósito ou significado. Ou seja, geralmente são mal nomeados ou, em casos extremos, não há nenhum comentário explicando o que são os comentários ou por quê. Assim como o código espaguete, esse é um dos antipadrões mais comuns. Alguém que não escreveu o código pode ou não ter a menor ideia sobre os números mágicos ou como eles funcionam (e com o tempo, o próprio autor não será capaz de explicá-los). Como resultado, alterar ou remover um número faz com que o código pare de funcionar magicamente. Por exemplo, 36 e 73. Para combater esse antipadrão, recomendo uma revisão de código. Seu código precisa ser examinado por desenvolvedores que não estão envolvidos nas seções relevantes do código. Seus olhos estarão frescos e eles terão perguntas: o que é isso e por que você fez isso? E claro, você precisa usar nomes explicativos ou deixar comentários.

12. Programação de copiar e colar

A programação de copiar e colar é um antipadrão no qual o código de outra pessoa é copiado e colado sem pensar, possivelmente resultando em efeitos colaterais inesperados. Por exemplo, copiar e colar métodos com cálculos matemáticos ou algoritmos complexos que não entendemos completamente. Pode funcionar para o nosso caso particular, mas em algumas outras circunstâncias pode causar problemas. Suponha que eu precise de um método para determinar o número máximo em uma matriz. Vasculhando a Internet, encontrei esta solução:

public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
Obtemos um array com os números 3, 6, 1, 4 e 2, e o método retorna 6. Ótimo, vamos mantê-lo! Mais tarde, porém, obtemos uma matriz que consiste em 2,5, -7, 2 e 3 e, em seguida, nosso resultado é -7. E esse resultado não é bom. O problema aqui é que Math.abs() retorna o valor absoluto. Ignorar isso leva ao desastre, mas apenas em certas situações. Sem uma compreensão profunda da solução, há muitos casos que você não poderá verificar. O código copiado também pode ir além da estrutura interna do aplicativo, tanto no estilo quanto em um nível arquitetônico mais fundamental. Esse código será mais difícil de ler e manter. E, claro, não devemos esquecer que copiar o código de outra pessoa é um tipo especial de plágio.

13. Reinventando a roda

Reinventar a roda é um antipadrão, também conhecido como reinventar a roda quadrada. Em essência, esse modelo é o oposto do antipadrão copiar e colar considerado acima. Nesse antipadrão, o desenvolvedor implementa sua própria solução para um problema para o qual já existem soluções. Às vezes, essas soluções existentes são melhores do que as inventadas pelo programador. Na maioria das vezes, isso leva apenas a perda de tempo e menor produtividade: o programador pode não encontrar uma solução ou pode encontrar uma solução que está longe de ser a melhor. Dito isso, não podemos descartar a possibilidade de criar uma solução independente, porque fazer isso é um caminho direto para a programação de copiar e colar. O programador deve guiar-se pelas tarefas específicas de programação que se apresentam de forma a resolvê-las com competência, quer recorrendo a soluções prontas quer a criar soluções à medida. Muitas vezes, a razão para usar esse antipadrão é simplesmente a pressa. O resultado é uma análise superficial de (busca por) soluções prontas. Reinventar a roda quadrada é um caso em que o antipadrão em consideração tem um resultado negativo. Ou seja, o projeto exige uma solução personalizada e o desenvolvedor a cria, mas mal. Ao mesmo tempo, uma boa opção já existe e outras estão usando com sucesso. Resumindo: uma grande quantidade de tempo é perdida. Primeiro, criamos algo que não funciona. Em seguida, tentamos refatorá-lo e, finalmente, substituímos por algo que já existia. Um exemplo é implementar seu próprio cache personalizado quando já existem várias implementações. Não importa o quão talentoso você seja como programador, lembre-se de que reinventar uma roda quadrada é, no mínimo, uma perda de tempo. E, como você sabe, o tempo é o recurso mais valioso.

14. Problema de ioiô

O problema ioiô é um antipadrão no qual a estrutura do aplicativo é excessivamente complicada devido à fragmentação excessiva (por exemplo, uma cadeia de herança excessivamente subdividida). O "problema ioiô" surge quando você precisa entender um programa cuja hierarquia de herança é longa e complexa, criando chamadas de método profundamente aninhadas. Como resultado, os programadores precisam navegar entre muitas classes e métodos diferentes para inspecionar o comportamento do programa. O nome desse antipadrão vem do nome do brinquedo. Como exemplo, vejamos a seguinte cadeia de herança: Temos uma interface de Tecnologia:

public interface Technology {
   void turnOn();
}
A interface Transport a herda:

public interface Transport extends Technology {
   boolean fillUp();
}
E então temos outra interface, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
E a partir daí, derivamos uma classe abstrata Car:

public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
A seguir, a classe abstrata do Volkswagen:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
E, finalmente, um modelo específico:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Essa cadeia nos obriga a buscar respostas para perguntas como:
  1. Quantos métodos VolkswagenAmaroktem?

  2. Que tipo deve ser inserido no lugar do ponto de interrogação para obter a máxima abstração:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
É difícil responder rapidamente a essas perguntas - exige que olhemos e investiguemos, e é fácil ficar confuso. E se a hierarquia for muito maior, mais longa e mais complicada, com todos os tipos de sobrecargas e substituições? A estrutura que teríamos seria obscurecida devido à fragmentação excessiva. A melhor solução seria reduzir as divisões desnecessárias. No nosso caso, sairíamos de Tecnologia → Carro → VolkswagenAmarok.

15. Complexidade acidental

A complexidade desnecessária é um antipadrão no qual complicações desnecessárias são introduzidas em uma solução.
"Qualquer tolo pode escrever um código que um computador pode entender. Bons programadores escrevem um código que os humanos podem entender." —Martin Fowler
Então, o que é complexidade? Pode ser definido como o grau de dificuldade com que cada operação é realizada no programa. Como regra, a complexidade pode ser dividida em dois tipos. O primeiro tipo de complexidade é o número de funções que um sistema possui. Só pode ser reduzido de uma maneira - removendo alguma função. Os métodos existentes precisam ser monitorados. Um método deve ser removido se não for mais usado ou ainda for usado, mas sem trazer nenhum valor. Além disso, você precisa avaliar como todos os métodos do aplicativo são usados, para entender onde valeria a pena investir (muito reuso de código) e o que você pode dizer não. O segundo tipo de complexidade é a complexidade desnecessária. Ela só pode ser curada por meio de uma abordagem profissional. Em vez de fazer algo "legal" (jovens desenvolvedores não são os únicos suscetíveis a esta doença), você precisa pensar em como fazer isso da maneira mais simples possível, porque a melhor solução é sempre simples. Por exemplo, suponha que temos pequenas tabelas relacionadas com descrições de algumas entidades, como um usuário: O que são antipadrões?  Vejamos alguns exemplos (Parte 2) - 3Assim, temos o id do usuário, o id do idioma em que a descrição é feita e a própria descrição. Da mesma forma, temos descritores auxiliares para as tabelas carros, arquivos, planos e clientes. Então, como seria inserir novos valores nessas tabelas?

public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
E, consequentemente, temos esta enumeração:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Tudo parece ser simples e bom... Mas e os outros métodos? Na verdade, todos eles também terão um monte de switchinstruções e um monte de consultas de banco de dados quase idênticas, o que, por sua vez, complicará e sobrecarregará muito nossa classe. Como tudo isso poderia ser facilitado? Vamos atualizar um pouco nosso enum:

@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
Agora cada tipo tem os nomes dos campos originais de sua tabela. Como resultado, o método para criar uma descrição torna-se:

private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
Conveniente, simples e compacto, você não acha? A indicação de um bom desenvolvedor não é nem a frequência com que ele usa padrões, mas sim a frequência com que ele evita os antipadrões. A ignorância é o pior inimigo, porque você precisa conhecer seus inimigos de vista. Bem, isso é tudo que tenho para hoje. Obrigado a todos! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION