Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> Helper functions / funkcje globalne w OOP
Brick
post 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
Go to the top of the page
+Quote Post
sabat24
post 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:

  1. public static function in_array_all($needles, $haystack) {
  2. return ! array_diff($needles, $haystack);
  3. }


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.
Go to the top of the page
+Quote Post
Pyton_000
post 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
Go to the top of the page
+Quote Post
markuz
post 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%)
-----


  1. public static function in_array_all($needles, $haystack) {
  2. return ! array_diff($needles, $haystack);
  3. }


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.


--------------------
Go to the top of the page
+Quote Post
Brick
post 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:
  1. public function copyUploadedFile($filename, $dst_dir)
  2. {
  3. $filename = convertToSimpleString($filename);
  4. $extension = getFileExtension($filename);
  5. $dst_dir = convertToSimpleString($dst_dir);
  6. createDir($dst_dir);
  7. ....
  8. $file_size = covertToHumanFormat(filesize(...));
  9. ... //tutaj zapisanie pliku i przekazanie informacji o nim (finalna nazwa, rozszerzenie, rozmiar) do innej metody, która zapisuje te dane do bazy
  10. }

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
Go to the top of the page
+Quote Post
Pilsener
post 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.
Go to the top of the page
+Quote Post
Pyton_000
post 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%)
-----


Cytat(Brick @ 30.10.2017, 09:20:10 ) *
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:
  1. public function copyUploadedFile($filename, $dst_dir)
  2. {
  3. $filename = convertToSimpleString($filename);
  4. $extension = getFileExtension($filename);
  5. $dst_dir = convertToSimpleString($dst_dir);
  6. createDir($dst_dir);
  7. ....
  8. $file_size = covertToHumanFormat(filesize(...));
  9. ... //tutaj zapisanie pliku i przekazanie informacji o nim (finalna nazwa, rozszerzenie, rozmiar) do innej metody, która zapisuje te dane do bazy
  10. }

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.
Go to the top of the page
+Quote Post
Brick
post 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:
  1. class Example
  2. {
  3. public function __construct(StringService $string_service, NumberService $number_service, FileController $file_controller ...)
  4. }

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
Go to the top of the page
+Quote Post
Pilsener
post 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:
  1. $this->container->get(MyService::class);
- 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 businesssmiley.png

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 arrowheadsmiley.png
Go to the top of the page
+Quote Post
Brick
post 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
Go to the top of the page
+Quote Post
Pilsener
post 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ć:
  1. $this->container->get(Service::class);

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
Go to the top of the page
+Quote Post
Brick
post 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
Go to the top of the page
+Quote Post
Pilsener
post 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.
Go to the top of the page
+Quote Post
Brick
post 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:
  • Funkcji globalnych (funkcji definiowanych przez użytkownika poza klasami) należy unikać, chyba że są to zupełnie małe, ogólne i niezależne od logiki funkcje, np oblicz pole powierzchni figury.
  • Pozostałe funkcje należy grupować w klasach jako odpowiednie metody.
  • Metody z klas "zewnętrznych" należy importować do klasy za pomocą wstrzykiwania zależności do kontruktora klasy (dependency injection).
  • Kontenery DIC (Dependency Injection Container) ułatwiają i automatyzują to wstrzykiwanie ale idea pozostaje ta sama.


--------------------
Wszystko należy robić najprościej jak się da, ale nie prościej
Albert Einstein
Go to the top of the page
+Quote Post
nospor
post 9.11.2017, 12:55:50
Post #15





Grupa: Moderatorzy
Postów: 36 429
Pomógł: 6289
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

Go to the top of the page
+Quote Post
Pyton_000
post 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 wink.gif Bo taka metoda będzie w klasie Object i implementacja w konkretnej klasie figury wink.gif

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).
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Wersja Lo-Fi Aktualny czas: 19.03.2024 - 06:18