Czy szablon „metaprogramowanie” w Javie jest dobrym pomysłem?


29

W dość dużym projekcie znajduje się plik źródłowy z kilkoma funkcjami niezwykle wrażliwymi na wydajność (nazywanymi milionami razy na sekundę). W rzeczywistości poprzedni opiekun postanowił napisać 12 kopii funkcji różniących się bardzo nieznacznie, aby zaoszczędzić czas, który zostałby poświęcony na sprawdzenie warunków w jednej funkcji.

Niestety oznacza to, że kod jest PITA do utrzymania. Chciałbym usunąć cały zduplikowany kod i napisać tylko jeden szablon. Jednak język Java nie obsługuje szablonów i nie jestem pewien, czy nadają się do tego generyczne produkty.

Mój obecny plan polega na tym, aby zamiast tego napisać plik, który generuje 12 kopii funkcji (praktycznie ekspander szablonów jednorazowego użytku). Oczywiście podałbym obszerne wyjaśnienie, dlaczego plik musi być generowany programowo.

Obawiam się, że doprowadziłoby to do zamieszania przyszłych opiekunów i być może wprowadziłoby nieprzyjemne błędy, jeśli zapomną zregenerować plik po modyfikacji, lub (co gorsza), jeśli zmodyfikują zamiast tego plik wygenerowany programowo. Niestety, poza przepisaniem całego tekstu w C ++, nie widzę sposobu, aby to naprawić.

Czy zalety tego podejścia przeważają nad wadami? Czy zamiast tego powinienem:

  • Wykorzystaj wydajność i skorzystaj z jednej, łatwej do utrzymania funkcji.
  • Dodaj wyjaśnienia, dlaczego funkcja musi być powielona 12 razy, i uprzejmie weź na siebie obciążenie związane z utrzymaniem.
  • Spróbuj użyć generycznych jako szablonów (prawdopodobnie nie działają w ten sposób).
  • Krzycz na starego opiekuna, aby uczynić kod tak zależnym od wydajności od jednej funkcji.
  • Inna metoda utrzymania wydajności i łatwości konserwacji?

PS Z powodu złej konstrukcji projektu profilowanie funkcji jest dość trudne ... jednak były opiekun przekonał mnie, że hit wydajności jest niedopuszczalny. Zakładam, że przez to rozumie on ponad 5%, choć z mojej strony jest to całkowite przypuszczenie.


Może powinienem trochę rozwinąć. 12 kopii wykonuje bardzo podobne zadanie, ale ma niewielkie różnice. Różnice występują w różnych miejscach funkcji, więc niestety istnieje wiele, wiele instrukcji warunkowych. Istnieje skutecznie 6 „trybów” działania i 2 „paradygmaty” działania (słowa wymyślone przeze mnie). Aby użyć tej funkcji, określa się „tryb” i „paradygmat” działania. To nigdy nie jest dynamiczne; każdy fragment kodu używa dokładnie jednego trybu i paradygmatu. Wszystkie 12 par tryb-paradygmat są używane gdzieś w aplikacji. Funkcje są odpowiednio nazwane func1 do func12, przy czym liczby parzyste reprezentują drugi paradygmat, a liczby nieparzyste reprezentują pierwszy paradygmat.

Zdaję sobie sprawę, że jest to chyba najgorszy projekt w historii, jeśli celem jest łatwość konserwacji. Ale wydaje się, że jest „wystarczająco szybki”, a ten kod przez jakiś czas nie wymagał żadnych zmian ... Warto również zauważyć, że oryginalna funkcja nie została usunięta (chociaż, o ile wiem, jest to martwy kod) , więc refaktoryzacja byłaby prosta.


Jeśli wydajność jest na tyle poważna, że ​​gwarantuje 12 wersji tej funkcji, być może utkniesz w niej. Jeśli zrezygnujesz z jednej funkcji (lub użyjesz ogólnych), to czy wydajność będzie na tyle niska, że ​​stracisz klientów i biznes?
FrustratedWithFormsDesigner

12
Więc myślę, że musisz wykonać własne testy (wiem, że powiedziałeś, że profilowanie jest trudne, ale nadal możesz wykonywać surowe testy wydajności „czarnej skrzynki” systemu jako całości, prawda?), Aby zobaczyć, jak duża jest różnica jest. A jeśli jest to zauważalne, to myślę, że możesz utknąć w generatorze kodu.
FrustratedWithFormsDesigner

1
Może to brzmieć tak, jakby mógł skorzystać z pewnego parametrycznego polimorfizmu (a może nawet genetyki), zamiast wywoływać każdą funkcję pod inną nazwą.
Robert Harvey

2
Nazywanie funkcji func1 ... func12 wydaje się szalone. Przynajmniej nazwij je mode1Par2 itp ... lub może myFuncM3P2.
user949300,

2
„jeśli zamiast tego zmodyfikują programowo wygenerowany plik” ... utwórz plik tylko podczas kompilacji z „ Makefile” (lub innego używanego systemu) i usuń go zaraz po zakończeniu kompilacji . W ten sposób po prostu nie mają możliwości zmodyfikowania niewłaściwego pliku źródłowego.
Bakuriu

Odpowiedzi:


23

Jest to bardzo zła sytuacja, trzeba byłaby to jak najszybciej - to dług techniczny to najgorsze - nie wiem nawet, jak ważny kod naprawdę jest - tylko spekulować, że to ważne.

Co do rozwiązań JAK NAJSZYBCIEJ:
Coś, co można zrobić, to dodać niestandardowy krok kompilacji. Jeśli używasz Mavena, który jest w rzeczywistości dość prosty, inne automatyczne systemy kompilacji również mogą sobie z tym poradzić. Napisz plik z innym rozszerzeniem niż .java i dodaj niestandardowy krok, który przeszuka źródło w poszukiwaniu takich plików i ponownie wygeneruje rzeczywistą wersję .java. Możesz także dodać ogromne zastrzeżenie do automatycznie generowanego pliku, wyjaśniające, że nie należy go modyfikować.

Plusy a używanie raz wygenerowanego pliku: Twoi programiści nie uzyskają zmian w .java. Jeśli faktycznie uruchomią kod na swoim komputerze przed zatwierdzeniem, stwierdzą, że ich zmiany nie mają wpływu (hah). A potem może przeczytają zastrzeżenie. Masz absolutną rację, nie ufając kolegom z drużyny i przyszłemu sobie, pamiętając, że ten konkretny plik musi zostać zmieniony w inny sposób. Pozwala również na automatyczne testowanie tak dobrze, jak JUnit skompiluje Twój program przed uruchomieniem testów (i również ponownie wygeneruje plik)

EDYTOWAĆ

Sądząc po komentarzach, odpowiedź wyszła tak, jakby to był sposób na to, aby działało to w nieskończoność i być może OK będzie można wdrożyć w innych częściach projektu o kluczowym znaczeniu dla wydajności.

Mówiąc prosto: nie jest.

Dodatkowy ciężar tworzenia własnego mini-języka, pisania dla niego generatora kodu i utrzymywania go, nie wspominając o uczeniu go dla przyszłych opiekunów, jest piekielny na dłuższą metę. Powyższe pozwala jedynie na bezpieczniejszy sposób rozwiązania problemu podczas pracy nad długoterminowym rozwiązaniem . To, co to zajmie, będzie nade mną.


19
Przepraszam, ale muszę się nie zgodzić. Nie wiesz wystarczająco dużo o kodzie OP, aby podjąć za niego decyzję. Generowanie kodu wydaje mi się, jakby zawierało więcej długu technicznego niż oryginalne rozwiązanie.
Robert Harvey

6
Nie można przecenić komentarza Roberta. Za każdym razem, gdy pojawia się „zrzeczenie się odpowiedzialności wyjaśniające, aby nie modyfikować pliku”, który jest „technicznym długiem” równoważnym z kasą czekową prowadzoną przez nielegalnego bukmachera o nazwie „Shark”.
corsiKa

8
Automatycznie wygenerowany plik oczywiście nie należy do repozytorium źródłowego (w końcu to nie jest kod źródłowy, to skompilowany kod) i powinien zostać usunięty przez ant cleancokolwiek innego. Nie musisz umieszczać zrzeczenia się odpowiedzialności w pliku, którego nawet tam nie ma!
Jörg W Mittag

2
@RobertHarvey Nie podejmuję żadnych decyzji dotyczących PO. OP zapytał, czy dobrym pomysłem jest mieć takie szablony, tak jak je ma, i zaproponowałem (lepszy) sposób ich utrzymania. Nie jest to idealne rozwiązanie i, jak już powiedziałem w pierwszym zdaniu, cała sytuacja jest zła, a o wiele lepszym rozwiązaniem jest usunięcie problemu, a nie jego niepewne rozwiązanie. Obejmuje to prawidłowe oszacowanie, jak krytyczny jest ten kod, jak niska jest wydajność i czy istnieją sposoby na jego poprawne działanie bez pisania dużej ilości złego kodu.
Ordous

2
@ corsiKa Nigdy nie mówiłem, że to trwałe rozwiązanie. Byłby to tak samo dług jak pierwotna sytuacja. Jedyne, co robi, to zmniejszać zmienność, aż do znalezienia systematycznego rozwiązania. Co prawdopodobnie będzie obejmować całkowicie przejście na nową platformę / platformę / język, ponieważ jeśli napotykasz takie problemy w Javie - robisz coś wyjątkowo złożonego lub bardzo źle.
Ordous

16

Czy konserwacja jest prawdziwa, czy może ci to przeszkadza? Jeśli ci to przeszkadza, zostaw to w spokoju.

Czy problem z wydajnością jest prawdziwy, czy może tylko poprzedni programista tak sądził ? Problemy z wydajnością, częściej niż nie, nie występują tam, gdzie są uważane, nawet jeśli używany jest profiler. (Używam tej techniki, aby niezawodnie je znaleźć).

Istnieje więc możliwość, że masz brzydkie rozwiązanie problemu, ale może to być brzydkie rozwiązanie prawdziwego problemu. W razie wątpliwości zostaw to w spokoju.


10

Naprawdę upewnij się, że w rzeczywistych warunkach produkcyjnych (realistyczne zużycie pamięci, które realistycznie wyzwalają czyszczenie pamięci itp.), Wszystkie te oddzielne metody naprawdę mają wpływ na wydajność (w przeciwieństwie do posiadania tylko jednej metody). To zajmie ci kilka dni, ale może zaoszczędzić tygodnie, upraszczając kod, z którym teraz pracujesz.

Ponadto, jeśli zrobić odkryć, że trzeba wszystkie funkcje, można użyć Javassist do generowania kodu programowo. Jak zauważyli inni, możesz zautomatyzować to za pomocą Maven .


2
Ponadto, nawet jeśli okaże się, że problemy z wydajnością są prawdziwe, ćwiczenie ich przestudiowania pomoże ci lepiej poznać możliwości kodu.
Carl Manaster

6

Dlaczego nie uwzględnić jakiegoś generatora szablonów \ generowania kodu dla tego systemu? Niestandardowy dodatkowy etap kompilacji, który wykonuje i emituje dodatkowe pliki źródłowe Java przed skompilowaniem pozostałej części kodu. W ten sposób często działa włączanie klientów WWW z plików wsdl i xsd.

Oczywiście będziesz mieć obsługę tego generatora kodu / preprocesora, ale nie będziesz musiał się martwić o obsługę zduplikowanego kodu podstawowego.

Ponieważ emitowany jest kod Java w czasie kompilacji, za dodatkowy kod nie trzeba płacić za wydajność. Ale zyskujesz na uproszczeniu konserwacji dzięki szablonowi zamiast całego zduplikowanego kodu.

Generyczne Java nie dają żadnej korzyści w zakresie wydajności ze względu na wymazywanie tekstu w języku, zamiast niego zastosowano proste rzutowanie.

Z mojego zrozumienia szablonów C ++ są one zestawiane w kilka funkcji dla każdego wywołania szablonu. Skończysz ze zduplikowanym kodem czasowym w połowie kompilacji dla każdego typu przechowywanego na przykład w standardzie std :: vector.


2

Żadna rozsądna metoda Java nie może być wystarczająco długa, aby mieć 12 wariantów ... a JITC nie znosi długich metod - po prostu odmawia ich właściwej optymalizacji. Widziałem współczynnik przyspieszenia równy dwa, po prostu dzieląc metodę na dwie krótsze. Może to jest właściwa droga.

OTOH posiadające wiele kopii może mieć sens, nawet jeśli były identyczne. Gdy każdy z nich przyzwyczaja się w różnych miejscach, są one optymalizowane pod kątem różnych przypadków (JITC profiluje je, a następnie umieszcza rzadkie przypadki na wyjątkowej ścieżce.

Powiedziałbym, że generowanie kodu nie jest niczym wielkim, zakładając, że jest dobry powód. Narzut jest raczej niski, a odpowiednio nazwane pliki prowadzą bezpośrednio do ich źródła. Jakiś czas temu, kiedy generowałem kod źródłowy, wstawiałem // DO NOT EDITkażdą linię ... Myślę, że to wystarczy.


1

Nie ma absolutnie nic złego we wspomnianym „problemie”. Z tego, co wiem, jest to dokładnie rodzaj projektu i podejście zastosowane przez serwer DB w celu uzyskania dobrej wydajności.

Mają wiele specjalnych metod, aby upewnić się, że mogą zmaksymalizować wydajność dla wszystkich rodzajów operacji: łączyć, wybierać, agregować itp., Gdy mają zastosowanie określone warunki.

Krótko mówiąc, generowanie kodu takiego jak ten, który uważasz za zły pomysł. Być może powinieneś spojrzeć na ten schemat, aby zobaczyć, jak DB rozwiązuje problem podobny do twojego:wprowadź opis zdjęcia tutaj


1

Możesz sprawdzić, czy nadal możesz używać sensownych abstrakcji i użyć np. Wzorca metody szablonu, aby napisać zrozumiały kod dla wspólnej funkcjonalności i przenieść różnice między metodami do „operacji prymitywnych” (zgodnie z opisem wzoru) w 12 podklasy. To znacznie poprawi łatwość konserwacji i testowania, i może faktycznie mieć taką samą wydajność jak bieżący kod, ponieważ JVM może po pewnym czasie wstawić wywołania metod do operacji pierwotnych. Oczywiście musisz to sprawdzić w teście wydajności.


0

Możesz rozwiązać problem metod specjalistycznych za pomocą języka Scala.

Scala może wstawiać metody, które (w połączeniu z łatwym użyciem funkcji wyższego rzędu) pozwalają uniknąć bezpłatnego powielania kodu - wygląda to na główny problem wymieniony w odpowiedzi.

Ale Scala ma również makra składniowe, które umożliwiają wykonywanie wielu rzeczy przy użyciu kodu w czasie kompilacji w sposób bezpieczny dla typu.

I powszechny problem boksowania typów prymitywów, gdy są używane w rodzajach, jest również możliwy do rozwiązania w Scali: może on robić ogólną specjalizację dla elementów podstawowych, aby uniknąć boksu automatycznie za pomocą @specializedadnotacji - ta rzecz jest wbudowana w sam język. Zasadniczo w Scali napiszesz jedną ogólną metodę, która będzie działać z szybkością metod specjalistycznych. A jeśli potrzebujesz również szybkiej arytmetyki ogólnej, można to łatwo zrobić za pomocą „wzorca” Typeclass do wstrzykiwania operacji i wartości dla różnych typów liczbowych.

Ponadto Scala jest niesamowity na wiele innych sposobów. I nie musisz przepisywać całego kodu do Scali, ponieważ interoperacyjność Scala <-> Java jest doskonała. Upewnij się, że używasz SBT (narzędzie do budowania scala) do budowania projektu.


-1

Jeśli dana funkcja jest duża, przekształcenie bitów „tryb / paradygmat” w interfejs, a następnie przekazanie obiektu, który implementuje ten interfejs jako parametr do funkcji, może działać. GoF nazywa to wzorem „strategii”, iirc. Jeśli funkcja jest niewielka, zwiększony narzut może być znaczący ... czy ktoś wspomniał już o profilowaniu? ... więcej osób powinno wspomnieć o profilowaniu.


brzmi to bardziej jak komentarz niż odpowiedź
komnata

-2

Ile lat ma ten program? Najprawdopodobniej nowszy sprzęt usunąłby wąskie gardło i można by przełączyć na łatwiejszą do utrzymania wersję. Ale musi istnieć powód do konserwacji, więc chyba że Twoim zadaniem jest poprawienie bazy kodu, pozostaw to tak, jak działa.


1
wydaje się, że to tylko powtórzenie punktów poczynionych i wyjaśnionych we wcześniejszej odpowiedzi opublikowanej kilka godzin temu
komnata

1
Jedynym sposobem ustalenia, gdzie faktycznie jest wąskie gardło, jest jego profilowanie - jak wspomniano w drugiej odpowiedzi. Bez tych kluczowych informacji wszelkie sugerowane poprawki wydajności są czystą spekulacją.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.