Czy można mieć zapach, jeśli kod pozwala łatwiej rozwiązać inny problem? [Zamknięte]

31

Grupa przyjaciół i ja pracowaliśmy od jakiegoś czasu nad projektem i chcieliśmy wymyślić przyjemny sposób OOP reprezentujący scenariusz specyficzny dla naszego produktu. Zasadniczo pracujemy nad grą w piekło w stylu Touhou i chcieliśmy stworzyć system, w którym moglibyśmy z łatwością reprezentować każde możliwe zachowanie kuli, jakie moglibyśmy wymarzyć.

Tak właśnie zrobiliśmy; stworzyliśmy naprawdę elegancką architekturę, która pozwoliła nam podzielić zachowanie pocisku na różne komponenty, które mogą być dowolnie dołączane do instancji pocisków, podobnie jak system komponentów Unity . Działał ładnie, był łatwo rozszerzalny, był elastyczny i obejmował wszystkie nasze bazy, ale był niewielki problem.

Nasza aplikacja obejmuje również dużą liczbę generowania procedur, a mianowicie generujemy proceduralnie zachowania pocisków. Dlaczego to jest problem? Cóż, nasze rozwiązanie OOP do reprezentowania zachowania pocisków, choć eleganckie, jest nieco skomplikowane w pracy bez człowieka. Ludzie są wystarczająco inteligentni, aby wymyślić rozwiązania problemów, które są zarówno logiczne, jak i sprytne. Algorytmy generowania procedur nie są jeszcze tak sprytne i trudno nam było wdrożyć sztuczną inteligencję, która w pełni wykorzystuje naszą architekturę OOP. Trzeba przyznać, że wadą architektury jest to, że nie jest ona intuicyjna we wszystkich sytuacjach.

Aby rozwiązać ten problem, w zasadzie wepchnęliśmy wszystkie zachowania oferowane przez różne komponenty do klasy wypunktowanej, aby wszystko, co mogliśmy sobie wyobrazić, było oferowane bezpośrednio w każdej instancji wypunktowanej, w przeciwieństwie do innych powiązanych instancji komponentowych. To sprawia, że ​​nasze algorytmy generowania procedur są nieco łatwiejsze w obsłudze, ale teraz nasza klasa pocisków jest ogromnym bogiem . Jest to jak dotąd największa klasa w programie z ponad pięciokrotnie większym kodem niż cokolwiek innego. Utrzymanie jest również trochę uciążliwe.

Czy to w porządku, że jedna z naszych klas zamieniła się w obiekt boski, aby ułatwić pracę z innym problemem? Zasadniczo, czy jest w porządku mieć zapach w kodzie, jeśli pozwala on na łatwiejsze rozwiązanie innego problemu?

użytkownik3002473
źródło
1
Cóż ... trudno jest odpowiedzieć. Moim zdaniem NIE. Zwłaszcza jeśli współpracujesz z innymi ludźmi. Jeśli pracujesz sam, możesz robić, co chcesz. Ale ogólnie zależy to od tego, z kim pracujesz.
Sarcastic Potato
13
„Jeśli jest głupi i działa, to nie jest głupi”. To powiedziawszy, musisz wziąć pod uwagę, że nawet jeśli działa dokładnie tak, jak chcesz, niezależnie od tego, czy nie sprawisz, że przyszłe kodowanie tej klasy boga nie będzie prawie niemożliwe z powodu tego, jak postanowiłeś to naprawić.
Flater
8
Lepiej wąchać i wiedzieć, że śmierdzisz, a potem wąchać tylko to, co zauważają inni. :)
Reactgular,
1
Wygląda na to, że potrzebujesz klasy adaptera, która zapewnia interfejs inny niż OO do twojego kodu proceduralnego, więc możesz utrzymać resztę kodu w czystości (er).
nikie
1
Wygląda na to, że zbudowałeś wspaniały system i postanowiłeś wysadzić go w piekło, ponieważ teraz nie działa zgodnie z oczekiwaniami w przypadku jednej funkcji. Czy tego naprawdę chcesz? Bądź dumny ze swojej pracy!
Maszt

Odpowiedzi:

74

Podczas tworzenia rzeczywistych programów często dochodzi do kompromisu między pragmatyzmem z jednej strony a utrzymaniem 100% czystości z drugiej. Jeśli utrzymanie czystości zabrania ci terminowego wysłania produktu, lepiej jest użyć trochę taśmy klejącej, aby wydostać się z domu.

Powiedziałeś, że twój opis brzmi inaczej - brzmi, że nie zamierzasz dodać trochę taśmy izolacyjnej, brzmi to tak, jakbyś zniszczył całą architekturę, ponieważ nie wyglądałeś wystarczająco długo i wystarczająco mocno, aby uzyskać lepsze rozwiązanie. Zamiast szukać kogoś tutaj na PSE, który da ci błogosławieństwo, lepiej zadaj inne pytanie, w którym opisujesz niektóre szczegóły problemów, które masz dogłębnie, i sprawdź, czy ktoś oferuje ci pomysł, który omija boga podejście klasowe.

Być może klasa pocisku może być zaprojektowana jako fasada wielu innych klas, więc klasa pocisku staje się mniejsza. Może wzorzec strategii może pomóc, aby pocisk mógł przekazać różne zachowania różnym obiektom strategii. Być może potrzebujesz tylko przejściówki między komponentem pocisku a generatorem procedur. Ale szczerze mówiąc, nie znając więcej szczegółów twojego systemu, można tylko zgadywać.

Doktor Brown
źródło
6
Aby potwierdzić tę odpowiedź, zamierzałem napisać coś z tymi samymi dwoma zaleceniami. W odniesieniu do ostatniego akapitu to, co opisuje OP, wydaje się samo w sobie zapachem kodu wskazującym, że potrzebny jest inny poziom pośrednictwa. Niezależnie od tego, czy jest to lepsza klasa fabryczna, fasady, czy nawet pełna wersja DSL, którą AI może wygenerować, zależy od OP.
Daniel B
2
Dobre sugestie dotyczące fasady / strategii, aby podzielić kod na mniejsze części. Choć nie znając więcej szczegółów, trudno wiedzieć, co zasugerować.
user949300 13.0415
25

Interesujące pytanie. Jestem jednak trochę stronniczy z powodu moich wcześniejszych doświadczeń, co skłania mnie do odpowiedzi na nie.

Krótka odpowiedź: nigdy nie przestajemy się uczyć. Kiedy uderzysz w taką ścianę, jest to szansa na poprawę twoich umiejętności architektonicznych / projektowych, a nie pretekst do dodawania zapachów kodu.

Dłuższa wersja jest taka, że ​​często zadawano mi podobne pytania w moich projektach w pracy, ale nieuchronnie skończyliśmy na omawianiu problemu bardziej szczegółowo, niż pierwotny pytający udzielił mu uznania. Chcesz uwzględnić w tym mentalności, takie jak analiza przyczyn źródłowych.

Uważam, że 5 Whys szczególnie pomocne. Twoje wyjaśnienie brzmi tak samo jak pierwsze, dlaczego:

  • „Mamy teraz tę klasę boga” Dlaczego?
  • „Ponieważ upraszcza algorytm generowania procedur”

Co to dokładnie jest uproszczone? A dlaczego tak jest? Postępuj zgodnie z tą linią, a to, co zwykle się dzieje, polega na tym, że zaczynasz rozpoznawać znacznie bardziej fundamentalne problemy, ale jednocześnie pozwalają one również na bardziej podstawowe rozwiązania.

Krótko mówiąc, po głębszym zastanowieniu się nad problemem, a zwłaszcza jego przyczynami, z mojego doświadczenia wynika, że ​​pierwotne pytanie okazało się nieważne i całkowicie nieinteresujące dla wszystkich uczestników. Jeśli masz wątpliwości, rzuć im wyzwanie i albo znikną, albo będziesz wiedział, co musisz zrobić. Uważam, że moim zdaniem dobrze jest mieć wątpliwości co do kodu / projektu. Inni nazywają to przeczuciem, ale aby rzucić wyzwanie tym problemom, musisz je najpierw rozpoznać. Odkąd zrobiłeś pierwszy krok, gratulacje! Teraz zejdź do króliczej nory ...

Szczery
źródło
9

Posiadanie takiej klasy boga nigdy nie jest pożądane, ponieważ oznacza to nie tylko, że twoje pociski są teraz obiektami monolitycznymi, ale również to samo dotyczy algorytmu generowania procedur.

Pierwszym krokiem byłoby przeanalizowanie, dlaczego dokładnie twoja sztuczna inteligencja miała tyle problemów z radzeniem sobie ze złożonością twojego wzoru?

Czy przez przypadek próbowałeś przekształcić swoją sztuczną inteligencję w obiekt klasy boskiej, w pełni świadomy semantyki każdej możliwej właściwości? Jeśli tak, to właśnie stąd powstał problem.

Rozwiązaniem nie byłoby wówczas zintegrowanie wszystkich strategii z samą klasą pocisku, lecz odciążenie wskazówek dla sztucznej inteligencji z rdzenia AI do samych implementacji strategii.

To dałoby ci elastyczność, której początkowo pragnąłeś, w tym opcję rozszerzenia systemu o nowe strategie, jak chcesz, bez obawy, że napotkasz skutki uboczne związane ze starszymi zachowaniami.

Zamiast tego masz teraz wszystkie problemy związane z obiektami boskimi: nie tylko twoja klasa boska jest trudna do zrozumienia, trudny do debugowania monolitem, ale to samo dotyczy wszystkich innych komponentów mających dostęp do takiej boskiej klasy. Z powodu braku abstrakcji twoja sztuczna inteligencja musi teraz zmienić się w obrzydliwość o podobnej złożoności, ponieważ musi być teraz świadoma wszystkich zbędnych, indywidualnych właściwości.

Nawet teraz masz problemy z konserwacją. Problemy te nasilają się, zwłaszcza gdy stracisz członków zespołu, którzy nadal mają model poznawczy, jak ta klasa powinna działać.

Do tej pory każdy projekt, z którym się spotkałem, który korzystał z takich klas boga lub funkcji boga, albo był całkowicie przepisywany od zera, albo był przerywany, bez wyjątków.

Ext3h
źródło
Jedną z rzeczy, których często brakuje w dyskusjach na temat tego, jak duże lub złożone powinny być klasy, jest rozróżnienie między „ile kod, który zawiera odwołanie typu X, może z tym zrobić”, a „dużo” logiki w pliku klas dla X ”. Jeśli klient prawdopodobnie miałby kolekcje obiektów, które obsługują różne możliwości (np. „Fnorble”), i często chce poprosić wszystkich członków kolekcji o np. „Fnorble, jeśli to możliwe, w przeciwnym razie nic nie rób”, to mając interfejs który łączy wiele takich metod, może być o wiele bardziej pomocny niż próba podzielenia interfejsu.
supercat
Biorąc pod uwagę taki interfejs, możliwe jest, że kod hermetyzuje obiekty z dowolną kombinacją umiejętności, bez konieczności oddzielnej obsługi różnych kombinacji. Segregacja interfejsów spowodowałaby konieczność napisania wielu różnych klas proxy, ponieważ proxy dla typu obsługującego metodę X nie mogą być używane do zawijania obiektów, które nie mogą X, a proxy, które mogą zawijać obiekty, które nie mogą X może być w stanie udostępnić metodę X, nawet jeśli owija obiekt, który mógłby ją obsługiwać.
supercat
8

Cały zły kod od zarania dziejów ma swoją historię, która sprawia, że ​​krok po kroku wygląda rozsądnie. Twój nie jest wyjątkiem. Kodery uczą się przez kodowanie. Są aspekty twojego problemu, których nie mogłeś przewidzieć, które wydają się teraz oczywiste. Podejmowane przez ciebie decyzje były całkowicie uzasadnione przyrostowo, ale ogólnie poprowadziły Twoją architekturę w złym kierunku. Musisz zidentyfikować i ponownie przeanalizować te decyzje.

Zapytaj w biurze, czy ludzie mają jakieś pomysły, jak to naprawić. W większości miejsc, w których pracowałem, około 10-20% programistów ma prawdziwy talent do tego rodzaju rzeczy i właśnie czekało na szansę. Dowiedz się, kim są ci ludzie. Często twoi nowi pracownicy, którzy nie byli historycznie inwestowani w obecną architekturę, mogą najłatwiej dostrzec alternatywy. Połącz ich z jednym z weteranów, a możesz być zaskoczony tym, co wspólnie wymyślą.

Karl Bielefeldt
źródło
2

W niektórych przypadkach jest to zdecydowanie dopuszczalne. Trudno mi jednak uwierzyć, że nie ma dobrego rozwiązania z wykorzystaniem zarówno generowania procedur, jak i twojej ładnej architektury zachowania opartej na załącznikach / komponentach. Jeśli wszystkie zachowania zostały właśnie wciągnięte do klasy pocisków, nie ma funkcjonalnej różnicy między boskim obiektem a zgrabną wersją architektoniczną. Co sprawiło, że korzystanie z algorytmów generowania procedur jest tak trudne?

Myślę, że nowe pytanie (może tutaj lub na gamedev.stackexchange.com?), W którym nakreśliłeś problemy, jakie miałeś z architekturą w połączeniu z proc. gen., byłoby naprawdę interesujące. Daj nam znać, jeśli również, jeśli zadajesz nowe pytanie!

Roy T.
źródło
3
Czy możesz podać jakiś przykład sytuacji, w której posiadanie boskiej klasy byłoby zarówno akceptowalne, jak i pożądane, podczas gdy klasa ta nie jest jedynie automatyczną kompilacją poszczególnych źródeł?
Ext3h
Z akceptowalnym miałem na myśli „Czy dobrze jest mieć zapach kodu, jeśli pozwala to na łatwiejsze rozwiązanie innego problemu?” Boskie klasy są zazwyczaj łatwe do rozwiązania przy użyciu kompozycji. Ale tak, istnieją pewne zapachy kodowe, które zdecydowanie nie zawsze są warte 100% pozbycia się. W końcu potrzebny jest pewien pragmatyzm, aby wykonać pracę :). (Nie oznacza to, że uważam, że powinieneś rzucić najlepsze praktyki pod wpływem kaprysu. Uważam, że większość producentów oprogramowania lepiej przestrzegałaby najlepszych praktyk bardziej rygorystycznie).
Roy T.
0

Aby uzyskać inspirację, warto przyjrzeć się programowaniu funkcjonalnemu, a ściślej ściśle dopasowanym typom. Pomysł, że rzeczy nie działają, jest niemożliwy do przedstawienia w dostępnym systemie typów. Załóżmy na przykład, że masz broń z komponentami „strzelaj pociskami” i „trzymaj pociski”. Jeśli są to dwa oddzielne komponenty, istnieją konfiguracje, które nie mają sensu - możesz mieć broń, która strzela pociskami, ale nie ma ich w magazynie, lub broń, która przechowuje kule, ale ich nie strzela.

Na tym poziomie złożoności myślisz „dobrze, człowiek zorientuje się, że jest to bezużyteczne i uniknie niemożliwych kombinacji”. Lepszym sposobem może być uczynienie tego całkowicie niemożliwym. Na przykład komponent „strzelaj pociskami” może wymagać odniesienia do komponentu „trzymaj pociski” (lub po prostu trzymaj go jako część samego siebie, chociaż ma to swoje własne problemy).

Chociaż twoje przykłady mogą być znacznie bardziej skomplikowane, klucz wciąż ogranicza możliwe kombinacje, dodając ograniczenia. W pewnym sensie, jeśli jest to zbyt skomplikowane, aby generowanie procedur mogło się odbyć poprawnie, prawdopodobnie i tak jest zbyt skomplikowane. Pomyśl o wszystkich sposobach ograniczania się przy projektowaniu broni - czy mówisz sobie „tak, ta broń ma już miotacz ognia, nie ma sensu dodawać granatnika”? To coś, co możesz włączyć do swojej heurystyki. Możesz użyć mechanizmu prawdopodobieństwa, który jest nieco bardziej skomplikowany niż tylko zapewnienie stałej szansy posiadania każdego komponentu - możesz mieć komponenty, które mają tendencję do wykluczania się nawzajem lub komponenty, które zwykle działają dobrze razem.

Na koniec zastanów się, czy to rzeczywiście wystarczająco duży problem. Czy to niszczy zabawę z gry? Czy pistolety wynikowe są bezużyteczne lub przytłoczone? Ile? Czy jeden na 10 nonsensów? Gry takie jak Borderlands (z generowanymi proceduralnie efektami broni) często ignorują nonsensowne kombinacje efektów - jaki jest sens posiadania strzelby z lunetą 16x? A jednak zdarza się to bardzo często na Pograniczu. To tylko gra dla śmiechu, a nie jako awaria mechanizmów generowania :)

Luaan
źródło