Kiedy powinieneś i nie powinieneś używać słowa kluczowego „nowy”?

15

Obejrzałem prezentację Google Tech Talk na temat testów jednostkowych , przeprowadzoną przez Misko Hevery'ego, i powiedział, aby unikać używania newsłowa kluczowego w kodzie logiki biznesowej.

Napisałem program i ostatecznie użyłem newsłowa kluczowego tu i tam, ale były one głównie do tworzenia instancji obiektów, które przechowują dane (tj. Nie miały żadnych funkcji ani metod).

Zastanawiam się, czy zrobiłem coś złego, gdy użyłem nowego słowa kluczowego w moim programie. A gdzie możemy złamać tę „zasadę”?

Sal
źródło
2
Czy możesz dodać tag związany z językiem programowania? Nowość istnieje w wielu językach (C ++, Java, Ruby, żeby wymienić tylko kilka) i ma inną semantykę.
sakisk,

Odpowiedzi:

16

To więcej wskazówek niż twardych i szybkich zasad.

Używając „nowego” w kodzie produkcyjnym, łączysz swoją klasę z jej współpracownikami. Jeśli ktoś chce użyć innego współpracownika, na przykład jakiegoś próbnego współpracownika do testowania jednostkowego, nie może tego zrobić - ponieważ współpracownik jest tworzony w logice biznesowej.

Oczywiście, ktoś musi utworzyć te nowe obiekty, ale często najlepiej pozostawić to w jednym z dwóch miejsc: frameworku wstrzykiwania zależności, takim jak Spring, lub w innej klasie, w której tworzona jest instancja klasy logiki biznesowej, wstrzykiwana przez konstruktor.

Oczywiście możesz posunąć się za daleko. Jeśli chcesz zwrócić nową ArrayList, prawdopodobnie nic ci nie jest - szczególnie, jeśli będzie to niezmienna lista.

Główne pytanie, które powinieneś sobie zadać, brzmi: „to główna odpowiedzialność tego fragmentu kodu do tworzenia obiektów tego typu, czy to tylko szczegóły implementacji, które mógłbym przenieść gdzie indziej?”

Bill Michell
źródło
1
Cóż, jeśli chcesz, aby była to niezmienna lista, powinieneś użyć Collections.unmodifiableListczegoś takiego. Ale wiem, co masz na myśli :)
MatrixFrog
Tak, ale musisz jakoś stworzyć oryginalną listę, którą następnie przekształcisz w niemodyfikowalny ...
Bill Michell,
5

Sedno tego pytania jest na końcu: zastanawiam się, czy zrobiłem coś złego, kiedy użyłem nowego słowa kluczowego w moim programie. A gdzie możemy złamać tę „zasadę”?

Jeśli jesteś w stanie napisać skuteczne testy jednostkowe dla swojego kodu, nie zrobiłeś nic złego. Jeśli użycie newgo utrudniło lub uniemożliwiło jednostkowe przetestowanie kodu, należy ponownie ocenić użycie nowego. Możesz rozszerzyć tę analizę na interakcje z innymi klasami, ale umiejętność pisania solidnych testów jednostkowych jest często wystarczająco dobrym proxy.

ObscureRobot
źródło
5

Krótko mówiąc, za każdym razem, gdy używasz „nowego”, ściśle łączysz klasę zawierającą ten kod z tworzonym obiektem; aby utworzyć instancję jednego z tych obiektów, klasa wykonująca instancję musi wiedzieć o instancji konkretnej klasy. Tak więc, używając „nowego”, powinieneś rozważyć, czy klasa, w której umieszczasz instancję, jest „dobrym” miejscem dla tej wiedzy i czy jesteś gotów wprowadzić zmiany w tym obszarze, jeśli forma instancja obiektu miała się zmienić.

Nie zawsze należy unikać ciasnego łączenia, czyli obiektu posiadającego wiedzę o innej konkretnej klasie; na pewnym poziomie coś, GDZIEŚ, musi wiedzieć, jak stworzyć ten obiekt, nawet jeśli wszystko inne zajmuje się tym obiektem, otrzymując jego kopię z innego miejsca. Jednak gdy tworzona klasa ulega zmianie, każda klasa, która wie o konkretnej implementacji tej klasy, musi zostać zaktualizowana, aby poprawnie radzić sobie ze zmianami tej klasy.

Pytanie, które powinieneś zawsze zadać, brzmi: „Czy posiadanie tej klasy będzie wiedziała, jak utworzyć tę drugą klasę, która stanie się obowiązkiem podczas utrzymywania aplikacji?” Obie główne metodologie projektowania (SOLID i GRASP) zwykle odpowiadają „tak” z nieco innych powodów. Są to jednak tylko metodologie i oba mają skrajne ograniczenie, że nie zostały sformułowane w oparciu o wiedzę o twoim unikalnym programie. W związku z tym mogą one popełniać błędy tylko po stronie ostrożności i zakładać, że każdy punkt ścisłego sprzężenia spowoduje w ODWRÓCENIU problem związany z wprowadzeniem zmian w jednej lub obu stronach tego punktu. Musisz podjąć ostateczną decyzję, wiedząc o trzech rzeczach; najlepsza praktyka teoretyczna (polegająca na luźnym powiązaniu wszystkiego, ponieważ wszystko może się zmienić); koszty wdrożenia teoretycznych najlepszych praktyk (które mogą obejmować kilka nowych warstw abstrakcji, które ułatwią jeden rodzaj zmiany, a przeszkadzają w innym); a realne prawdopodobieństwo, że przewidywany rodzaj zmian będzie kiedykolwiek konieczny.

Kilka ogólnych wskazówek:

  • Unikaj ścisłego łączenia między skompilowanymi bibliotekami kodów. Interfejs między bibliotekami DLL (lub EXE i jego bibliotekami DLL) jest głównym miejscem, w którym ścisłe sprzężenie będzie niekorzystne. Jeśli zmienisz klasę A w DLL X, a klasa B w głównym EXE wie o klasie A, musisz ponownie skompilować i zwolnić oba pliki binarne. W obrębie jednego pliku binarnego ściślejsze sprzężenie jest ogólnie bardziej dopuszczalne, ponieważ cały plik binarny musi zostać zrekonstruowany pod kątem każdej zmiany. Czasami konieczność odbudowania wielu plików binarnych jest nieunikniona, ale powinieneś tak ustrukturyzować swój kod, abyś mógł go w miarę możliwości uniknąć, szczególnie w sytuacjach, w których przepustowość jest na wysokim poziomie (takich jak wdrażanie aplikacji mobilnych; wrzucanie nowej biblioteki DLL podczas aktualizacji jest znacznie tańsze niż wypychanie całego programu).

  • Unikaj ścisłego powiązania między głównymi „centrami logicznymi” twojego programu. Możesz myśleć o dobrze ustrukturyzowanym programie składającym się z poziomych i pionowych przekrojów. Wycinki poziome mogą być tradycyjnymi warstwami aplikacji, takimi jak interfejs użytkownika, kontroler, domena, DAO, dane; pionowe wycinki mogą być zdefiniowane dla poszczególnych okien lub widoków, lub dla indywidualnych „historii użytkowników” (takich jak utworzenie nowego rekordu jakiegoś podstawowego typu). Kiedy wykonujesz połączenie, które przesuwa się w górę, w dół, w lewo lub w prawo w dobrze zorganizowanym systemie, powinieneś ogólnie streścić to połączenie. Na przykład, gdy sprawdzanie poprawności wymaga pobrania danych, nie powinno ono mieć bezpośredniego dostępu do bazy danych, ale powinno wywołać interfejs do pobierania danych, który jest wspierany przez rzeczywisty obiekt, który wie, jak to zrobić. Gdy niektóre elementy interfejsu użytkownika muszą wykonywać zaawansowaną logikę obejmującą inne okno, powinno to wyzwolić wyzwolenie tej logiki poprzez zdarzenie i / lub wywołanie zwrotne; nie musi wiedzieć, co zostanie w wyniku tego zrobione, pozwalając ci zmienić to, co zostanie zrobione, bez zmiany kontroli, która je wyzwala.

  • W każdym razie zastanów się, jak łatwa lub trudna będzie zmiana i jak prawdopodobne będzie ta zmiana. Jeśli obiekt, który tworzysz, jest używany tylko z jednego miejsca i nie przewiduje się takiej zmiany, wówczas ścisłe sprzężenie jest ogólnie bardziej dopuszczalne, aw tej sytuacji może nawet przewyższyć luźne sprzężenie. Luźne sprzężenie wymaga abstrakcji, która jest dodatkową warstwą, która zapobiega zmianie obiektów zależnych, gdy implementacja zależności musi ulec zmianie. Jeśli jednak sam interfejs musi się zmienić (dodanie nowego wywołania metody lub dodanie parametru do istniejącego wywołania metody), interfejs faktycznie zwiększa ilość pracy niezbędnej do wprowadzenia zmiany. Musisz rozważyć prawdopodobieństwo różnych rodzajów zmian wpływających na projekt,

KeithS
źródło
3

Obiekty przechowujące tylko dane lub DTO (obiekty przesyłania danych) są w porządku, twórz je przez cały dzień. Jeśli chcesz uniknąć newklas, które wykonują działania, chcesz, aby klasy konsumujące programowały zamiast interfejsu , w którym mogą wywoływać tę logikę i wykorzystywać ją, ale nie są odpowiedzialne za faktyczne tworzenie instancji klas zawierających logikę . Te klasy wykonujące akcję lub zawierające logikę są zależnościami. Rozmowa Misko jest nastawiona na to, że wstrzykujesz te zależności i pozwalasz, by jakaś inna istota była odpowiedzialna za ich tworzenie.

Anthony Pegram
źródło
2

Inni wspominali już o tym, ale chcieli zacytować to w Clean Code wuja Boba (Bob Martin), ponieważ może to ułatwić zrozumienie tej koncepcji:

„Silnym mechanizmem oddzielającym konstrukcję od zastosowania jest Dependency Injection (DI), zastosowanie Inversion of Control (IoC) do zarządzania zależnościami. Inversion of Control przenosi drugorzędne obowiązki z obiektu na inne obiekty, które są przeznaczone do tego celu, wspierając tym samym pojedyncze odpowiedzialność Zasada . W kontekście zarządzania zależność, obiekt nie powinien brać odpowiedzialność za instancji zależności się. Zamiast tego, powinien przekazać tę odpowiedzialność na siebie „autorytatywne” mechanizmu, a tym samym odwrócenie kontroli. Ponieważ konfiguracja jest globalnym problemem, to mechanizmem autorytatywnym będzie zwykle „główna” procedura lub kontener specjalnego przeznaczenia ”.

Myślę, że zawsze dobrze jest przestrzegać tej zasady. Inni wspominali o ważniejszym IMO -> to oddziela twoją klasę od jej zależności (współpracowników). Drugi powód jest taki, że sprawia, że ​​twoje klasy są mniejsze i bardziej zrozumiałe, łatwiejsze do zrozumienia, a nawet ponownego wykorzystania w innych kontekstach.

c_maker
źródło