CodeGym /Blog Java /Aleatoriu /Ce sunt anti-modele? Să ne uităm la câteva exemple (Parte...
John Squirrels
Nivel
San Francisco

Ce sunt anti-modele? Să ne uităm la câteva exemple (Partea 2)

Publicat în grup
Ce sunt anti-modele? Să ne uităm la câteva exemple (Partea 1) Astăzi continuăm revizuirea celor mai populare anti-modele. Dacă ai ratat prima parte, iată- o. Ce sunt anti-modele?  Să ne uităm la câteva exemple (Partea 2) - 1Deci, modelele de design sunt cele mai bune practici. Cu alte cuvinte, sunt exemple de modalități bune, testate în timp, de a rezolva probleme specifice. La rândul lor, anti-patternele sunt exact opusul lor, în sensul că sunt tipare de capcane sau greșeli la rezolvarea diverselor probleme (modeluri malefice). Să trecem la următorul anti-model de dezvoltare de software.

8. Ciocan de aur

Un ciocan de aur este un anti-model definit de încrederea că o anumită soluție este aplicabilă universal. Exemple:
  1. După ce a întâmpinat o problemă și a găsit un model pentru soluția perfectă, un programator încearcă să lipească acest tipar peste tot, aplicând-l la proiectele actuale și viitoare, în loc să caute soluțiile adecvate pentru cazuri specifice.

  2. Unii dezvoltatori și-au creat odată propria variantă a unui cache pentru o situație specifică (pentru că nimic altceva nu era potrivit). Mai târziu, la următorul proiect care nu a implicat nicio logică de cache specială, au folosit din nou varianta lor în loc să folosească biblioteci gata făcute (de exemplu, Ehcache). Rezultatul a fost o grămadă de bug-uri și incompatibilități, precum și mult timp pierdut și nervi prăjiți.

    Oricine se poate îndrăgosti de acest anti-model. Dacă sunteți începător, este posibil să nu aveți cunoștințe despre modelele de design. Acest lucru vă poate determina să încercați să rezolvați toate problemele într-un singur mod pe care l-ați stăpânit. Dacă vorbim de profesioniști, atunci numim această deformare profesională sau nerdview. Aveți propriile modele de design preferate și, în loc să îl folosiți pe cel potrivit, îl folosiți pe cel preferat, presupunând că o potrivire bună în trecut garantează același rezultat în viitor.

    Această capcană poate produce rezultate foarte triste - de la o implementare proastă, instabilă și dificil de menținut până la un eșec complet al proiectului. Așa cum nu există o singură pastilă pentru toate bolile, nu există un singur model de design pentru toate ocaziile.

9. Optimizare prematură

Optimizarea prematură este un anti-model al cărui nume vorbește de la sine.
„Programatorii petrec o cantitate imensă de timp gândindu-se și îngrijorându-se cu privire la locurile necritice din cod și încercând să le optimizeze, ceea ce afectează doar negativ depanarea și suportul ulterioare. În general, ar trebui să uităm de optimizare în, să zicem, 97% din cazuri. Mai mult decât atât. , optimizarea prematură este rădăcina tuturor relelor. Acestea fiind spuse, trebuie să acordăm toată atenția celor 3% rămase." — Donald Knuth
De exemplu, adăugarea prematură a indecșilor la o bază de date. De ce este atât de rău? Ei bine, este rău prin aceea că indicii sunt stocați ca un arbore binar. Ca urmare, de fiecare dată când o nouă valoare este adăugată și ștearsă, arborele va fi recalculat, iar acest lucru consumă resurse și timp. Prin urmare, indexurile trebuie adăugate numai atunci când există o nevoie urgentă (dacă aveți o cantitate mare de date și interogările durează prea mult) și numai pentru câmpurile cele mai importante (câmpurile care sunt interogate cel mai frecvent).

10. Cod spaghete

Codul spaghetti este un anti-model definit de cod care este prost structurat, confuz și greu de înțeles, care conține tot felul de ramificații, cum ar fi excepțiile de împachetare, condiții și bucle. Anterior, operatorul goto era principalul aliat al acestui anti-pattern. Declarațiile Goto nu mai sunt folosite cu adevărat, ceea ce elimină din fericire o serie de dificultăți și probleme asociate.

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;
               }
           }
       }
   }
}
Arată îngrozitor, nu-i așa? Din păcate, acesta este cel mai comun anti-model :( Nici măcar persoana care scrie un astfel de cod nu va putea să-l înțeleagă în viitor. Alți dezvoltatori care văd codul se vor gândi: „Ei bine, dacă funcționează, atunci bine - este mai bine să nu o atingeți". Adesea, o metodă este inițial simplă și foarte transparentă, dar pe măsură ce se adaugă noi cerințe, metoda este încărcată treptat cu tot mai multe enunțuri condiționate, transformând-o într-o monstruozitate ca aceasta. Dacă o astfel de metodă apare, trebuie să-l refactorizezi fie complet, fie cel puțin părțile cele mai confuze. De obicei, atunci când programați un proiect, timpul este alocat pentru refactorizare, de exemplu, 30% din timpul de sprint este pentru refactorizare și teste. Desigur, acest lucru presupune că nu există nicio grabă (dar când se întâmplă asta vreodată).aici .

11. Numere magice

Numerele magice este un anti-model în care într-un program sunt folosite tot felul de constante fără nicio explicație a scopului sau semnificației lor. Adică sunt, în general, prost numite sau în cazuri extreme, nu există niciun comentariu care să explice care sunt comentariile sau de ce. La fel ca codul de spaghete, acesta este unul dintre cele mai comune anti-modele. Cineva care nu a scris codul poate avea sau nu idee despre numerele magice sau despre modul în care acestea funcționează (și în timp, autorul însuși nu le va putea explica). Ca rezultat, schimbarea sau eliminarea unui număr face ca codul să nu mai funcționeze împreună. De exemplu, 36 și 73. Pentru a combate acest anti-pattern, recomand o revizuire a codului. Codul dvs. trebuie să fie examinat de dezvoltatori care nu sunt implicați în secțiunile relevante ale codului. Ochii lor vor fi proaspeți și vor avea întrebări: ce este asta și de ce ai făcut asta? Și, desigur, trebuie să folosiți nume explicative sau să lăsați comentarii.

12. Programare copiere și lipire

Programarea prin copiere și inserare este un anti-model în care codul altcuiva este copiat și lipit fără gânduri, ceea ce poate duce la efecte secundare neprevăzute. De exemplu, metode de copiere și lipire cu calcule matematice sau algoritmi complecși pe care nu îi înțelegem pe deplin. Poate funcționa pentru cazul nostru particular, dar în alte circumstanțe ar putea duce la probleme. Să presupunem că am nevoie de o metodă pentru a determina numărul maxim dintr-o matrice. Scotocind pe internet, am găsit această soluție:

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;
}
Obținem o matrice cu numerele 3, 6, 1, 4 și 2, iar metoda returnează 6. Grozav, să o păstrăm! Dar mai târziu obținem o matrice formată din 2,5, -7, 2 și 3, iar apoi rezultatul nostru este -7. Și acest rezultat nu este bun. Problema aici este că Math.abs() returnează valoarea absolută. Ignorarea acestui lucru duce la dezastru, dar numai în anumite situații. Fără o înțelegere aprofundată a soluției, există multe cazuri pe care nu le veți putea verifica. Codul copiat poate depăși, de asemenea, structura internă a aplicației, atât din punct de vedere stilistic, cât și la un nivel mai fundamental, arhitectural. Un astfel de cod va fi mai dificil de citit și întreținut. Și, desigur, nu trebuie să uităm că copiarea directă a codului altcuiva este un tip special de plagiat.

13. Reinventarea roții

Reinventarea roții este un anti-model, cunoscut și sub numele de reinventarea roții pătrate. În esență, acest șablon este opusul anti-modelului de tip copy-and-paste considerat mai sus. În acest anti-pattern, dezvoltatorul implementează propria soluție pentru o problemă pentru care există deja soluții. Uneori, aceste soluții existente sunt mai bune decât ceea ce inventează programatorul. Cel mai adesea, acest lucru duce doar la timp pierdut și la o productivitate mai scăzută: programatorul poate să nu găsească deloc o soluție sau poate găsi o soluție care este departe de cea mai bună. Acestea fiind spuse, nu putem exclude posibilitatea de a crea o soluție independentă, deoarece a face asta este un drum direct către programare copy-and-paste. Programatorul trebuie să fie ghidat de sarcinile de programare specifice care apar pentru a le rezolva în mod competent, fie prin utilizarea de soluții gata făcute, fie prin crearea de soluții personalizate. Foarte des, motivul folosirii acestui anti-model este pur și simplu graba. Rezultatul este o analiză superficială a (căutării) soluțiilor gata făcute. Reinventarea roții pătrate este un caz în care anti-modelul luat în considerare are un rezultat negativ. Adică, proiectul necesită o soluție personalizată, iar dezvoltatorul o creează, dar prost. În același timp, o opțiune bună există deja și alții o folosesc cu succes. Concluzia: se pierde o cantitate imensă de timp. În primul rând, creăm ceva care nu funcționează. Apoi încercăm să-l refactorăm și, în cele din urmă, îl înlocuim cu ceva care exista deja. Un exemplu este implementarea propriului cache personalizat atunci când există deja o mulțime de implementări. Indiferent cât de talentat ai fi ca programator, ar trebui să reții că reinventarea unei roți pătrate este cel puțin o pierdere de timp. Și, după cum știți, timpul este cea mai valoroasă resursă.

14. Problema yo-yo

Problema yo-yo este un anti-pattern în care structura aplicației este excesiv de complicată din cauza fragmentării excesive (de exemplu, un lanț de moștenire subdivizat excesiv). „Problema yo-yo” apare atunci când trebuie să înțelegeți un program a cărui ierarhie de moștenire este lungă și complexă, creând apeluri de metodă profund imbricate. Ca rezultat, programatorii trebuie să navigheze între multe clase și metode diferite pentru a inspecta comportamentul programului. Numele acestui anti-model provine de la numele jucăriei. Ca exemplu, să ne uităm la următorul lanț de moștenire: Avem o interfață de tehnologie:

public interface Technology {
   void turnOn();
}
Interfața de transport o moștenește:

public interface Transport extends Technology {
   boolean fillUp();
}
Și apoi avem o altă interfață, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
Și de acolo, derivăm o clasă abstractă 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();
}
Urmează clasa Volkswagen abstractă:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
Și, în sfârșit, un model specific:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Acest lanț ne obligă să căutăm răspunsuri la întrebări precum:
  1. Câte metode VolkswagenAmarokare?

  2. Ce tip ar trebui inserat în locul semnului de întrebare pentru a obține o abstractizare maximă:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
Este dificil să răspundem rapid la astfel de întrebări – ne cere să aruncăm o privire și să investigăm și este ușor să ne confuzi. Și dacă ierarhia este mult mai mare, mai lungă și mai complicată, cu tot felul de supraîncărcări și suprascrieri? Structura pe care am avea-o ar fi ascunsă din cauza fragmentării excesive. Cea mai bună soluție ar fi reducerea diviziunilor inutile. În cazul nostru, am părăsi Tehnologie → Mașină → VolkswagenAmarok.

15. Complexitate accidentală

Complexitatea inutilă este un anti-model în care complicațiile inutile sunt introduse într-o soluție.
„Orice prost poate scrie cod pe care un computer îl poate înțelege. Programatorii buni scriu cod pe care oamenii îl pot înțelege.” — Martin Fowler
Deci, ce este complexitatea? Poate fi definit ca gradul de dificultate cu care fiecare operatie este efectuata in program. De regulă, complexitatea poate fi împărțită în două tipuri. Primul tip de complexitate este numărul de funcții pe care le are un sistem. Poate fi redus doar într-un singur mod - prin eliminarea unei anumite funcții. Metodele existente trebuie monitorizate. O metodă ar trebui eliminată dacă nu mai este folosită sau este încă folosită, dar fără a aduce vreo valoare. Mai mult, trebuie să evaluezi cum sunt folosite toate metodele din aplicație, pentru a înțelege unde ar merita investițiile (multă reutilizare a codului) și la ce poți spune nu. Al doilea tip de complexitate este complexitatea inutilă. Se poate vindeca doar printr-o abordare profesională. În loc să faci ceva „mișto” (Tinerii dezvoltatori nu sunt singurii sensibili la această boală), trebuie să vă gândiți cum să o faceți cât mai simplu posibil, deoarece cea mai bună soluție este întotdeauna simplă. De exemplu, să presupunem că avem mici tabele înrudite cu descrieri ale unor entități, cum ar fi un utilizator: Ce sunt anti-modele?  Să ne uităm la câteva exemple (Partea 2) - 3Deci, avem id-ul utilizatorului, id-ul limbii în care este făcută descrierea și descrierea în sine. În mod similar, avem descriptori auxiliari pentru mașini, fișiere, planuri și tabelele clienți. Atunci cum ar arăta să inserăm noi valori în astfel de tabele?

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();
   }
}
Și, în consecință, avem această enumerare:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Totul pare a fi simplu și bun... Dar cum rămâne cu celelalte metode? Într-adevăr, toți vor avea, de asemenea, o grămadă de switchdeclarații și o grămadă de interogări de baze de date aproape identice, care, la rândul lor, vor complica foarte mult și vor umfla foarte mult clasa noastră. Cum ar putea fi mai ușor toate acestea? Să facem upgrade puțin enumerarea noastră:

@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;
}
Acum fiecare tip are numele câmpurilor originale ale tabelului său. Ca urmare, metoda de creare a unei descrieri devine:

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);
   }
Convenabil, simplu și compact, nu crezi? Indicația unui dezvoltator bun nu este cât de des folosește modele, ci mai degrabă cât de des evită anti-modele. Ignoranța este cel mai rău dușman, pentru că trebuie să-ți cunoști dușmanii din vedere. Ei bine, asta e tot ce am pentru azi. Vă mulțumesc tuturor! :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION