CodeGym /Java Blog /Willekeurig /Wat zijn antipatronen? Laten we enkele voorbeelden bekijk...
John Squirrels
Niveau 41
San Francisco

Wat zijn antipatronen? Laten we enkele voorbeelden bekijken (deel 2)

Gepubliceerd in de groep Willekeurig
Wat zijn antipatronen? Laten we naar enkele voorbeelden kijken (deel 1) Vandaag vervolgen we onze bespreking van de meest populaire antipatronen. Als je het eerste deel hebt gemist, hier is het dan. Wat zijn antipatronen?  Laten we een paar voorbeelden bekijken (deel 2) - 1Ontwerppatronen zijn dus best practices. Met andere woorden, het zijn voorbeelden van goede, beproefde manieren om specifieke problemen op te lossen. Antipatronen zijn op hun beurt precies het tegenovergestelde, in die zin dat het patronen van valkuilen of fouten zijn bij het oplossen van verschillende problemen (kwaadaardige patronen). Laten we doorgaan naar het volgende antipatroon voor softwareontwikkeling.

8. Gouden hamer

Een gouden hamer is een antipatroon dat wordt gedefinieerd door het vertrouwen dat een bepaalde oplossing universeel toepasbaar is. Voorbeelden:
  1. Nadat hij een probleem is tegengekomen en een patroon voor de perfecte oplossing heeft gevonden, probeert een programmeur dit patroon overal te plakken en toe te passen op huidige en alle toekomstige projecten, in plaats van te zoeken naar de juiste oplossingen voor specifieke gevallen.

  2. Sommige ontwikkelaars hebben ooit hun eigen variant van een cache gemaakt voor een specifieke situatie (omdat niets anders geschikt was). Later, bij het volgende project zonder speciale cachelogica, gebruikten ze hun variant opnieuw in plaats van kant-en-klare bibliotheken (bijvoorbeeld Ehcache). Het resultaat was een hoop bugs en onverenigbaarheden, evenals veel verspilde tijd en gefrituurde zenuwen.

    Iedereen kan vallen voor dit anti-patroon. Als je een beginner bent, heb je misschien geen kennis van ontwerppatronen. Dit kan ertoe leiden dat je probeert alle problemen op te lossen op de manier die je onder de knie hebt. Als we het over professionals hebben, dan noemen we dit beroepsvervorming of nerdview. U heeft uw eigen favoriete ontwerppatronen en in plaats van de juiste te gebruiken, gebruikt u uw favoriet, ervan uitgaande dat een goede pasvorm in het verleden hetzelfde resultaat in de toekomst garandeert.

    Deze valkuil kan zeer trieste resultaten opleveren - van een slechte, onstabiele en moeilijk te onderhouden implementatie tot een volledige mislukking van het project. Net zoals er niet één pil is voor alle ziekten, is er ook niet één ontwerppatroon voor alle gelegenheden.

9. Voortijdige optimalisatie

Voortijdige optimalisatie is een anti-patroon waarvan de naam voor zichzelf spreekt.
"Programmeurs besteden enorm veel tijd aan het nadenken over en zich zorgen maken over niet-kritieke plaatsen in de code en proberen deze te optimaliseren, wat de daaropvolgende debugging en ondersteuning alleen maar negatief beïnvloedt. Over het algemeen zouden we optimalisatie in bijvoorbeeld 97% van de gevallen moeten vergeten. Bovendien , voortijdige optimalisatie is de wortel van alle kwaad. Dat gezegd hebbende, moeten we alle aandacht besteden aan de resterende 3%." — Donald Knuth
Bijvoorbeeld het voortijdig toevoegen van indexen aan een database. Waarom is dat erg? Nou, dat is slecht, indexen worden opgeslagen als een binaire boom. Het resultaat is dat elke keer dat een nieuwe waarde wordt toegevoegd en verwijderd, de boom opnieuw wordt berekend, wat middelen en tijd kost. Indexen moeten daarom alleen worden toegevoegd als er een dringende behoefte aan is (als u een grote hoeveelheid gegevens heeft en query's te lang duren) en alleen voor de belangrijkste velden (de velden die het vaakst worden opgevraagd).

10. Spaghetticode

Spaghetticode is een anti-patroon dat wordt gedefinieerd door code die slecht gestructureerd, verwarrend en moeilijk te begrijpen is en allerlei vertakkingen bevat, zoals uitzonderingen voor het omwikkelen, voorwaarden en lussen. Voorheen was de goto-operator de belangrijkste bondgenoot van dit anti-patroon. Goto-statements worden eigenlijk niet meer gebruikt, wat gelukkig een aantal bijbehorende moeilijkheden en problemen elimineert.

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;
               }
           }
       }
   }
}
Het ziet er vreselijk uit, nietwaar? Helaas is dit het meest voorkomende antipatroon :( Zelfs de persoon die dergelijke code schrijft, zal het in de toekomst niet meer kunnen begrijpen. Andere ontwikkelaars die de code zien, zullen denken: "Nou, als het werkt, dan is het goed - it's better not touch it". Vaak is een methode in het begin eenvoudig en zeer transparant, maar naarmate er nieuwe vereisten worden toegevoegd, wordt de methode geleidelijk opgezadeld met steeds meer voorwaardelijke uitspraken, waardoor het een wangedrocht als dit wordt. Als zo'n methode verschijnt, moet u het volledig herstructureren of in ieder geval de meest verwarrende delen.Typisch wordt bij het plannen van een project tijd toegewezen voor refactoring, bijvoorbeeld 30% van de sprinttijd is voor refactoring en tests.Dit veronderstelt natuurlijk dat er geen haast is (maar wanneer gebeurt dat ooit).hier .

11. Magische getallen

Magische getallen zijn een anti-patroon waarin allerlei constanten in een programma worden gebruikt zonder enige uitleg van hun doel of betekenis. Dat wil zeggen, ze hebben over het algemeen een slechte naam of in extreme gevallen is er geen commentaar waarin wordt uitgelegd wat de opmerkingen zijn of waarom. Net als spaghetticode is dit een van de meest voorkomende antipatronen. Iemand die de code niet heeft geschreven, heeft misschien geen idee van de magische getallen of hoe ze werken (en na verloop van tijd zal de auteur ze zelf niet meer kunnen uitleggen). Als gevolg hiervan zorgt het wijzigen of verwijderen van een nummer ervoor dat de code op magische wijze stopt met werken. Bijvoorbeeld 36 en 73. Om dit antipatroon te bestrijden, raad ik een codereview aan. Uw code moet worden bekeken door ontwikkelaars die niet betrokken zijn bij de relevante delen van de code. Hun ogen zullen fris zijn en ze zullen vragen hebben: wat is dit en waarom deed je dat? En natuurlijk moet u verklarende namen gebruiken of opmerkingen achterlaten.

12. Programmeren kopiëren en plakken

Copy-and-paste-programmering is een antipatroon waarbij de code van iemand anders gedachteloos wordt gekopieerd en geplakt, met mogelijk onverwachte neveneffecten tot gevolg. Bijvoorbeeld methoden kopiëren en plakken met wiskundige berekeningen of complexe algoritmen die we niet helemaal begrijpen. Het kan in ons specifieke geval werken, maar in sommige andere omstandigheden kan het tot problemen leiden. Stel dat ik een methode nodig heb om het maximale aantal in een array te bepalen. Zoekend op internet vond ik deze oplossing:

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;
}
We krijgen een array met de getallen 3, 6, 1, 4 en 2, en de methode geeft 6 terug. Mooi, laten we het houden! Maar later krijgen we een array bestaande uit 2,5, -7, 2 en 3, en dan is ons resultaat -7. En dit resultaat is niet goed. Het probleem hier is dat Math.abs() de absolute waarde retourneert. Onwetendheid hiervan leidt tot rampspoed, maar alleen in bepaalde situaties. Zonder een diepgaand begrip van de oplossing zijn er veel gevallen die u niet kunt verifiëren. Gekopieerde code kan ook verder gaan dan de interne structuur van de applicatie, zowel stilistisch als op een meer fundamenteel, architectonisch niveau. Dergelijke code zal moeilijker te lezen en te onderhouden zijn. En natuurlijk mogen we niet vergeten dat het zomaar kopiëren van andermans code een bijzondere vorm van plagiaat is.

13. Het wiel opnieuw uitvinden

Het wiel opnieuw uitvinden is een anti-patroon, ook wel bekend als het opnieuw uitvinden van het vierkante wiel. In wezen is dit sjabloon het tegenovergestelde van het hierboven besproken anti-patroon voor kopiëren en plakken. In dit anti-patroon implementeert de ontwikkelaar zijn of haar eigen oplossing voor een probleem waarvoor al oplossingen bestaan. Soms zijn deze bestaande oplossingen beter dan wat de programmeur bedenkt. Meestal leidt dit alleen maar tot tijdverlies en lagere productiviteit: de programmeur vindt mogelijk helemaal geen oplossing of vindt een oplossing die verre van de beste is. Dat gezegd hebbende, kunnen we de mogelijkheid niet uitsluiten om een ​​onafhankelijke oplossing te creëren, omdat dit een directe weg is naar kopiëren en plakken van programmeren. De programmeur moet zich laten leiden door de specifieke programmeertaken die zich voordoen om deze competent op te lossen, hetzij door gebruik te maken van kant-en-klare oplossingen, hetzij door aangepaste oplossingen te creëren. Heel vaak, de reden voor het gebruik van dit anti-patroon is gewoon haast. Het resultaat is een oppervlakkige analyse van (zoeken naar) pasklare oplossingen. Het opnieuw uitvinden van het vierkante wiel is een geval waarin het anti-patroon in kwestie een negatief resultaat heeft. Dat wil zeggen, het project vereist een aangepaste oplossing en de ontwikkelaar maakt het, maar slecht. Tegelijkertijd bestaat er al een goede optie en maken anderen daar met succes gebruik van. Kortom: er gaat enorm veel tijd verloren. Eerst maken we iets dat niet werkt. Vervolgens proberen we het te herstructureren en uiteindelijk vervangen we het door iets dat al bestond. Een voorbeeld is het implementeren van uw eigen aangepaste cache terwijl er al veel implementaties zijn. Hoe getalenteerd je ook bent als programmeur, onthoud dat het opnieuw uitvinden van een vierkant wiel op zijn minst tijdverspilling is. En zoals u weet, is tijd de meest waardevolle hulpbron.

14. Jojo-probleem

Het jojo-probleem is een anti-patroon waarbij de structuur van de toepassing te gecompliceerd is vanwege overmatige fragmentatie (bijvoorbeeld een overmatig onderverdeelde overervingsketen). Het "jojo-probleem" doet zich voor wanneer u een programma moet begrijpen waarvan de overervingshiërarchie lang en complex is, waardoor diep geneste methodeaanroepen ontstaan. Als gevolg hiervan moeten programmeurs tussen veel verschillende klassen en methoden navigeren om het gedrag van het programma te inspecteren. De naam van dit anti-patroon komt van de naam van het speelgoed. Laten we als voorbeeld de volgende overervingsketen bekijken: We hebben een technologie-interface:

public interface Technology {
   void turnOn();
}
De Transport-interface erft het:

public interface Transport extends Technology {
   boolean fillUp();
}
En dan hebben we nog een interface, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
En van daaruit leiden we een abstracte autoklasse af:

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();
}
Het volgende is de abstracte Volkswagen-klasse:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
En tot slot een specifiek model:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Deze ketting dwingt ons om op zoek te gaan naar antwoorden op vragen als:
  1. Hoeveel methoden VolkswagenAmarokheeft?

  2. Welk type moet worden ingevoegd in plaats van het vraagteken om maximale abstractie te bereiken:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
Het is moeilijk om dergelijke vragen snel te beantwoorden - het vereist dat we een kijkje nemen en onderzoeken, en het is gemakkelijk om in de war te raken. En wat als de hiërarchie veel groter, langer en gecompliceerder is, met allerlei overloads en overrides? De structuur die we zouden hebben, zou worden verdoezeld door overmatige versnippering. De beste oplossing zou zijn om de onnodige verdeeldheid te verminderen. In ons geval zouden we Technologie → Auto → VolkswagenAmarok verlaten.

15. Toevallige complexiteit

Onnodige complexiteit is een anti-patroon waarin nodeloze complicaties aan een oplossing worden toegevoegd.
"Elke dwaas kan code schrijven die een computer kan begrijpen. Goede programmeurs schrijven code die mensen kunnen begrijpen." —Martin Fowler
Dus wat is complexiteit? Het kan worden gedefinieerd als de moeilijkheidsgraad waarmee elke bewerking in het programma wordt uitgevoerd. In de regel kan complexiteit in twee typen worden verdeeld. De eerste soort complexiteit is het aantal functies dat een systeem heeft. Het kan maar op één manier worden verminderd: door een functie te verwijderen. De bestaande methoden moeten worden gemonitord. Een methode moet worden verwijderd als deze niet meer wordt gebruikt of nog steeds wordt gebruikt, maar geen waarde oplevert. Bovendien moet u beoordelen hoe alle methoden in de applicatie worden gebruikt, om te begrijpen waar investeringen de moeite waard zijn (veel hergebruik van code) en waar u nee tegen kunt zeggen. Het tweede type complexiteit is onnodige complexiteit. Het kan alleen worden genezen door een professionele aanpak. In plaats van iets "cool" te doen (jonge ontwikkelaars zijn niet de enigen die vatbaar zijn voor deze ziekte), je moet nadenken over hoe je het zo eenvoudig mogelijk kunt doen, want de beste oplossing is altijd eenvoudig. Stel dat we kleine gerelateerde tabellen hebben met beschrijvingen van bepaalde entiteiten, zoals een gebruiker: Wat zijn antipatronen?  Laten we enkele voorbeelden bekijken (deel 2) - 3We hebben dus de id van de gebruiker, de id van de taal waarin de beschrijving is gemaakt en de beschrijving zelf. Evenzo hebben we aanvullende descriptoren voor de auto's, bestanden, plattegronden en klantentabellen. Hoe zou het er dan uitzien om nieuwe waarden in dergelijke tabellen in te voegen?

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();
   }
}
En dienovereenkomstig hebben we deze opsomming:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Alles lijkt eenvoudig en goed... Maar hoe zit het met de andere methodes? Ze zullen inderdaad ook allemaal een heleboel switchstatements en een heleboel bijna identieke databasequery's hebben, die op hun beurt onze klas enorm zullen compliceren en opzwellen. Hoe zou dit allemaal makkelijker gemaakt kunnen worden? Laten we onze opsomming een beetje upgraden:

@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;
}
Elk type heeft nu de namen van de oorspronkelijke velden van de tabel. Als gevolg hiervan wordt de methode voor het maken van een beschrijving:

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);
   }
Handig, eenvoudig en compact, vind je ook niet? De indicatie van een goede ontwikkelaar is niet eens hoe vaak hij of zij patronen gebruikt, maar eerder hoe vaak hij of zij antipatronen vermijdt. Onwetendheid is de ergste vijand, omdat je je vijanden van gezicht moet kennen. Nou, dat is alles wat ik heb voor vandaag. Bedankt iedereen! :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION