Antywzorce projektowe – metodyczne

Antywzorzec projektowy, czyli przeciwieństwo wzorca projektowego. Antywzorce projektowe mówią jakie są najczęściej powtarzane błędy, które potem mogą prowadzić projekt do nieudanego projektu. Dobrze jest wiedzieć co trzeba robić, aby pisać dobry kod, ale równie dobrze jest wiedzieć czego nie robić, aby projekt się powiódł. W tej części zajmę się antywzorcami metodycznymi. Kto nigdy nie używał któregoś z tych antywzorców niech pierwszy rzuci kamieniem ;).

Antywzorce projektowe – metodyczne

Copy and paste programming (Copypasteryzm)

Antywzorzec namiętnie stosowany przez początkujących programistów. Początkujący programista szuka odpowiedzi w Internecie jak można rozwiązać jego problem. Znajduje rozwiązuje i kopiuje do swojego programu – nic w tym złego nie widzę. Problem zaczyna się w momencie kiedy nie rozumie jaka działa skopiowany przez niego kod. Już pomijając takie szczegóły jak to, że skopiowany kod może nie pasować do konwencji pisania kodu w całym systemie.

Kiedy zaczynałem programować unikałem funkcji jak ognia (może ze względu na wskaźniki, które są na porządku dziennym w C). Często zdarzało mi się, że musiałem użyć pętli for i wrzucić jakąś logikę do środka pętli. W momencie kiedy potrzebowałem wcześniej napisanej pętli wraz z jej logiką robiłem CTRL+C, CTRL+V i ponownie używałem działającego rozwiązania. Linijki kodu cudownie się mnożyły, a ja dumny z siebie mówiłem, że mój program urósł znowu o 50 linijek. Problem pojawiał się, kiedy skopiowałem pętle for 10 razy i okazało się, że jest tam błąd do poprawy. W tym przypadku łatwo było domyślić się że byłem początkujący, bo nie używałem funkcji. Nie tworzyłem generycznych rozwiązań.

Antywzorce projektowe -  Copy and paste programming Antywzorce projektowe -  Copy and paste programming
Źródło: wikipedia.org

 

Reinventing the wheel (Tworzenie koła na nowo)

Po co tworzyć kod, skoro już go ktoś wcześniej napisał i opublikował jako darmowy framework/biblioteka? Jakbyś miał napisać funkcjonalność, która pobierałaby dane z Instagrama to zajęłoby to trochę czasu: implementacja podstawowego kodu, przypadki skrajne, naprawianie błędów. W zależności od Twojego doświadczenia zajęłoby więcej lub mniej czasu. W tym antywzorcu projektowym chodzi o nie marnowanie czasu na to co już istniej. Przecież stworzona jest już biblioteka, która pobiera dane z Instagrama (Instasharp). Nie ma potrzeby, aby pisać wszystko od początku. Trzeba wykorzystywać to co już istnieje. Owszem do każdego problemu nie znajdzie się już napisanej biblioteki. Często trzeba napisać coś samemu – ale to już nie jest tworzenia koła na nowo.
Jednak nie zawsze tworzenie koła na nowo jest takie złe! Czasem używanie gotowego rozwiązania to jest jak strzelanie z armaty do muchy. W swojej pracy magisterskiej pobierałem dane z Instagrama. Nie skorzystałem z gotowego rozwiązania. Dlaczego?Czarna skrzynka, nie miałem całkowitej kontroli nad tym co się dzieje w programie.

  1. Dużo funkcjonalności, której nie potrzebuje.
  2. Brak możliwości zmiany kodu źródłowego, aby pasował do moich potrzeb (tak, wiem że można, kod źródłowy jest dostępny, ale to inna bajka).
  3. Niewystarczająca, bądź zawiła dokumentacja.
  4. Czarna skrzynka – jeżeli biblioteka miałaby błąd i zwracała błędne wyniki to mogłoby to niekorzystnie wpłynąć na wyniki moich badań.
  5. Fantastyczne ćwiczenie! Dużo się nauczyłem pisząc taką funkcjonalność samemu od podstaw.

Ogólnie zaleca się, aby tworzyć koło na nowo, w celu nauki. Na studiach nie raz pisałem listę dwukierunkową w C++, mimo że istniały biblioteki które miały taką funkcjonalność. Ale jak to mówią – nauka nie poszła w las!
Uważam, ze można stworzyć koło na nowo jeżeli potrzebna jest tylko mała funkcjonalność istniejącego rozwiązania, a API jakieś biblioteki jest niejasne, zawiłe, nie zwraca wyników jakie się potrzebuje.

 

Reinventing the square wheel (Odkrywanie kwadratowego koła)

Antywzorzec bardzo powiązany z „tworzeniem koła na nowo”. Antywzorzec ten kładzie nacisk na to, że rozwiązanie które samemu się stworzyło może działać ze znacznie gorszą wydajność niż istniejące darmowe rozwiązanie. Myślę, ze spokojnie można zaufać najpopularniejszym bibliotekom na NuGetcie. Ten antywzorzec moim zdaniem stosowany przy większych funkcjonalnościach, jak na przykład stworzenie nowego ORMa, biblioteki graficznej.

Not invented here (Nie wynalezione tutaj)

Antywzorzec nazywany również NIH Syndrome. Załóżmy, że Kowalski uważa, że nie będzie używać biblioteki X do swojego projektu, ponieważ jest ona wątpliwej jakości. Dlatego napisze własną bibliotekę Y z taką samą funkcjonalnością co biblioteka X. Ale jego biblioteka będzie znacznie lepsza od X! Bezpieczniejsza, wydajniejsza, łatwiejsza w utrzymaniu i w ogóle bijącą na głowę pod wszystkimi względami bibliotekę X. W rzeczywistości stworzenie ulepszonej biblioteki X może być Tworzeniem kwadratowego koła. Czym ten antywzorzec różni się od antywzorca Tworzenie kwadratowego koła? Podejściem. NIH swoje korzenie ma w dumnym podejściu do problemu. Nasza biblioteka będzie lepsza. Nie ufam innym bibliotekom.

Warto w tym momencie się zastanowić jakie są różnice pomiędzy przepisaniem modułu, a tworzeniem na nowo modułu.

Przepisanie modułu
  • Przechodzi te same testy jednostkowe co oryginalny moduł.
  • Efektowniej rozwiązuje problemy pod które ten moduł został stworzony.
  • Deweloperzy oryginalnego modułu pomyśleli: „Dlaczego na to nie wpadliśmy wcześniej?”
  • Ma mnie linijek kodu.
Tworzenie modułu na nowo
  • Ma inne (ale możne mniej) bugi.
  • Zachowuje się inaczej (ale nie do końca lepiej) w przypadkach krańcowych.
  • Może mieć więcej linijek kodu, aby poprawić błędy, który powstały przy pisaniu tego modułu.

Co może stać w obronie NIH?

  • Kontroluje się API i implementacje.
  • Mieć większą rzetelność przez wprowadzenie odpowiednich testów jednostkowych.
  • Możliwość zmiany modułu z powodu zmiany wymagań.
  • Istniejąca implementacja może być w napisana w np. języku hiszpańskim, bądź stworzona na inną platformę.
  • Potrzebujesz tylko mały ułamek istniejącego rozwiązania.
  • Powstanie konkurencyjnego produktu. Co by było gdyby Gates powiedział: „Co co tworzyć nowy system operacyjny skoro już jest Mac OS?”
  • Zwiększenie zależności w projekcie (to jednocześnie plus i minus).

 

Invented Here (Wynalezione tutaj)

Invented Here Syndrom. Przeciwieństwo do NIH. To co pochodzi z zewnątrz jest lepsze. Skoro ktoś już coś stworzył to znaczy, że to musi być bardzo dobre, a my nic lepszego nie nie damy rady stworzyć.

 

Improbability factor (Czynnik nieprawdopodobieństwa)

Założenie, że znany programistom bug nie wystąpi podczas użytkowania programu. W ramach możliwości finansowych i czasowych programiści starają się naprawić inne problemy, które mają większe prawdopodobieństwo wystąpienia.

Czynnik Nieprawdopodobienstwa

Źródło: https://twitter.com/msdevuk/status/642311129914585088

 

Programming by permutation (Programowanie przez permutację)

Wzorzec stosowany przez osoby, które nie wiedza jak działa kod, uczą się programować lub wolą działać niż dwa razy pomyśleć. Działająca funkcjonalność tworzona jest przez bardzo malutkie zmiany (tytułowe permutacje) w kodzie, często nawet nie rozumiejąc dlaczego takie zmiany się wprowadziło. Antywzorzec ten jest również nazywany „programming by accident” (programowanie przez wypadki) lub „by-try programming” (programowanie przez próbowanie). Innymi słowy: coś nie działa? Zmienię jedna linijkę i zobaczę czy zacznie działać. Przykład programowania przez permutacje

Odwróć kolejność elementów w tablicy.
Wejście – Najpierw liczba testów t (t ≤ 100). Następnie dla każdego testu liczba n (n ≤ 100) i n liczb oddzielonych spacjami.
Wyjście – Dla każdego testu n liczb w porządku odwrotnym niż na wejściu.
Przykład
Wejście:
2
7 1 2 3 4 5 6 7
3 3 2 11
Wyjście:
7 6 5 4 3 2 1
11 2 3

Spróbuję to rozwiązać.

void ZadanieTablica(int t)
{
	while (t > 0)
	{
		string wejscieZKonsoli = = Console.ReadLine();
		List<string> rodzieloneSpacjami = wejscieZKonsoli.Split(' ').ToList();
		List<int> liczby = rodzieloneSpacjami.Select(s => int.Parse(s)).ToList();
		liczby.RemoveAt(0);
		List<int> bezPowtarzania = liczby.Distinct().ToList();
		List<int> odwroconaKolejnosc = bezPowtarzania.OrderByDescending(s => s).ToList();
		foreach (int temp in odwroconaKolejnosc)             
			Console.Write(temp + " ");
		
		Console.WriteLine();
		t--;
	}
}

Nie działa. W sumie może nie potrzebnie usuwam pierwszą liczbę? Spróbujmy. Podejście drugie:

void ZadanieTablica(int t)
{
	while (t > 0)
	{
		string wejscieZKonsoli = = Console.ReadLine();
		List<string> rodzieloneSpacjami = wejscieZKonsoli.Split(' ').ToList();
		List<int> liczby = rodzieloneSpacjami.Select(s => int.Parse(s)).ToList();
		
		List<int> bezPowtarzania = liczby.Distinct().ToList();
		List<int> odwroconaKolejnosc = bezPowtarzania.OrderByDescending(s => s).ToList();
		foreach (int temp in odwroconaKolejnosc)             
			Console.Write(temp + " ");
		
		Console.WriteLine();
		t--;
	}
}

Dalej nie działa. Może trzeba inaczej parsować przy użyciu LINQu stringa na liczby. Podejście trzecie:

void ZadanieTablica(int t)
{
	while (t > 0)
	{
		string wejscieZKonsoli = = Console.ReadLine();
		List<string> rodzieloneSpacjami = wejscieZKonsoli.Split(' ').ToList();
		List<int> liczby = rodzieloneSpacjami.Select(int.Parse).ToList();
		
		List<int> bezPowtarzania = liczby.Distinct().ToList();
		List<int> odwroconaKolejnosc = bezPowtarzania.OrderByDescending(s => s).ToList();
		foreach (int temp in odwroconaKolejnosc)             
			Console.Write(temp + " ");
		
		Console.WriteLine();
		t--;
	}
}

Nie działa. Może wynik nie jest wyświetlany tak jak powinien być? Podejście czwarte:

void ZadanieTablica(int t)
{
	while (t > 0)
	{
		string wejscieZKonsoli = = Console.ReadLine();
		List<string> rodzieloneSpacjami = wejscieZKonsoli.Split(' ').ToList();
		List<int> liczby = rodzieloneSpacjami.Select(int.Parse).ToList();
		
		List<int> bezPowtarzania = liczby.Distinct().ToList();
		List<int> odwroconaKolejnosc = bezPowtarzania.OrderByDescending(s => s).ToList();
		foreach (int temp in odwroconaKolejnosc)       
		{
			if(temp == odwronaKolejnosc.Last())   
				Console.Write(temp);
			else
				Console.Write(temp + " ");		
		}
		t--;
                if(t != 0)
                    Console.WriteLine();
	}
}

Nie działa! Może zamiast metody OrderByDescending() spróbuję Reverse(). Podejście piąte.

void ZadanieTablica(int t)
{
	while (t > 0)
	{
		string wejscieZKonsoli = = Console.ReadLine();
		List<string> rodzieloneSpacjami = wejscieZKonsoli.Split(' ').ToList();
		List<int> liczby = rodzieloneSpacjami.Select(int.Parse).ToList();
		liczby.RemoveAt(0);
		liczby.Reverse();
		foreach (int temp in odwroconaKolejnosc)             
			Console.Write(temp + " ");
		
		t--;
	}
}

Działa! Jak widać pisząc ten kod i patrząc za tokiem rozumowania osoba nie rozumie co się dzieje w kodzie. Robi coś metodą prób i błędów – bez przemyślenia.

Golden Hammer (Złoty młotek)

Jeżeli ktoś uważa, że istnieje jedno narzędzie bądź technologia, które potrafi w efektowny sposób rozwiązać wszystkie problemy to znaczy, ze ta osoba używa złotego młotka. To tak jakby Kowalski powiedział, że chce w C# zaprogramować działanie pralki. A dlaczego Kowalski mogłaby tak powiedzieć?

  1. C# zna na wylot i wszystko potrafi w przy jego użyciu zrobić.
  2. Rozwiązał już wiele problemów przy użyciu C#.
  3. Kowalski nie jest zaznajomiony z innymi technologiami.
  4. Łatwo mu będzie oszacować czas wykonania tego zadania.

Jakie mogą być inne przykłady złotego młotka?

  • Tworzenie wszystkiego jako aplikacje internetowe. Program do rejestracji pacjenta w klinice, który obsługuje recepcjonistka może być zwykłą desktopową aplikacją.
  • Używanie tylko jednego wzorca projektowego.
  • Dodatkowo można rozszerzyć ten wzorzec o czynnik ludzki. Nie ma osoby, która potrafiłaby rozwiązać każdy problem.

Istnieje jednak szczególny przypadek, kiedy używanie złotego młotka nie jest złym pomysłem. W momencie kiedy koszty użycia złotego młotka są mniejsze, niż w przypadku zastosowania bardziej dopasowanego narzędzia do problemu.

 

Silver Bullet (Srebrny pocisk)

Srebrnym pociskiem zabija się wilkołaki – stąd właśnie wzięła się nazywa tego antywzorca. Duży problem próbujemy załatwić przy pomocy naboju. Raz wystrzelonej kulki nie można cofnąć, kontrolować. Zespół widzi, że projekt jest w poważnych tarapatach. Aby wyratować się w opresji zespół postanowił użyć nowego frameworka, który wydaje się lekarstwem na ich problemy. Jeżeli implementacja nowego rozwiązania powiedzie się – projekt uratowany. Jeżeli nie – srebrny pocisk nie trafił w wilkołaka.

 

Premature Optimization (Przedwczesna optymalizacja)

Donald Knuth powiedział:

Programiści marnują niewyobrażalną ilość czas na myśleniu albo martwieniu się o szybkość niekrytycznych części ich programów. Ich próby usprawnienia [kodu] mają negatywny wpływ podczas debugowania i utrzymywania [kodu]. Powinniśmy zapomnieć o efektywności na około 97% czasu: przedwczesna optymalizacja jest źródłem całego zła. Jednak nie powinniśmy przepuścisz naszych możliwości w tych krytycznych 3%.

Co jest tym złem?

  • Mniej czytelny kod.
  • Znacznie większa ilość linijek kodu.
  • Mniej bezpieczna aplikacja.
  • Tracenie czasu programistów na optymalizacje nie kluczowych elementów
  • Pojawiające się bugi wynikające z przedwczesnej optymalizacji

Zdarza się z osoby odpowiedzialne za UMLe przykładają za dużo uwagi do szczegółowej optymalizacji – przez co projekt może być bardziej zagmatwany. Osoby tworzące UML mają prawo, albo wręcz obowiązek robienia wczesnej optymalizacji, ale na poziomie architektury lub przepływu danych.

Więc kiedy jest odpowiedni moment dla deweloperów, aby rozpocząć optymalizacje?

  • Napisz to tak, aby działało.
  • Napisz to tak, aby było poprawnie (czytelny kod, odpowiednia budowa kodu).
  • Zrób to tak, aby cały system działał.
  • Zrób to tak, aby cały system był poprawnie napisany.
  • Sprawdź, gdzie jest wąskie gardło w systemie.
  • Sprawdź co musi być zoptymalizowane.
  • Optymalizuj. Mam nadzieję, że masz test jednostkowe, aby wiedzieć, czy podczas optymalizacji nic się nie popsuło.

Optymalizacja bez sprawdzenia, gdzie jest wąskie gardła jest niedojrzałym podejściem.

Oczywiście nie rozmawiamy tutaj o optymalizacji, która jest łatwa do zaimplementowania i drożenia. Ale to już zależy od doświadczenia programisty. Przykładem takiej malutkiej optymalizacji, która jest łatwa do wdrożenia to budowanie stringa w pętli: https://msdn.microsoft.com/en-US/library/ms182272(v=vs.80).aspx .

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *