CodeGym /Java Blog /Random-IT /Cosa sono gli anti-pattern? Diamo un'occhiata ad alcuni e...
John Squirrels
Livello 41
San Francisco

Cosa sono gli anti-pattern? Diamo un'occhiata ad alcuni esempi (Parte 2)

Pubblicato nel gruppo Random-IT
Cosa sono gli anti-pattern? Diamo un'occhiata ad alcuni esempi (Parte 1) Oggi continuiamo la nostra rassegna degli anti-pattern più popolari. Se vi siete persi la prima parte, eccola qui . Cosa sono gli anti-pattern?  Vediamo alcuni esempi (Parte 2) - 1Quindi, i modelli di progettazione sono le migliori pratiche. In altre parole, sono esempi di modi validi e collaudati per risolvere problemi specifici. A loro volta, gli anti-pattern sono il loro esatto opposto, nel senso che sono modelli di insidie ​​o errori nella risoluzione di vari problemi (modelli malvagi). Passiamo al prossimo anti-pattern di sviluppo del software.

8. Martello d'oro

Un martello d'oro è un anti-modello definito dalla fiducia che una particolare soluzione sia universalmente applicabile. Esempi:
  1. Dopo aver incontrato un problema e aver trovato uno schema per la soluzione perfetta, un programmatore cerca di applicare questo schema ovunque, applicandolo ai progetti attuali e futuri, invece di cercare le soluzioni appropriate per casi specifici.

  2. Alcuni sviluppatori una volta hanno creato la propria variante di una cache per una situazione specifica (perché nient'altro era adatto). Successivamente, nel progetto successivo che non prevedeva una logica cache speciale, hanno utilizzato di nuovo la loro variante invece di utilizzare librerie già pronte (ad esempio, Ehcache). Il risultato è stato un sacco di bug e incompatibilità, oltre a un sacco di tempo perso e nervi fritti.

    Chiunque può innamorarsi di questo anti-modello. Se sei un principiante, potresti non essere informato sui modelli di progettazione. Questo potrebbe portarti a provare a risolvere tutti i problemi nell'unico modo che hai imparato. Se parliamo di professionisti, chiamiamo questa deformazione professionale o nerdview. Hai i tuoi modelli di design preferiti e invece di usare quello giusto, usi il tuo preferito, supponendo che un buon adattamento in passato garantisca lo stesso risultato in futuro.

    Questa trappola può produrre risultati molto tristi: da un'implementazione cattiva, instabile e difficile da mantenere a un completo fallimento del progetto. Proprio come non esiste una pillola per tutte le malattie, non esiste un modello di progettazione per tutte le occasioni.

9. Ottimizzazione prematura

L'ottimizzazione prematura è un anti-pattern il cui nome parla da sé.
"I programmatori trascorrono un'enorme quantità di tempo pensando e preoccupandosi di punti non critici nel codice e cercando di ottimizzarli, il che influisce solo negativamente sul successivo debugging e supporto. Generalmente dovremmo dimenticare l'ottimizzazione, diciamo, nel 97% dei casi. Inoltre , l'ottimizzazione prematura è la radice di tutti i mali. Detto questo, dobbiamo prestare la massima attenzione al restante 3%." — Donald Knut
Ad esempio, l'aggiunta prematura di indici a un database. Perché è così male? Bene, è un male in quanto gli indici sono memorizzati come un albero binario. Di conseguenza, ogni volta che viene aggiunto e cancellato un nuovo valore, l'albero verrà ricalcolato e questo consuma risorse e tempo. Pertanto, gli indici dovrebbero essere aggiunti solo in caso di necessità urgente (se si dispone di una grande quantità di dati e le query richiedono troppo tempo) e solo per i campi più importanti (i campi che vengono interrogati più frequentemente).

10. Codice spaghetti

Il codice spaghetti è un anti-pattern definito da un codice mal strutturato, confuso e difficile da comprendere, contenente tutti i tipi di ramificazioni, come eccezioni di wrapping, condizioni e cicli. In precedenza, l'operatore goto era il principale alleato di questo anti-pattern. Le dichiarazioni Goto non sono più realmente utilizzate, il che elimina felicemente una serie di difficoltà e problemi associati.

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;
               }
           }
       }
   }
}
Sembra orribile, non è vero? Sfortunatamente, questo è l'anti-pattern più comune :( Anche la persona che scrive tale codice non sarà in grado di capirlo in futuro. Altri sviluppatori che vedranno il codice penseranno: "Bene, se funziona, allora va bene — è meglio non toccarlo". Spesso, un metodo è inizialmente semplice e molto trasparente, ma man mano che vengono aggiunti nuovi requisiti, il metodo viene gradualmente gravato da un numero sempre maggiore di affermazioni condizionali, trasformandolo in una mostruosità come questa. Se un tale metodo appare, è necessario rifattorizzare completamente o almeno le parti più confuse.In genere, durante la pianificazione di un progetto, il tempo viene assegnato per il refactoring, ad esempio, il 30% del tempo di sprint è per il refactoring e i test.Naturalmente, questo presuppone che non c'è fretta (ma quando mai succede).qui .

11. Numeri magici

I numeri magici sono un anti-pattern in cui tutti i tipi di costanti vengono utilizzati in un programma senza alcuna spiegazione del loro scopo o significato. Cioè, sono generalmente nominati male o, in casi estremi, non ci sono commenti che spiegano cosa sono i commenti o perché. Come il codice degli spaghetti, questo è uno degli anti-pattern più comuni. Qualcuno che non ha scritto il codice potrebbe avere o meno la minima idea dei numeri magici o di come funzionano (e col tempo l'autore stesso non sarà in grado di spiegarli). Di conseguenza, la modifica o la rimozione di un numero fa sì che il codice smetta magicamente di funzionare tutti insieme. Ad esempio, 36 e 73. Per combattere questo anti-pattern, consiglio una revisione del codice. Il tuo codice deve essere esaminato da sviluppatori che non sono coinvolti nelle sezioni pertinenti del codice. I loro occhi saranno freschi e avranno domande: cos'è questo e perché l'hai fatto? E, naturalmente, devi usare nomi esplicativi o lasciare commenti.

12. Programmazione copia e incolla

La programmazione copia e incolla è un anti-pattern in cui il codice di qualcun altro viene copiato e incollato sconsideratamente, con possibili effetti collaterali imprevisti. Ad esempio, copiare e incollare metodi con calcoli matematici o algoritmi complessi che non comprendiamo appieno. Potrebbe funzionare per il nostro caso particolare, ma in alcune altre circostanze potrebbe causare problemi. Supponiamo che io abbia bisogno di un metodo per determinare il numero massimo in un array. Rovistando in Internet, ho trovato questa soluzione:

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;
}
Otteniamo un array con i numeri 3, 6, 1, 4 e 2 e il metodo restituisce 6. Fantastico, teniamolo! Ma in seguito otteniamo un array composto da 2,5, -7, 2 e 3, e quindi il nostro risultato è -7. E questo risultato non va bene. Il problema qui è che Math.abs() restituisce il valore assoluto. L'ignoranza di ciò porta al disastro, ma solo in determinate situazioni. Senza una comprensione approfondita della soluzione, ci sono molti casi che non sarai in grado di verificare. Il codice copiato può anche andare oltre la struttura interna dell'applicazione, sia dal punto di vista stilistico che a un livello architettonico più fondamentale. Tale codice sarà più difficile da leggere e mantenere. E, naturalmente, non dobbiamo dimenticare che copiare direttamente il codice di qualcun altro è un tipo speciale di plagio.

13. Reinventare la ruota

Reinventare la ruota è un anti-modello, talvolta noto anche come reinventare la ruota quadrata. In sostanza, questo modello è l'opposto dell'anti-pattern copia e incolla considerato sopra. In questo anti-pattern, lo sviluppatore implementa la propria soluzione per un problema per il quale esistono già soluzioni. A volte queste soluzioni esistenti sono migliori di ciò che il programmatore inventa. Molto spesso, questo porta solo alla perdita di tempo e alla minore produttività: il programmatore potrebbe non trovare affatto una soluzione o potrebbe trovare una soluzione tutt'altro che migliore. Detto questo, non possiamo escludere la possibilità di creare una soluzione indipendente, perché farlo è una strada diretta per la programmazione copia e incolla. Il programmatore dovrebbe essere guidato dalle attività di programmazione specifiche che si presentano per risolverle in modo competente, utilizzando soluzioni già pronte o creando soluzioni personalizzate. Molto spesso, la ragione per usare questo anti-pattern è semplicemente la fretta. Il risultato è un'analisi superficiale di (ricerca di) soluzioni già pronte. Reinventare la ruota quadra è un caso in cui l'anti-pattern in esame ha un esito negativo. Cioè, il progetto richiede una soluzione personalizzata e lo sviluppatore lo crea, ma male. Allo stesso tempo, esiste già una buona opzione e altri la stanno utilizzando con successo. In conclusione: si perde un'enorme quantità di tempo. Per prima cosa, creiamo qualcosa che non funziona. Quindi proviamo a rifattorizzarlo e infine lo sostituiamo con qualcosa che già esisteva. Un esempio è l'implementazione della propria cache personalizzata quando esistono già molte implementazioni. Non importa quanto tu sia talentuoso come programmatore, dovresti ricordare che reinventare una ruota quadrata è come minimo una perdita di tempo. E, come sai, il tempo è la risorsa più preziosa.

14. Problema con lo yo-yo

Il problema yo-yo è un anti-pattern in cui la struttura dell'applicazione è eccessivamente complicata a causa di un'eccessiva frammentazione (ad esempio, una catena di ereditarietà eccessivamente suddivisa). Il "problema yo-yo" sorge quando è necessario comprendere un programma la cui gerarchia di ereditarietà è lunga e complessa, creando chiamate di metodo profondamente nidificate. Di conseguenza, i programmatori devono navigare tra molte classi e metodi diversi per ispezionare il comportamento del programma. Il nome di questo anti-pattern deriva dal nome del giocattolo. Ad esempio, diamo un'occhiata alla seguente catena di ereditarietà: Abbiamo un'interfaccia Technology:

public interface Technology {
   void turnOn();
}
L'interfaccia di trasporto lo eredita:

public interface Transport extends Technology {
   boolean fillUp();
}
E poi abbiamo un'altra interfaccia, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
E da lì, deriviamo una classe Car astratta:

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();
}
La prossima è la classe Volkswagen astratta:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
E infine, un modello specifico:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Questa catena ci costringe a cercare risposte a domande come:
  1. Quanti metodi VolkswagenAmarokha?

  2. Quale tipo deve essere inserito al posto del punto interrogativo per ottenere la massima astrazione:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
È difficile rispondere rapidamente a tali domande: ci richiede di dare un'occhiata e indagare ed è facile confondersi. E se la gerarchia fosse molto più ampia, più lunga e più complicata, con tutti i tipi di overload e override? La struttura che avremmo sarebbe oscurata a causa dell'eccessiva frammentazione. La soluzione migliore sarebbe ridurre le inutili divisioni. Nel nostro caso, lasceremmo Tecnologia → Auto → VolkswagenAmarok.

15. Complessità accidentale

La complessità non necessaria è un anti-modello in cui vengono introdotte complicazioni inutili per una soluzione.
"Qualsiasi sciocco può scrivere codice che un computer può capire. I bravi programmatori scrivono codice che gli umani possono capire." — Martin Fowler
Quindi cos'è la complessità? Può essere definito come il grado di difficoltà con cui ogni operazione viene eseguita nel programma. Di norma, la complessità può essere suddivisa in due tipi. Il primo tipo di complessità è il numero di funzioni che ha un sistema. Può essere ridotto in un solo modo: rimuovendo alcune funzioni. I metodi esistenti devono essere monitorati. Un metodo dovrebbe essere rimosso se non è più utilizzato o è ancora utilizzato ma senza apportare alcun valore. Inoltre, è necessario valutare come vengono utilizzati tutti i metodi nell'applicazione, per capire dove varrebbe la pena investire (molto riutilizzo del codice) e cosa si può dire di no. Il secondo tipo di complessità è la complessità non necessaria. Può essere curato solo attraverso un approccio professionale. Invece di fare qualcosa di "figo" (i giovani sviluppatori non sono gli unici sensibili a questa malattia), bisogna pensare a come farlo nel modo più semplice possibile, perché la soluzione migliore è sempre semplice. Ad esempio, supponiamo di avere piccole tabelle correlate con descrizioni di alcune entità, come un utente: Cosa sono gli anti-pattern?  Vediamo alcuni esempi (Parte 2) - 3Quindi, abbiamo l'id dell'utente, l'id della lingua in cui è fatta la descrizione e la descrizione stessa. Allo stesso modo, abbiamo descrittori ausiliari per le tabelle automobili, file, piani e clienti. Allora come sarebbe inserire nuovi valori in tali tabelle?

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 di conseguenza, abbiamo questo enum:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Sembra tutto semplice e buono... Ma per quanto riguarda gli altri metodi? In effetti, avranno anche tutti un mucchio di switchistruzioni e un mucchio di query di database quasi identiche, che a loro volta complicheranno e gonfieranno notevolmente la nostra classe. Come si potrebbe semplificare tutto questo? Aggiorniamo un po' il nostro 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;
}
Ora ogni tipo ha i nomi dei campi originali della sua tabella. Di conseguenza, il metodo per creare una descrizione diventa:

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);
   }
Comodo, semplice e compatto, non credi? L'indicazione di un buon sviluppatore non è nemmeno la frequenza con cui utilizza i pattern, ma piuttosto la frequenza con cui evita gli anti-pattern. L'ignoranza è il peggior nemico, perché devi conoscere i tuoi nemici di vista. Bene, questo è tutto quello che ho per oggi. Grazie a tutti! :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION