Helper functions / funkcje globalne w OOP |
Helper functions / funkcje globalne w OOP |
29.10.2017, 11:20:17
Post
#1
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Czy używacie funkcji własnych funkcji globalnych wewnątrz klas? Chodzi mi o typowe funkcjie pomocnicze (helper functions), które dokonują jakiś obliczeń czy robią transformacje na ciągach znaków. Są małe, ogólne i zupełnie niezwiązane z logiką biznesową czy modułami programu.
Minusem tych funkcji jest to że uniemożliwiają testowanie (mockowanie) klas podobnie jak funkcje statyczne. (Jest jakieś dobry polski zamiennik na "mock")? Plusem jest to, że są zawsze dostępne - nie wymagają tworzenia obiektu i przekazywania za pomocą dependency injection. Jak rozwiązujecie ten problem? Bez funkcji globalnych tylko zawsze jako metody przypisane do konkretnej klasy? Czy może jednak funkcje globalne ale tylko w projektach dla których nie są wykonywane testy jednostkowe? -------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
29.10.2017, 14:20:17
Post
#2
|
|
Grupa: Zarejestrowani Postów: 175 Pomógł: 26 Dołączył: 13.09.2007 Skąd: Gdańsk Ostrzeżenie: (0%) |
Jeśli o mnie chodzi, to wszystko zależy jakie są powiązania i do czego się używa takich metod. Jeśli to są klasyczne, proste helpery na zasadzie "wyciągnij pierwiastek", to nie ma najmniejszego problemu, by włożyć to w statyczną klasę Math i używać globalnie.
Co do testów, niektórzy powiedzą, że na statycznych nie robi się unit testów, tylko testy integracyjne. Jak dla mnie "zwał jak zwał", ale jeśli metoda się da przetestować, a kod typu:
jest bardzo łatwy to przetestowania, to nie widzę problemu, by dla wygody nie użyć go jako w klasie statycznej. Odnośnie mockowania (zwię się to atrapa po naszemu), to jest np. taka biblioteka: https://github.com/Codeception/AspectMock Oczywiście da się przesadzić ze statycznością, ale jeśli się korzysta z takich helperów, ja nie widzę przeciwskazań. Nigdy jednak nie byłem zwolniennikiem purystycznego kodu dla samej sztuki. |
|
|
29.10.2017, 14:44:15
Post
#3
|
|
Grupa: Zarejestrowani Postów: 8 068 Pomógł: 1414 Dołączył: 26.10.2005 Ostrzeżenie: (0%) |
Przecież da się wytestować metodę która ma jakieś static calls. Wystarczy utworzyć mock właśnie tej metody.
Tutaj przydład: https://stackoverflow.com/questions/5961023...-static-methods |
|
|
29.10.2017, 15:49:25
Post
#4
|
|
Grupa: Zarejestrowani Postów: 1 240 Pomógł: 278 Dołączył: 11.03.2008 Ostrzeżenie: (0%) |
Dla mnie to nic innego jak własny alias, jak jestem nowym programistą w projekcie to wolę zobaczyć zapis !array_diff($needles, $haystack) niż Costam::in_array_all($needles, $haystack) - w 1 przypadku nie muszę zaglądać głębiej, w 2 niby wiem po nazwie co robi metoda ale doświadczenie pokazuje, że trzeba tam zajrzeć. Aktualnie jestem zwolennikiem nie używania takich helperów w kodzie klas - często trafia do nich logika biznesowa. A sam fakt, ze musimy użyć w wielu miejscach metody z jakiegoś helpera może świadczyć o błędnej architekturze. Nawet jeżeli nie ma tam logiki biznesowej to pojawi się programista który ją tam umieści żeby nie główkować za długo. Jednak każdy przypadek trzeba rozpatrzeć osobno, możesz podać konkretny przykład helpera i jego użycia w konkretnych klasach. -------------------- |
|
|
30.10.2017, 09:20:10
Post
#5
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Dzięki za odpowiedzi. Ta biblioteka AspectMock to super sprawa, nie wiedziałem że jest coś takiego.
Oczywiście zgadza się - wszystko zależy od tego jak się używa takich globalnych funkcji. Jak ktoś potrafi wstawić tam logikę biznesową czy elementy które powiązane z konkretnymi modułami programu no to lepiej żeby nie ich nie używał. Poniżej przykładowy kod:
Jest to oczywiście przykład uproszczony, sama metoda może nie zawierać na raz tyle funkcji globalnych. Chodzi o to żeby pokazać ich wykorzystanie. - Funkcja convertToSimpleString() zamienia ciąg znaków np: "Raport biegłego rewidenta z oświadczeniem - za ROK 2012" na "Raport_bieglego_rewidenta_z_oswiadczeniem_za_rok_2012" jednocześnie usuwając różne niepożądane znaki. - Funkcja createDir() sprawdza czy podany katalog istnieje - jeżeli nie to go tworzy. - Funkcja convertToHumanFormat() - zamienia rozmiar w bajtach na rozmiar w kB, MB, GB -------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
30.10.2017, 09:59:29
Post
#6
|
|
Grupa: Zarejestrowani Postów: 1 590 Pomógł: 185 Dołączył: 19.04.2006 Skąd: Gdańsk Ostrzeżenie: (0%) |
Cytat Czy używacie funkcji własnych funkcji globalnych wewnątrz klas? Nie, bo to zła praktyka. Różnego rodzaju "utilsy" są pogrupowane w serwisach wg ich przeznaczenia (np. service/tool/StringService.php) i "wstrzykiwane" do odpowiedniego kontekstu. Przy czym część funkcji jest dostępna defaultowo (np. $this->logger w kontekście całej aplikacji) a inne są "na życzenie". Oczywiście dużo łatwiej jest użyć czegoś wprost, niż implementować jako zależność, ale to pozorna oszczędność bo tworzymy w ten sposób różne ukryte zależności które ciężko jest kontrolować, powstają różne dziwne błędy i tak dalej. |
|
|
30.10.2017, 10:18:30
Post
#7
|
|
Grupa: Zarejestrowani Postów: 8 068 Pomógł: 1414 Dołączył: 26.10.2005 Ostrzeżenie: (0%) |
Dzięki za odpowiedzi. Ta biblioteka AspectMock to super sprawa, nie wiedziałem że jest coś takiego. Oczywiście zgadza się - wszystko zależy od tego jak się używa takich globalnych funkcji. Jak ktoś potrafi wstawić tam logikę biznesową czy elementy które powiązane z konkretnymi modułami programu no to lepiej żeby nie ich nie używał. Poniżej przykładowy kod:
Jest to oczywiście przykład uproszczony, sama metoda może nie zawierać na raz tyle funkcji globalnych. Chodzi o to żeby pokazać ich wykorzystanie. - Funkcja convertToSimpleString() zamienia ciąg znaków np: "Raport biegłego rewidenta z oświadczeniem - za ROK 2012" na "Raport_bieglego_rewidenta_z_oswiadczeniem_za_rok_2012" jednocześnie usuwając różne niepożądane znaki. - Funkcja createDir() sprawdza czy podany katalog istnieje - jeżeli nie to go tworzy. - Funkcja convertToHumanFormat() - zamienia rozmiar w bajtach na rozmiar w kB, MB, GB Jak na moje to nadaje się na normalną klasę i wstrzyknięcie jako DI bo tu dużo logiki się dzieje jak na helper. |
|
|
31.10.2017, 08:13:31
Post
#8
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Cytat Przy czym część funkcji jest dostępna defaultowo (np. $this->logger w kontekście całej aplikacji) a inne są "na życzenie" W jaki sposób jest dostępna defaultowo? Każdy obiekt ma domyślnie wstrzykiwany odpowiedni zestaw? Czy to się robi za pomocą Data Object Container? Czy czasem w klasie nie robi się zbyt dużo tych wstrzyknięć? Np:
Chyba, że jest to zwykle objaw sugerujący że klasa ma za dużą odpowiedzialność (naruszenie single responsibility)? -------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
31.10.2017, 09:04:15
Post
#9
|
|
Grupa: Zarejestrowani Postów: 1 590 Pomógł: 185 Dołączył: 19.04.2006 Skąd: Gdańsk Ostrzeżenie: (0%) |
Cytat W jaki sposób jest dostępna defaultowo? - najczęściej jest zaimplementowana w odpowiedniej warstwie abstrakcji, np. ControllerAbstract ma metodę redirect.Cytat Czy to się robi za pomocą Data Object Container? - w ten sposób pobieramy zależności "na życzenie" i tak, zauważyłem, że kontener zależności ładowanych w sposób "lazy" to dziś pewien standard. Jednak używanie wszędzie w kodzie:- zalatuje lekko globalem. Znów tworzymy jakieś zależności pozaszywane w kodzie. Lepiej uszyć getry/swetry a jeszcze lepiej przez konstruktor - wtedy od razu widać, jakich zależności potrzebujemy. Cytat Czy czasem w klasie nie robi się zbyt dużo tych wstrzyknięć? - robi, ale to jest już kwestia trzymania porządku w kodzie. Jeśli klasa się rozrasta to z pomocą przychodzi nam fasada i podzielenie tego na pod-serwisy, problem jest taki, że nikomu nie chce się tego robić bo poza uporządkowaniem kodu nie ma z tego żadnego zysku biznesowego Bardzo istotne też jest, czy piszemy testy jednostkowe - jeśli tak, to już to wymusza zupełnie inne podejście do architektury kodu. Tylko w ilu firmach preferuje się takie podejście |
|
|
2.11.2017, 13:06:27
Post
#10
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Cytat najczęściej jest zaimplementowana w odpowiedniej warstwie abstrakcji, np. ControllerAbstract ma metodę redirect Czy możesz rozwinąć trochę ten wątek? Jak to wygląda w praktyce? -------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
3.11.2017, 08:49:20
Post
#11
|
|
Grupa: Zarejestrowani Postów: 1 590 Pomógł: 185 Dołączył: 19.04.2006 Skąd: Gdańsk Ostrzeżenie: (0%) |
Najlepiej spojrzeć na jakiś framework, gdy projektujemy np. kontroler to musimy przemyśleć nasz abstrakt:
- jakie metody w nim zaimplementować (co często jest trudne) - jak dostarczyć do niego potrzebne zależności - jak w łatwy sposób rozszerzać klasę Przy dostarczaniu zależności wykorzystujemy kontener, implementując interfejs z metodą "setContainer" Potem gdy rozszerzamy klasę, to zazwyczaj deklarujemy ją jako serwis aczkolwiek jeśli implementuje ona kontener to możemy używać:
Jednak z wiadomych względów nie jest to zalecane. Oczywiście są też względy praktyczne, stąd używanie kontenera bo: - z klasy abstrakcyjnej nie da się przecież utworzyć obiektu i czegoś tam wstrzyknąć - dobrze jest mieć do dyspozycji konstruktor (ciągłe call super czy tam parent nie jest przecież zalecane) - wygoda |
|
|
6.11.2017, 09:35:02
Post
#12
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Cytat zauważyłem, że kontener zależności ładowanych w sposób "lazy" to dziś pewien standard Co dokładnie masz na myśli pisząc "lazy"? Ładowanie obiektu na żądanie, wtedy gdy bo faktycznie potrzebujemy a nie "na zapas"? -------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
6.11.2017, 13:28:14
Post
#13
|
|
Grupa: Zarejestrowani Postów: 1 590 Pomógł: 185 Dołączył: 19.04.2006 Skąd: Gdańsk Ostrzeżenie: (0%) |
Chodzi o to, że obiekty są przygotowywane dopiero wtedy, gdy są potrzebne. Np. masz kontener połączeń z bazami danych - w podejściu tradycyjnym połączenie do baz następuje przy starcie aplikacji, w podejściu "lazy" dopiero wtedy, gdy pojawi się pierwsze zapytanie do danej bazy.
|
|
|
9.11.2017, 12:51:48
Post
#14
|
|
Grupa: Zarejestrowani Postów: 107 Pomógł: 9 Dołączył: 16.02.2004 Skąd: Kraków Ostrzeżenie: (0%) |
Dzięki za wszystkie odpowiedzi.
Podsumowując dla osób które trafią na ten wątek:
-------------------- Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein |
|
|
9.11.2017, 12:55:50
Post
#15
|
|
Grupa: Moderatorzy Postów: 36 519 Pomógł: 6308 Dołączył: 27.12.2004 |
Cytat chyba że są to zupełnie małe, ogólne i niezależne od logiki funkcje, np oblicz pole powierzchni figury. Tak czytam ten temat i czytam i nikogo, procz ciebie, nie widze by to pochwalal.
-------------------- "Myśl, myśl, myśl..." - Kubuś Puchatek || "Manual, manual, manual..." - Kubuś Programista "Szukaj, szukaj, szukaj..." - Kubuś Odkrywca || "Debuguj, debuguj, debuguj..." - Kubuś Developer |
|
|
9.11.2017, 13:30:42
Post
#16
|
|
Grupa: Zarejestrowani Postów: 8 068 Pomógł: 1414 Dołączył: 26.10.2005 Ostrzeżenie: (0%) |
Z tym pp figury to akurat słaby przykład Bo taka metoda będzie w klasie Object i implementacja w konkretnej klasie figury
Tak na prawdę jeśli chodzi o utilsy jako takie to nie ma złotego środka. Można grupować utils ze względu na przznaczenie np. w klasie String i tam odpowiednie itd. Wtedy potrzebującu utils do generowania slug albo zaciągasz klasę String albo klasę Uri (w zależności gdzie podczepisz). |
|
|
Wersja Lo-Fi | Aktualny czas: 24.09.2024 - 07:55 |