CodeGym /Blog Java /Random-PL /Czym są antywzorce? Spójrzmy na kilka przykładów (część 2...
John Squirrels
Poziom 41
San Francisco

Czym są antywzorce? Spójrzmy na kilka przykładów (część 2)

Opublikowano w grupie Random-PL
Czym są antywzorce? Spójrzmy na kilka przykładów (Część 1) Dzisiaj kontynuujemy przegląd najpopularniejszych antywzorców. Jeśli przegapiłeś pierwszą część, oto ona. Czym są antywzorce?  Spójrzmy na kilka przykładów (część 2) - 1Zatem wzorce projektowe to najlepsze praktyki. Innymi słowy, są przykładami dobrych, sprawdzonych sposobów rozwiązywania konkretnych problemów. Z kolei antywzorce są ich dokładnym przeciwieństwem, w tym sensie, że są wzorami pułapek lub błędów przy rozwiązywaniu różnych problemów (złe wzorce). Przejdźmy do następnego antywzorca wytwarzania oprogramowania.

8. Złoty młot

Złoty młotek to antywzorzec określony przez pewność, że dane rozwiązanie ma uniwersalne zastosowanie. Przykłady:
  1. Po napotkaniu problemu i znalezieniu wzorca idealnego rozwiązania, programista stara się wkleić ten wzorzec wszędzie, stosując go w bieżących i wszystkich przyszłych projektach, zamiast szukać odpowiednich rozwiązań dla konkretnych przypadków.

  2. Niektórzy programiści stworzyli kiedyś własny wariant pamięci podręcznej dla określonej sytuacji (ponieważ nic innego nie było odpowiednie). Później, w kolejnym projekcie, który nie obejmował żadnej specjalnej logiki pamięci podręcznej, ponownie użyli swojego wariantu zamiast korzystania z gotowych bibliotek (na przykład Ehcache). Rezultatem była masa błędów i niezgodności, a także mnóstwo zmarnowanego czasu i smażonych nerwów.

    Każdy może zakochać się w tym antywzorcu. Jeśli jesteś początkującym, możesz nie mieć wiedzy na temat wzorców projektowych. Może to skłonić cię do próby rozwiązania wszystkich problemów w jeden sposób, który opanowałeś. Jeśli mówimy o profesjonalistach, nazywamy to profesjonalną deformacją lub nerdview. Masz swoje preferowane wzorce projektowe i zamiast używać właściwego, używasz swojego ulubionego, wychodząc z założenia, że ​​dobre dopasowanie w przeszłości gwarantuje taki sam rezultat w przyszłości.

    Ta pułapka może przynieść bardzo smutne skutki — od złej, niestabilnej i trudnej w utrzymaniu implementacji do całkowitej porażki projektu. Tak jak nie ma jednej pigułki na wszystkie choroby, tak nie ma jednego wzoru na każdą okazję.

9. Przedwczesna optymalizacja

Przedwczesna optymalizacja to antywzorzec, którego nazwa mówi sama za siebie.
„Programiści spędzają ogromną ilość czasu myśląc i martwiąc się o niekrytyczne miejsca w kodzie i próbując je zoptymalizować, co tylko negatywnie wpływa na późniejsze debugowanie i wsparcie. Generalnie o optymalizacji powinniśmy zapomnieć w powiedzmy 97% przypadków. Co więcej , przedwczesna optymalizacja jest źródłem wszelkiego zła. To powiedziawszy, musimy zwrócić całą uwagę na pozostałe 3%”. — Donalda Knuta
Na przykład przedwczesne dodawanie indeksów do bazy danych. Dlaczego to jest złe? Cóż, jest źle, ponieważ indeksy są przechowywane jako drzewo binarne. W rezultacie za każdym razem, gdy nowa wartość jest dodawana i usuwana, drzewo zostanie przeliczone, co pochłania zasoby i czas. Dlatego indeksy należy dodawać tylko wtedy, gdy zachodzi pilna potrzeba (jeśli masz dużą ilość danych, a zapytania trwają zbyt długo) i tylko dla najważniejszych pól (pola, które są najczęściej odpytywane).

10. Kod spaghetti

Kod spaghetti to antywzorzec zdefiniowany przez kod o słabej strukturze, zagmatwany i trudny do zrozumienia, zawierający wszelkiego rodzaju rozgałęzienia, takie jak zawijanie wyjątków, warunków i pętli. Poprzednio operator goto był głównym sprzymierzeńcem tego antywzorca. Instrukcje Goto nie są już tak naprawdę używane, co szczęśliwie eliminuje szereg związanych z tym trudności i problemów.

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;
               }
           }
       }
   }
}
Wygląda okropnie, prawda? Niestety, jest to najczęstszy antywzorzec :( Nawet osoba, która napisze taki kod, nie będzie w stanie go w przyszłości zrozumieć. Inni programiści, którzy zobaczą kod, pomyślą: „Cóż, jeśli to działa, to dobrze — lepiej tego nie dotykać”. Często metoda jest początkowo prosta i bardzo przejrzysta, ale w miarę dodawania nowych wymagań metoda jest stopniowo obarczana coraz większą liczbą instrukcji warunkowych, zamieniając ją w takie monstrum. Jeśli taka metoda pojawi się komunikat, musisz zrefaktoryzować go całkowicie lub przynajmniej najbardziej zagmatwane części. Zazwyczaj podczas planowania projektu przeznacza się czas na refaktoryzację, na przykład 30% czasu sprintu jest przeznaczone na refaktoryzację i testy. Oczywiście przy założeniu, że że nie ma pośpiechu (ale kiedy to się w ogóle zdarza).tutaj .

11. Magiczne liczby

Liczby magiczne to antywzorzec, w którym w programie używane są wszelkiego rodzaju stałe bez żadnego wyjaśnienia ich celu lub znaczenia. Oznacza to, że generalnie są źle nazwane lub w skrajnych przypadkach nie ma komentarza wyjaśniającego, czym są komentarze i dlaczego. Podobnie jak kod spaghetti, jest to jeden z najpopularniejszych antywzorców. Ktoś, kto nie napisał kodu, może mieć pojęcie o magicznych liczbach lub ich działaniu (a z czasem sam autor nie będzie w stanie ich wyjaśnić). W rezultacie zmiana lub usunięcie numeru powoduje, że kod magicznie przestaje działać razem. Na przykład 36 i 73. Aby zwalczyć ten antywzorzec, polecam przegląd kodu. Twój kod musi zostać przejrzany przez programistów, którzy nie są zaangażowani w odpowiednie sekcje kodu. Ich oczy będą świeże i będą mieć pytania: co to jest i dlaczego to zrobiłeś? I oczywiście musisz używać nazw wyjaśniających lub zostawiać komentarze.

12. Programowanie kopiuj-wklej

Programowanie typu „kopiuj i wklej” to antywzorzec, w którym cudzy kod jest bezmyślnie kopiowany i wklejany, co może skutkować nieoczekiwanymi efektami ubocznymi. Na przykład kopiowanie i wklejanie metod z obliczeniami matematycznymi lub złożonymi algorytmami, których nie do końca rozumiemy. Może to zadziałać w naszym konkretnym przypadku, ale w niektórych innych okolicznościach może prowadzić do kłopotów. Załóżmy, że potrzebuję metody określania maksymalnej liczby w tablicy. Szperając w internecie znalazłem takie rozwiązanie:

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;
}
Otrzymujemy tablicę z liczbami 3, 6, 1, 4 i 2, a metoda zwraca 6. Świetnie, zostawmy to! Ale później otrzymujemy tablicę składającą się z 2,5, -7, 2 i 3, a następnie naszym wynikiem jest -7. I ten wynik nie jest dobry. Problem polega na tym, że Math.abs() zwraca wartość bezwzględną. Nieznajomość tego prowadzi do katastrofy, ale tylko w określonych sytuacjach. Bez dogłębnego zrozumienia rozwiązania jest wiele przypadków, których nie będziesz w stanie zweryfikować. Skopiowany kod może również wykraczać poza wewnętrzną strukturę aplikacji, zarówno stylistycznie, jak i na bardziej fundamentalnym, architektonicznym poziomie. Taki kod będzie trudniejszy do odczytania i utrzymania. I oczywiście nie wolno nam zapominać, że bezpośrednie skopiowanie czyjegoś kodu jest szczególnym rodzajem plagiatu.

13. Wymyślanie koła na nowo

Ponowne wynalezienie koła to antywzorzec, czasami znany również jako ponowne wynalezienie kwadratowego koła. Zasadniczo ten szablon jest przeciwieństwem antywzorca kopiuj i wklej omówionego powyżej. W tym antywzorcu programista implementuje własne rozwiązanie problemu, dla którego rozwiązania już istnieją. Czasami te istniejące rozwiązania są lepsze niż to, co wymyśli programista. Najczęściej prowadzi to tylko do straconego czasu i niższej produktywności: programista może w ogóle nie znaleźć rozwiązania lub może znaleźć rozwiązanie dalekie od najlepszego. To powiedziawszy, nie możemy wykluczyć możliwości stworzenia niezależnego rozwiązania, ponieważ jest to bezpośrednia droga do programowania metodą „kopiuj i wklej”. Programista powinien kierować się konkretnymi zadaniami programistycznymi, które się pojawiają, aby umiejętnie je rozwiązać, czy to korzystając z gotowych rozwiązań, czy też tworząc rozwiązania niestandardowe. Bardzo często, powodem użycia tego antywzorca jest po prostu pośpiech. Rezultatem jest płytka analiza (poszukiwanie) gotowych rozwiązań. Ponowne wynalezienie kwadratowego koła to przypadek, w którym rozważany antywzorzec ma negatywny wynik. Oznacza to, że projekt wymaga niestandardowego rozwiązania, a programista je tworzy, ale źle. Jednocześnie dobra opcja już istnieje i inni z powodzeniem z niej korzystają. Konkluzja: traci się ogromną ilość czasu. Najpierw tworzymy coś, co nie działa. Potem próbujemy to refaktoryzować, a na koniec zastępujemy czymś, co już istniało. Przykładem jest implementacja własnej niestandardowej pamięci podręcznej, gdy istnieje już wiele implementacji. Bez względu na to, jak utalentowany jesteś jako programista, powinieneś pamiętać, że odkrywanie kwadratowego koła na nowo jest co najmniej stratą czasu. A jak wiadomo, czas jest najcenniejszym zasobem.

14. Problem jo-jo

Problem jo-jo to antywzorzec, w którym struktura aplikacji jest nadmiernie skomplikowana ze względu na nadmierną fragmentację (na przykład nadmiernie podzielony łańcuch dziedziczenia). „Problem jo-jo” pojawia się, gdy trzeba zrozumieć program, którego hierarchia dziedziczenia jest długa i złożona, tworząc głęboko zagnieżdżone wywołania metod. W rezultacie programiści muszą nawigować między wieloma różnymi klasami i metodami, aby sprawdzić zachowanie programu. Nazwa tego antywzorca pochodzi od nazwy zabawki. Jako przykład spójrzmy na następujący łańcuch dziedziczenia: Mamy interfejs technologii:

public interface Technology {
   void turnOn();
}
Interfejs Transport dziedziczy go:

public interface Transport extends Technology {
   boolean fillUp();
}
A potem mamy inny interfejs, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
Stamtąd wyprowadzamy abstrakcyjną klasę 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();
}
Następna jest abstrakcyjna klasa Volkswagena:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
I na koniec konkretny model:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Ten łańcuch zmusza nas do poszukiwania odpowiedzi na pytania takie jak:
  1. Ile ma metod VolkswagenAmarok?

  2. Jaki typ należy wstawić zamiast znaku zapytania, aby uzyskać maksymalną abstrakcję:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
Trudno jest szybko odpowiedzieć na takie pytania — wymaga to przyjrzenia się i zbadania, a łatwo się pogubić. A co, jeśli hierarchia jest znacznie większa, dłuższa i bardziej skomplikowana, z różnego rodzaju przeciążeniami i nadpisaniami? Struktura, którą byśmy mieli, byłaby zaciemniona z powodu nadmiernej fragmentacji. Najlepszym rozwiązaniem byłoby ograniczenie zbędnych podziałów. W naszym przypadku zostawilibyśmy Technika → Samochód → VolkswagenAmarok.

15. Przypadkowa złożoność

Niepotrzebna złożoność to antywzorzec, w którym do rozwiązania wprowadzane są niepotrzebne komplikacje.
„Każdy głupiec może napisać kod zrozumiały dla komputera. Dobrzy programiści piszą kod zrozumiały dla ludzi”. — Marcin Fowler
Czym więc jest złożoność? Można go zdefiniować jako stopień trudności, z jakim każda operacja jest wykonywana w programie. Z reguły złożoność można podzielić na dwa rodzaje. Pierwszy rodzaj złożoności to liczba funkcji, które posiada system. Można go zmniejszyć tylko w jeden sposób — usuwając jakąś funkcję. Istniejące metody muszą być monitorowane. Metoda powinna zostać usunięta, jeśli nie jest już używana lub jest nadal używana, ale nie wnosząca żadnej wartości. Co więcej, musisz ocenić, w jaki sposób wykorzystywane są wszystkie metody w aplikacji, aby zrozumieć, gdzie inwestycje byłyby opłacalne (dużo ponownego wykorzystania kodu), a czemu można odmówić. Drugi rodzaj złożoności to niepotrzebna złożoność. Można go wyleczyć tylko dzięki profesjonalnemu podejściu. Zamiast robić coś „fajnego” (nie tylko młodzi programiści są podatni na tę chorobę), trzeba pomyśleć jak to zrobić jak najprościej, bo najlepsze rozwiązanie jest zawsze proste. Załóżmy na przykład, że mamy małe powiązane tabele z opisami niektórych jednostek, takich jak użytkownik: Czym są antywzorce?  Spójrzmy na kilka przykładów (część 2) - 3Mamy więc id użytkownika, id języka, w którym jest wykonany opis i sam opis. Podobnie mamy deskryptory pomocnicze dla samochodów, plików, planów i tabel klientów. Jak zatem wyglądałoby wstawianie nowych wartości do takich tabel?

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 odpowiednio, mamy to wyliczenie:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Wszystko wydaje się proste i dobre... Ale co z innymi metodami? Rzeczywiście, wszystkie one będą miały również kilka switchinstrukcji i kilka prawie identycznych zapytań do bazy danych, co z kolei znacznie skomplikuje i rozrośnie naszą klasę. Jak można to wszystko uprościć? Zaktualizujmy nieco nasze wyliczenie:

@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;
}
Teraz każdy typ ma nazwy oryginalnych pól swojej tabeli. W rezultacie metoda tworzenia opisu staje się:

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);
   }
Wygodny, prosty i kompaktowy, nie sądzisz? Wyznacznikiem dobrego programisty nie jest nawet to, jak często używa wzorców, ale raczej to, jak często unika antywzorców. Ignorancja jest najgorszym wrogiem, ponieważ wrogów trzeba znać z widzenia. Cóż, to wszystko, co mam na dziś. Dziękuje wszystkim! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION