Skąd mam wiedzieć, w jaki sposób moje metody powinny być wielokrotnego użytku? [Zamknięte]


133

Mam na myśli własny biznes w domu, a moja żona podchodzi do mnie i mówi

Kochanie ... Czy możesz wydrukować wszystkie Day Light Oszczędności na całym świecie na rok 2018 w konsoli? Muszę coś sprawdzić.

I jestem bardzo szczęśliwy, ponieważ właśnie z tym doświadczeniem Java czekałem całe życie i wymyśliłem:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(2018, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (2018 == now.getYear()) {
                    int hour = now.getHour();
                    now = now.plusHours(1);
                    if (now.getHour() == hour) {
                        System.out.println(now);
                    }
                }
            }
        );
    }
}

Ale potem mówi, że właśnie testowała mnie, czy jestem etycznie wyszkolonym inżynierem oprogramowania, i mówi mi, że wygląda na to, że nie jestem (zabrany stąd ) ..

Należy zauważyć, że żaden etycznie wyszkolony inżynier oprogramowania nigdy nie wyraziłby zgody na napisanie procedury DestroyBaghdad. Podstawowa etyka zawodowa wymagałaby zamiast tego napisania procedury DestroyCity, której Bagdad mógłby zostać podany jako parametr.

I jestem jak, w porządku, ok, dostałeś mnie .. Spędź dowolny rok , jak chcesz, proszę bardzo:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings(int year) {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(year, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (year == now.getYear()) {
                    // rest is same..

Ale skąd mam wiedzieć, ile (i co) sparametryzować? W końcu mogłaby powiedzieć…

  • chce przekazać niestandardowy formatator ciągów, może nie lubi formatu, w którym już drukuję: 2018-10-28T02:00+01:00[Arctic/Longyearbyen]

void dayLightSavings(int year, DateTimeFormatter dtf)

  • interesuje się tylko niektórymi miesięcznymi okresami

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd)

  • interesuje się pewnymi przedziałami godzinowymi

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd, int hourStart, int hourend)

Jeśli szukasz konkretnego pytania:

Jeśli destroyCity(City city)jest lepszy niż destroyBaghdad(), czy jest takeActionOnCity(Action action, City city)jeszcze lepszy? Dlaczego? Dlaczego nie?

Po tym wszystkim, mogę najpierw wywołać ją Action.DESTROYwtedy Action.REBUILD, prawda?

Ale podejmowanie działań na miastach nie jest na tyle dla mnie, co powiesz takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)? W końcu nie chcę dzwonić:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

następnie

takeActionOnCity(Action.DESTORY, City.ERBIL);

i tak dalej, kiedy mogę:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

ps Moje pytanie zbudowałem tylko wokół cytowanego przeze mnie cytatu: nie mam nic przeciwko żadnemu krajowi, religii, rasie czy cokolwiek na świecie. Ja tylko próbuję coś wyjaśnić.



71
Kwestia, o której tu mówisz, jest tym, o czym wiele razy starałem się wyrazić: ogólność jest droga i dlatego musi być uzasadniona konkretnymi, wyraźnymi korzyściami . Ale to idzie głębiej; języki programowania są tworzone przez ich projektantów, aby niektóre rodzaje ogólności były łatwiejsze niż inne, co wpływa na nasze wybory jako programistów. Jest łatwy do parametryzacji sposób przez wartość, a gdy to jest najłatwiejszym narzędziem masz w skrzynce narzędziowej, pokusa jest go używać niezależnie od tego, czy ma to sens dla użytkownika.
Eric Lippert,

30
Ponowne użycie nie jest czymś, czego chcesz dla samej korzyści. Priorytetem jest ponowne użycie, ponieważ uważamy, że artefakty kodu są kosztowne w budowie, a zatem powinny być użyteczne w jak największej liczbie scenariuszy, w celu amortyzacji tych kosztów w tych scenariuszach. To przekonanie często nie jest uzasadnione obserwacjami, dlatego też porady dotyczące projektowania w celu ponownego użycia są często niewłaściwie stosowane . Zaprojektuj swój kod, aby obniżyć całkowity koszt aplikacji .
Eric Lippert

7
Twoja żona jest nieetyczna, ponieważ marnujesz czas, okłamując cię. Poprosiła o odpowiedź i podała sugerowane medium; Na podstawie tej umowy sposób uzyskiwania tej produkcji zależy tylko od ciebie i ciebie. Ponadto destroyCity(target)jest o wiele bardziej nieetyczne niż destroyBagdad()! Jaki potwór pisze program wymazania miasta, a co dopiero każdego miasta na świecie? Co jeśli system został przejęty ?! Co również zarządzanie czasem / zasobami (zainwestowane wysiłki) ma wspólnego z etyką? O ile umowa ustna / pisemna została zakończona zgodnie z ustaleniami.
Tezra

25
Myślę, że zbyt wiele czytasz w tym dowcipie. To żart o tym, jak programiści komputerowi podejmują złe decyzje etyczne, ponieważ priorytetowo traktują względy techniczne przed wpływem ich pracy na ludzi. Nie ma na celu stanowić dobrej porady na temat projektowania programu.
Eric Lippert,

Odpowiedzi:


114

Żółwie są na dole.

Lub abstrakcje w tym przypadku.

Kodowanie dobrych praktyk jest czymś, co można zastosować w nieskończoność, a w pewnym momencie abstraktujesz dla samego abstrakcji, co oznacza, że ​​posunąłeś się za daleko. Znalezienie tej linii nie jest czymś łatwym do zastosowania, ponieważ zależy od twojego środowiska.

Na przykład mieliśmy klientów, którzy wcześniej prosili o proste aplikacje, a potem o rozszerzenia. Mieliśmy również klientów, którzy pytają, czego chcą i na ogół nigdy nie wracają do nas w celu rozszerzenia.
Twoje podejście będzie się różnić w zależności od klienta. Pierwszy klient zapłaci za uprzedzające wyodrębnienie kodu, ponieważ masz całkowitą pewność, że w przyszłości będziesz musiał go ponownie odwiedzić. W przypadku drugiego klienta możesz nie chcieć zainwestować dodatkowego wysiłku, jeśli oczekujesz, że nie będzie chciał rozszerzyć aplikacji w żadnym momencie (uwaga: nie oznacza to, że nie przestrzegasz żadnej dobrej praktyki, ale po prostu że unikasz robienia więcej niż jest to obecnie konieczne.

Skąd mam wiedzieć, które funkcje wdrożyć?

Powodem, dla którego wspominam powyżej, jest to, że już wpadłeś w tę pułapkę:

Ale skąd mam wiedzieć, ile (i co) sparametryzować? W końcu mogłaby powiedzieć .

„Może powiedzieć” nie jest aktualnym wymogiem biznesowym. Odgadnij przyszłe wymagania biznesowe. Zasadniczo nie opieraj się na domysłach, opracowuj tylko to, co jest obecnie wymagane.

Jednak kontekst ma tutaj zastosowanie. Nie znam twojej żony. Może dokładnie oceniłeś, że tak naprawdę będzie tego chciała. Ale nadal powinieneś potwierdzić z klientem, że to jest właśnie to, czego on chce, ponieważ w przeciwnym razie poświęcisz czas na opracowanie funkcji, z której nigdy nie skorzystasz.

Skąd mam wiedzieć, którą architekturę wdrożyć?

To trudniejsze. Klient nie dba o wewnętrzny kod, więc nie możesz zapytać go, czy go potrzebuje. Ich opinia w tej sprawie jest w większości nieistotna.

Nadal jednak możesz to potwierdzić, zadając odpowiednie pytania klientowi. Zamiast pytać o architekturę, zapytaj ich o ich oczekiwania dotyczące przyszłego rozwoju lub rozszerzenia bazy kodu. Możesz również zapytać, czy aktualny cel ma wyznaczony termin, ponieważ możesz nie być w stanie wdrożyć fantazyjnej architektury w niezbędnym czasie.

Skąd mam wiedzieć, kiedy jeszcze bardziej wyodrębnić kod?

Nie wiem, gdzie to czytam (jeśli ktoś wie, daj mi znać, a dam kredyt), ale dobrą zasadą jest, że programiści powinni liczyć się jak jaskiniowiec: raz, dwa, wiele .

wprowadź opis zdjęcia tutaj XKCD # 764

Innymi słowy, gdy jakiś algorytm / wzorzec zostanie użyty po raz trzeci , należy go wyodrębnić, aby można go było ponownie wykorzystać (= użyteczny wiele razy).

Żeby było jasne, nie sugeruję, że nie powinieneś pisać kodu wielokrotnego użytku, gdy używane są tylko dwa wystąpienia algorytmu. Oczywiście możesz to również wyodrębnić, ale reguła powinna być taka, że ​​w trzech przypadkach musisz wyabstrahować.

Ponownie, to wpływa na twoje oczekiwania. Jeśli już wiesz, że potrzebujesz trzech lub więcej instancji, oczywiście możesz natychmiast odreagować. Ale jeśli tylko zgadniesz, że możesz chcieć zaimplementować go więcej razy, poprawność implementacji abstrakcji w pełni zależy od poprawności twojego przypuszczenia.
Jeśli zgadłeś poprawnie, zaoszczędziłeś trochę czasu. Jeśli źle zgadłeś, zmarnowałeś trochę czasu i wysiłku i być może naraziłeś swoją architekturę na wdrożenie czegoś, czego w końcu nie potrzebujesz.

Jeśli destroyCity(City city)jest lepszy niż destroyBaghdad(), czy jest takeActionOnCity(Action action, City city)jeszcze lepszy? Dlaczego? Dlaczego nie?

To bardzo zależy od wielu rzeczy:

  • Są tam wiele działań, które mogą być podjęte na każdym mieście?
  • Czy działania te można stosować zamiennie? Ponieważ jeśli akcje „zniszczyć” i „odbudować” mają zupełnie inne wykonania, nie ma sensu scalać tych dwóch w jednej takeActionOnCitymetodzie.

Pamiętaj też, że jeśli rekurencyjnie to abstrakcyjnie skończysz, uzyskasz metodę tak abstrakcyjną, że uruchomisz inną metodę tylko w kontenerze, co oznacza, że ​​Twoja metoda stała się nieistotna i bez znaczenia.
Jeśli całe takeActionOnCity(Action action, City city)ciało metody kończy się niczym więcej action.TakeOn(city);, powinieneś się zastanowić, czy takeActionOnCitymetoda ma naprawdę cel, czy nie jest tylko dodatkową warstwą, która nie wnosi nic wartościowego.

Ale podejmowanie działań na miastach nie jest na tyle dla mnie, co powiesz takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)?

To samo pytanie pojawia się tutaj:

  • Czy masz przypadek użycia dla regionów geograficznych?
  • Czy wykonanie akcji w mieście i regionie jest takie samo?
  • Czy można podjąć jakieś działania w dowolnym regionie / mieście?

Jeśli możesz definitywnie odpowiedzieć „tak” na wszystkie trzy, uzasadniona jest abstrakcja.


16
Nie mogę wystarczająco podkreślić zasady „jeden, dwa, wielu”. Istnieją nieskończone możliwości abstrakcji / parametryzacji czegoś, ale użyteczny podzbiór jest niewielki, często zerowy. Wiedzę dokładnie, który wariant ma wartość, najczęściej można ustalić tylko z perspektywy czasu. Dlatego trzymaj się bezpośrednich wymagań * i dodawaj złożoność zgodnie z wymaganiami nowych wymagań lub z perspektywy czasu. * Czasami dobrze znasz problematyczną przestrzeń, wtedy możesz coś dodać, bo wiesz, że będziesz tego potrzebować jutro. Ale używaj tej mocy mądrze, może to również prowadzić do ruiny.
Christian Sauer

2
> „Nie wiem, gdzie to przeczytałem [..]”. Być może czytałeś Coding Horror: The Rule of Three .
Run

10
„Reguła jeden, dwa, wiele” naprawdę istnieje, aby zapobiec budowaniu niewłaściwej abstrakcji poprzez ślepe stosowanie SUCHEGO. Chodzi o to, że dwa fragmenty kodu mogą zacząć wyglądać prawie dokładnie tak samo, więc kuszące jest usunięcie różnic; ale na początku nie wiadomo, które części kodu są stabilne, a które nie; poza tym może się okazać, że faktycznie muszą ewoluować niezależnie (różne wzorce zmian, różne zestawy obowiązków). W obu przypadkach niewłaściwa abstrakcja działa przeciwko tobie i przeszkadza.
Filip Milovanović

4
Oczekiwanie na więcej niż dwa przykłady „tej samej logiki” pozwala lepiej ocenić, co należy wyodrębnić i jak (i ​​tak naprawdę chodzi o zarządzanie zależnościami / sprzężeniem między kodem o różnych wzorcach zmian).
Filip Milovanović

1
@kukis: Realistyczna linia powinna być narysowana na 2 (zgodnie z komentarzem Baldrickka): zero-jeden-wiele (jak w przypadku relacji z bazą danych). To jednak otwiera drzwi do niepotrzebnego zachowania polegającego na szukaniu wzorów. Dwie rzeczy mogą niejasno wyglądać podobnie, ale to nie znaczy, że w rzeczywistości są takie same. Jednak gdy trzecia instancja wkracza do walki, która przypomina zarówno pierwszą, jak i drugą instancję, możesz dokładniej ocenić, czy ich podobieństwa są rzeczywiście wzorem wielokrotnego użytku. Tak więc linia zdrowego rozsądku jest narysowana na 3, co uwzględnia błąd ludzki przy wykrywaniu „wzorów” tylko między dwoma instancjami.
Flater

44

Ćwiczyć

To jest Software Engineering SE, ale oprogramowanie do tworzenia to o wiele więcej sztuki niż inżynieria. Nie ma uniwersalnego algorytmu do naśladowania ani pomiaru, aby ustalić, ile wystarczy ponownego użycia. Jak w przypadku wszystkiego, im więcej ćwiczysz projektowania programów, tym lepiej sobie z tym poradzisz. Lepiej poczujesz, co jest „wystarczające”, ponieważ zobaczysz, co idzie nie tak i jak idzie źle, gdy sparametryzujesz za dużo lub za mało.

Nie jest to teraz zbyt pomocne , więc co powiesz na jakieś wytyczne?

Spójrz na swoje pytanie. Jest wiele „mogła powiedzieć” i „mógłbym”. Wiele wypowiedzi teoretycznych na temat niektórych przyszłych potrzeb. Ludzie nie znają przewidywać przyszłości. A ty (najprawdopodobniej) jesteś człowiekiem. Przytłaczającym problemem związanym z projektowaniem oprogramowania jest próba wyjaśnienia przyszłości, której nie znasz.

Wytyczna 1: Nie będziesz go potrzebować

Poważnie. Przestań. Najczęściej wyobrażony problem przyszłości nie pojawia się - i na pewno nie pojawi się tak, jak sobie wyobrażałeś.

Wytyczna 2: Koszt / korzyść

Fajnie, ten mały program zajął ci kilka godzin. Więc co jeśli żona ma wrócić i poprosić o tych rzeczach? W najgorszym przypadku poświęcasz kilka godzin na zebranie innego programu, aby to zrobić. W tym przypadku nie jest zbyt wiele czasu, aby ten program był bardziej elastyczny. Nie wpłynie to znacząco na szybkość działania ani zużycie pamięci. Ale nietrywialne programy mają różne odpowiedzi. Różne scenariusze mają różne odpowiedzi. W pewnym momencie koszty wyraźnie nie są warte korzyści, nawet przy niedoskonałych umiejętnościach mówienia w przyszłości.

Wytyczna 3: Koncentracja na stałych

Spójrz na pytanie. W twoim oryginalnym kodzie jest wiele stałych int. 2018, 1. Stałe ints, stałe ciągi ... Są to najbardziej prawdopodobne rzeczy, które powinny być niestałe . Co więcej, parametryzacja zajmuje im tylko trochę czasu (lub przynajmniej definiuje jako rzeczywiste stałe). Ale inną rzeczą, na którą należy uważać, jest ciągłe zachowanie . TheSystem.out.printlnna przykład. Takie założenie dotyczące użytkowania jest czymś, co zmienia się w przyszłości i jest bardzo kosztowne do naprawienia. Nie tylko to, ale takie IO sprawia, że ​​funkcja jest nieczysta (wraz z nieco pobieraną strefą czasową). Parametryzacja tego zachowania może uczynić funkcję bardziej czystą, co prowadzi do większej elastyczności i testowalności. Duże korzyści przy minimalnych kosztach (zwłaszcza, jeśli przeciążenie jest używane System.outdomyślnie).


1
To tylko wskazówka, 1s są w porządku, ale spójrz na nie i powiedz „czy to się kiedykolwiek zmieni?” Nie. A println można sparametryzować za pomocą funkcji wyższego rzędu - choć Java nie jest w tym świetna.
Telastyn

5
@KorayTugay: jeśli program był naprawdę dla twojej żony wracającej do domu, YAGNI powiedziałby ci, że twoja początkowa wersja jest idealna i nie powinieneś inwestować więcej czasu na wprowadzenie stałych lub parametrów. YAGNI potrzebuje kontekstu - czy Twój program jest rozwiązaniem odrzuconym , czy programem migracyjnym działającym tylko przez kilka miesięcy, czy też jest częścią ogromnego systemu ERP, który ma być używany i utrzymywany przez kilka dziesięcioleci?
Doc Brown

8
@KorayTugay: Oddzielenie I / O od obliczeń jest podstawową techniką strukturyzacji programu. Oddzielne generowanie danych od filtrowania danych od transformacji danych od ich zużycia po prezentację danych. Powinieneś przestudiować niektóre programy funkcjonalne, wtedy zobaczysz to wyraźniej. W programowaniu funkcjonalnym dość powszechne jest generowanie nieskończonej ilości danych, filtrowanie tylko tych, które są zainteresowane, przekształcanie danych do potrzebnego formatu, konstruowanie z niego ciągu i drukowanie tego ciągu w 5 różnych funkcjach, jeden na każdy krok.
Jörg W Mittag

3
Krótko mówiąc, zdecydowane podążanie za YAGNI prowadzi do konieczności ciągłego refaktoryzacji: „Używany bez ciągłego refaktoryzacji, może prowadzić do niezorganizowanego kodu i ogromnej przeróbki, znanej jako dług techniczny”. Więc chociaż YAGNI jest ogólnie rzeczą dobrą, wiąże się z wielką odpowiedzialnością za weryfikację i ponowną ocenę kodu, co nie jest zadaniem każdego programisty / firmy.
Flater

4
@Telastyn: Sugeruję rozszerzenie pytania na „czy to się nigdy nie zmieni i czy intencja kodu jest trywialnie czytelna bez nazywania stałej ?” Nawet w przypadku wartości, które nigdy się nie zmieniają, może być istotne, aby nazwać je tylko po to, aby zachować czytelność.
Flater

27

Po pierwsze: żaden programista zorientowany na bezpieczeństwo nie napisałby metody DestroyCity bez podania tokena autoryzacji z jakiegokolwiek powodu.

Ja też mogę napisać wszystko jako imperatyw, który ma oczywistą mądrość, ale nie można go zastosować w innym kontekście. Dlaczego konieczne jest autoryzowanie konkatenacji ciągu?

Po drugie: cały kod po uruchomieniu musi być w pełni określony .

Nie ma znaczenia, czy decyzja została na stałe zakodowana na miejscu, czy odroczona na inną warstwę. W pewnym momencie jest jakiś kod w jakimś języku, który wie zarówno, co ma zostać zniszczone, jak i instrukcje.

Może to być ten sam plik obiektowy destroyCity(xyz)i może to być plik konfiguracyjny: destroy {"city": "XYZ"}"lub może to być seria kliknięć i naciśnięć klawiszy w interfejsie użytkownika.

Po trzecie:

Kochanie ... Czy możesz wydrukować wszystkie Day Light Oszczędności na całym świecie na rok 2018 w konsoli? Muszę coś sprawdzić.

to zupełnie inny zestaw wymagań do:

chce przekazać niestandardowy formatator ciągów, ... zainteresowany tylko niektórymi okresami miesiąca, ... [i] zainteresowany pewnymi przedziałami godzin ...

Teraz drugi zestaw wymagań oczywiście zapewnia bardziej elastyczne narzędzie. Ma szerszą grupę docelową i szerszą dziedzinę zastosowań. Niebezpieczeństwo polega na tym, że najbardziej elastyczną aplikacją na świecie jest w rzeczywistości kompilator kodu maszynowego. Jest to dosłownie program tak ogólny, że może zbudować wszystko, aby uczynić komputer jakimkolwiek jest (w ramach ograniczeń sprzętowych).

Ogólnie rzecz biorąc, ludzie, którzy potrzebują oprogramowania, nie chcą czegoś ogólnego; chcą czegoś konkretnego. Dając więcej opcji, w rzeczywistości komplikujesz ich życie. Gdyby chcieli tej złożoności, zamiast tego użyliby kompilatora, nie pytając.

Twoja żona prosiła o funkcjonalność i nie spełniła twoich wymagań. W tym przypadku było to pozornie celowe, a ogólnie dlatego, że nie wiedzą nic lepszego. W przeciwnym razie po prostu skorzystaliby z kompilatora. Pierwszym problemem jest to, że nie poprosiłeś o więcej szczegółów na temat tego, co dokładnie chciała zrobić. Czy chciała to uruchomić przez kilka różnych lat? Czy chciała tego w pliku CSV? Nie dowiedziałeś się, jakie decyzje chce sama podjąć, i o co poprosiła cię, abyś wymyślił i zdecydował za nią. Po ustaleniu, jakie decyzje należy odroczyć, możesz dowiedzieć się, jak przekazać te decyzje za pomocą parametrów (i innych konfigurowalnych środków).

To powiedziawszy, większość klientów nie komunikuje się, nie zakłada lub nie wie o pewnych szczegółach (czyli decyzjach), które naprawdę chcieliby sami zrobić, lub których tak naprawdę nie chcą (ale brzmi to niesamowicie). Dlatego ważne są metody pracy takie jak PDSA (plan-opracowanie-badanie-akt). Zaplanowałeś pracę zgodnie z wymaganiami, a następnie opracowałeś zestaw decyzji (kod). Teraz nadszedł czas, aby przestudiować go, samodzielnie lub z klientem, i nauczyć się nowych rzeczy, a te informują o dalszym myśleniu. Wreszcie działaj zgodnie ze swoimi nowymi spostrzeżeniami - zaktualizuj wymagania, dopracuj proces, zdobądź nowe narzędzia itp. Następnie zacznij planowanie ponownie. To z czasem ujawniłoby wszelkie ukryte wymagania i świadczy o postępie wielu klientów.

Wreszcie. Twój czas jest ważny ; jest bardzo realny i bardzo skończony. Każda twoja decyzja pociąga za sobą wiele innych ukrytych decyzji i na tym właśnie polega tworzenie oprogramowania. Opóźnienie decyzji jako argumentu może uprościć bieżącą funkcję, ale sprawia, że ​​gdzie indziej staje się bardziej złożona. Czy ta decyzja jest istotna w tej innej lokalizacji? Czy jest to bardziej odpowiednie tutaj? Czyją decyzję należy podjąć? Ty decydujesz o tym; to jest kodowanie. Jeśli często powtarzasz zestawy decyzji, bardzo realna jest kodyfikacja ich w ramach abstrakcji. XKCD ma tutaj przydatną perspektywę. Ma to znaczenie na poziomie systemu, czy to funkcji, modułu, programu itp.

Porady na początku sugerują, że decyzje, do których Twoja funkcja nie ma prawa podejmować, powinny być przekazywane jako argument. Problem polega na tym, że DestroyBaghdadfunkcja może być funkcją, która ma to prawo.


+1 uwielbiam część dotyczącą kompilatora!
Lee

4

Jest tu wiele długich odpowiedzi, ale szczerze mówiąc, myślę, że to bardzo proste

Wszelkie zakodowane na stałe informacje w funkcji, które nie są częścią nazwy funkcji, powinny być parametrem.

więc w twojej funkcji

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(zoneId -> {
            LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2018, 1, 1), LocalTime.of(0, 0, 0));
            ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
            while (2018 == now.getYear()) {
                int hour = now.getHour();
                now = now.plusHours(1);
                if (now.getHour() == hour) {
                    System.out.println(now);
                }
            }
        });
    }
}

Ty masz:

The zoneIds
2018, 1, 1
System.out

Więc przeniósłbym to wszystko do parametrów w takiej czy innej formie. Można argumentować, że zoneIds są niejawne w nazwie funkcji, być może chciałbyś uczynić to jeszcze bardziej, zmieniając ją na „DaylightSavingsAroundTheWorld” lub coś w tym rodzaju

Nie masz ciągu formatu, więc dodanie jednego jest żądaniem funkcji i powinieneś skierować swoją żonę do instancji rodziny Jira. Można go zaliczyć do zaległości i ustalić priorytet na odpowiednim posiedzeniu komitetu zarządzającego projektem.


1
Ty (zwracając się do OP) z pewnością nie powinieneś dodawać ciągu formatującego, ponieważ nie powinieneś drukować niczego. Jedyną rzeczą w tym kodzie, która absolutnie uniemożliwia jego ponowne użycie, jest to, że drukuje. Powinien zwrócić strefy lub mapę stref do momentu, w którym opuszczą czas letni. (Chociaż dlaczego tylko identyfikuje kiedy udać się czas letni, a nie na DST, nie rozumiem Nie wydaje się, aby dopasować oświadczenie problem..)
David Conrad

wymagane jest drukowanie na konsoli. możesz zablokować ciasne sprzężenie, przekazując strumień wyjściowy jako parametr, jak sugeruję
Ewan

1
Mimo to, jeśli chcesz, aby kod był wielokrotnego użytku, nie powinieneś drukować na konsoli. Napisz metodę, która zwraca wyniki, a następnie napisz program wywołujący, który je pobierze i wydrukuje. To także sprawia, że ​​można to przetestować. Jeśli chcesz, aby generowała dane wyjściowe, nie przekazałbym strumienia wyjściowego, przekazałbym konsumenta.
David Conrad

nasz strumień transmisji jest konsumentem
Ewan


4

Krótko mówiąc, nie konstruuj swojego oprogramowania pod kątem możliwości ponownego użycia, ponieważ żaden użytkownik końcowy nie dba o to, czy twoje funkcje mogą być ponownie użyte. Zamiast tego inżynier zrozumiałości projektu - czy mój kod jest łatwy do zrozumienia dla kogoś innego lub dla mojej przyszłej zapomnianej osoby? - i elastyczność projektowania- kiedy nieuchronnie muszę naprawić błędy, dodać funkcje lub w inny sposób zmodyfikować funkcjonalność, w jakim stopniu mój kod będzie odporny na zmiany? Jedyne, na czym zależy Twojemu klientowi, to jak szybko możesz odpowiedzieć, kiedy zgłosi błąd lub poprosi o zmianę. Nawiasem mówiąc, zadawanie tych pytań na temat projektu powoduje, że kod nadaje się do ponownego użycia, ale takie podejście pozwala skupić się na unikaniu prawdziwych problemów, które napotkasz w ciągu całego życia tego kodu, dzięki czemu możesz lepiej służyć użytkownikowi końcowemu, niż zajmować się wzniosłym, niepraktycznym „inżynieryjne” ideały zadowolenia karku.

W przypadku czegoś tak prostego, jak podany przykład, początkowa implementacja jest dobra ze względu na to, jak niewielka jest, ale ten prosty projekt będzie trudny do zrozumienia i kruchy, jeśli spróbujesz wcisnąć zbyt dużą elastyczność funkcjonalną (w przeciwieństwie do elastyczności projektowania) w jedna procedura. Poniżej znajduje się wyjaśnienie mojego preferowanego podejścia do projektowania złożonych systemów pod kątem zrozumiałości i elastyczności, które mam nadzieję pokażą, co mam na myśli. Nie zastosowałbym tej strategii do czegoś, co można by napisać w mniej niż 20 wierszach w jednej procedurze, ponieważ coś tak małego już spełnia moje kryteria zrozumiałości i elastyczności.


Przedmioty, a nie Procedury

Zamiast używać klas takich jak oldskulowe moduły z szeregiem wywoływanych procedur w celu wykonywania czynności, które powinno robić oprogramowanie, rozważ modelowanie domeny jako obiektów, które oddziałują na siebie i współpracują w celu wykonania danego zadania. Metody w paradygmacie zorientowanym obiektowo zostały pierwotnie stworzone, aby były sygnałami między obiektami, dzięki czemu Object1mogły powiedzieć, Object2że robią coś, cokolwiek to jest, i być może otrzymać sygnał zwrotny. Wynika to z faktu, że paradygmat zorientowany obiektowo polega na modelowaniu obiektów domeny i ich interakcji, a nie na wymyślnym sposobie organizowania tych samych starych funkcji i procedur paradygmatu imperatywnego. W przypadkuvoid destroyBaghdadna przykład, zamiast próbować pisać bezkontekstową ogólną metodę radzenia sobie ze zniszczeniem Bagdadu lub jakiejkolwiek innej rzeczy (która może szybko stać się złożona, trudna do zrozumienia i krucha), każda rzecz, która może zostać zniszczona, powinna być odpowiedzialna za zrozumienie, w jaki sposób zniszczyć się. Na przykład masz interfejs opisujący zachowanie rzeczy, które można zniszczyć:

interface Destroyable {
    void destroy();
}

Masz miasto, które implementuje ten interfejs:

class City implements Destroyable {
    @Override
    public void destroy() {
        ...code that destroys the city
    }
}

Nic, co wymaga zniszczenia instancji City, nigdy nie będzie miało znaczenia , jak to się stanie, więc nie ma powodu, aby ten kod istniał gdziekolwiek poza City::destroy, a rzeczywiście, intymna znajomość wewnętrznych mechanizmów działania Citysamego siebie byłaby ścisłym sprzężeniem, które zmniejsza elastyczność, ponieważ musisz wziąć pod uwagę te elementy zewnętrzne, jeśli kiedykolwiek będziesz musiał zmodyfikować zachowanie City. To jest prawdziwy cel enkapsulacji. Pomyśl o tym, jakby każdy obiekt miał swój własny interfejs API, który powinien umożliwić ci zrobienie z nim wszystkiego, co musisz, abyś mógł się martwić spełnieniem twoich żądań.

Delegacja, a nie „kontrola”

Teraz, czy twoja klasa wdrażająca jest, Cityczy Baghdadzależy od tego, jak ogólny jest proces niszczenia miasta. Najprawdopodobniej a Citybędzie składać się z mniejszych elementów, które będą musiały zostać zniszczone indywidualnie, aby doprowadzić do całkowitego zniszczenia miasta, więc w takim przypadku każdy z tych elementów również się wdroży Destroyable, i każdy z nich zostanie poinstruowany, Cityaby zniszczyć sami w taki sam sposób, jak ktoś z zewnątrz poprosił Cityo samozniszczenie.

interface Part extends Destroyable {
    ...part-specific methods
}

class Building implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
       ...code to destroy a building
    }
}

class Street implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
        ...code to destroy a building
    }
}

class City implements Destroyable {
    public List<Part> parts() {...}

    @Override
    public void destroy() {
        parts().forEach(Destroyable::destroy);            
    }
}

Jeśli chcesz naprawdę oszaleć i wdrożyć ideę Bombupuszczenia na lokalizację i niszczy wszystko w określonym promieniu, może to wyglądać mniej więcej tak:

class Bomb {
    private final Integer radius;

    public Bomb(final Integer radius) {
        this.radius = radius;
    }

    public void drop(final Grid grid, final Coordinate target) {
        new ObjectsByRadius(
            grid,
            target,
            this.radius
        ).forEach(Destroyable::destroy);
    }
}

ObjectsByRadiusreprezentuje zestaw obiektów, który jest obliczany na podstawie Bombdanych wejściowych, ponieważ Bombnie ma znaczenia, w jaki sposób obliczenia są wykonywane, o ile może on działać z obiektami. Nawiasem mówiąc, jest to wielokrotnego użytku, ale głównym celem jest odizolowanie obliczeń od procesów upuszczania Bombi niszczenia obiektów, abyś mógł zrozumieć każdy element i sposób, w jaki pasują do siebie, i zmienić zachowanie pojedynczego elementu bez konieczności przekształcania całego algorytmu .

Interakcje, nie algorytmy

Zamiast próbować odgadnąć odpowiednią liczbę parametrów dla złożonego algorytmu, bardziej sensowne jest modelowanie procesu jako zestawu interaktywnych obiektów, z których każdy ma niezwykle wąskie role, ponieważ daje to możliwość modelowania złożoności twojego przetwarzaj przez interakcje między tymi dobrze zdefiniowanymi, łatwymi do zrozumienia i prawie niezmiennymi obiektami. Prawidłowe wykonanie sprawia, że ​​nawet niektóre z najbardziej złożonych modyfikacji są tak trywialne, jak implementacja jednego lub dwóch interfejsów i przerobienie, które obiekty są tworzone w twojej main()metodzie.

Dałbym ci coś do twojego oryginalnego przykładu, ale szczerze mówiąc, nie mogę zrozumieć, co to znaczy „wydrukować ... Oszczędności na światło dzienne”. Co mogę powiedzieć o tej kategorii problemu, to to, że za każdym razem, gdy wykonujesz obliczenia, których wynik można sformatować na wiele sposobów, mój preferowany sposób na rozbicie tego wygląda następująco:

interface Result {
    String print();
}

class Caclulation {
    private final Parameter paramater1;

    private final Parameter parameter2;

    public Calculation(final Parameter parameter1, final Parameter parameter2) {
        this.parameter1 = parameter1;
        this.parameter2 = parameter2;
    }

    public Result calculate() {
        ...calculate the result
    }
}

class FormattedResult {
    private final Result result;

    public FormattedResult(final Result result) {
        this.result = result;
    }

    @Override
    public String print() {
        ...interact with this.result to format it and return the formatted String
    }
}

Ponieważ twój przykład używa klas z biblioteki Java, które nie obsługują tego projektu, możesz po prostu użyć interfejsu API ZonedDateTimebezpośrednio. Chodzi o to, że każde obliczenie jest zawarte w swoim własnym obiekcie. Nie przyjmuje żadnych założeń co do tego, ile razy powinien działać i jak sformatować wynik. Dotyczy wyłącznie wykonywania najprostszej formy obliczeń. To sprawia, że ​​jest łatwy do zrozumienia i elastyczny w zmianie. Podobnie Resultdotyczy wyłącznie enkapsulacji wyniku obliczeń, a FormattedResultdotyczy wyłącznie interakcji z Resultformatowaniem zgodnie z określonymi przez nas regułami. W ten sposób,możemy znaleźć idealną liczbę argumentów dla każdej z naszych metod, ponieważ każda z nich ma dobrze zdefiniowane zadanie . O wiele łatwiej jest modyfikować poruszanie się do przodu, o ile interfejsy się nie zmieniają (co nie jest tak prawdopodobne, jeśli odpowiednio zminimalizujesz odpowiedzialność za swoje obiekty). Naszamain()metoda może wyglądać następująco:

class App {
    public static void main(String[] args) {
        final List<Set<Paramater>> parameters = ...instantiated from args
        parameters.forEach(set -> {
            System.out.println(
                new FormattedResult(
                    new Calculation(
                        set.get(0),
                        set.get(1)
                    ).calculate()
                ).print()
            );
        });
    }
}

W rzeczywistości programowanie zorientowane obiektowo zostało wymyślone specjalnie jako rozwiązanie problemu złożoności / elastyczności paradygmatu imperatywnego, ponieważ nie ma po prostu dobrej odpowiedzi (na którą każdy może się zgodzić lub niezależnie), jak optymalnie określić funkcje imperatywne i procedury w ramach tego idiomu.


To bardzo szczegółowa i przemyślana odpowiedź, ale niestety myślę, że nie trafia w sedno tego, o co OP tak naprawdę chciał. Nie prosił o lekcję dobrych praktyk OOP, aby rozwiązać swój szczególny przykład, pytał o kryteria, przy których decydujemy o inwestowaniu czasu w rozwiązanie vs uogólnienie.
wałek klonowy

@maple_shaft Może przegapiłem znak, ale myślę, że ty też. PO nie pyta o inwestycję czasu a generalizację. Pyta: „Skąd mam wiedzieć, jak wielokrotne powinny być moje metody?” Następnie pyta w swoim pytaniu: „Jeśli destroyCity (miasto miasta) jest lepsze niż destroyBaghdad (), czy takeActionOnCity (akcja akcji, miasto miasta) jest jeszcze lepsze? Dlaczego / dlaczego nie?” Opowiedziałem się za alternatywnym podejściem do rozwiązań inżynieryjnych, które, jak sądzę, rozwiązuje problem polegający na ustaleniu, jak generyczne są metody, i podałem przykłady na poparcie mojego twierdzenia. Przepraszam, że ci się nie podobało.
Stuporman

@maple_shaft Szczerze mówiąc, tylko OP może ustalić, czy moja odpowiedź była istotna dla jego pytania, ponieważ reszta z nas może walczyć w wojnach, broniąc naszych interpretacji jego zamiarów, z których wszystkie mogą być równie błędne.
Stuporman

@maple_shaft Dodałem wprowadzenie, aby spróbować wyjaśnić, w jaki sposób odnosi się ono do pytania i podałem wyraźny podział między odpowiedzią a przykładową implementacją. Czy to jest lepsze?
Stuporman

1
Szczerze mówiąc, jeśli zastosujesz wszystkie te zasady, odpowiedź przyjdzie naturalnie, będzie gładka i czytelna. Dodatkowo wymienny bez większego zamieszania. Nie wiem kim jesteś, ale szkoda, że ​​nie było cię więcej! Ciągle wyrywam Reaktywny kod dla przyzwoitego OO, i ZAWSZE jest o połowę mniejszy, bardziej czytelny, bardziej kontrolowany i wciąż ma wątki / dzielenie / mapowanie. Myślę, że React jest dla osób, które nie rozumieją „podstawowych” pojęć, które właśnie wymieniłeś.
Stephen J

3

Doświadczenie , wiedza o domenach i recenzje kodów.

I niezależnie od tego, ile masz doświadczenia , wiedzy na temat domeny lub zespołu , nie możesz uniknąć konieczności refaktoryzacji w razie potrzeby.


Dzięki Experience zaczniesz rozpoznawać wzorce w metodach (i klasach) niespecyficznych dla domeny, które piszesz. A jeśli w ogóle interesujesz się kodem DRY, poczujesz złe uczucia, gdy napotkasz metodę, o której instynktownie wiesz , że napiszesz różne wersje w przyszłości. Zatem zamiast tego intuicyjnie napiszesz sparametryzowany najmniej powszechny mianownik.

(To doświadczenie może instynktownie przenieść się na niektóre obiekty i metody domeny).

Dzięki Domain Knowledge masz poczucie, z którymi koncepcjami biznesowymi są ściśle powiązane, które mają zmienne, które są dość statyczne itp.

Dzięki przeglądom kodu niedostateczna i nadmierna parametryzacja będzie częściej wychwytywana, zanim stanie się kodem produkcyjnym, ponieważ Twoi rówieśnicy (miejmy nadzieję) będą mieli unikalne doświadczenia i perspektywy, zarówno w dziedzinie, jak i ogólnie w kodowaniu.


To powiedziawszy, nowi programiści zazwyczaj nie będą mieli tych Spidey Senses ani doświadczonej grupy rówieśników, na których mogliby się oprzeć. Nawet doświadczeni programiści korzystają z podstawowej dyscypliny, która poprowadzi ich przez nowe wymagania - lub przez mgliste dni. Oto, co zasugeruję na początek :

  • Zacznij od naiwnej implementacji przy minimalnej parametryzacji.
    (Uwzględnij wszelkie parametry, które już wiesz, że będą potrzebne, oczywiście ...)
  • Usuń magiczne liczby i łańcuchy, przenosząc je do konfiguracji i / lub parametrów
  • Rozłóż metody „duże” na mniejsze, dobrze nazwane metody
  • Refaktoryzuj wysoce zbędne metody (jeśli jest to wygodne) do wspólnego mianownika, parametryzując różnice.

Kroki te niekoniecznie występują w podanej kolejności. Jeśli usiądziesz, aby napisać metodę, o której wiesz, że jest wysoce zbędna w stosunku do istniejącej metody , przejdź od razu do refaktoryzacji, jeśli jest to wygodne. (Jeśli refaktoryzacja nie zajmie znacznie więcej czasu niż byłoby po prostu napisać, przetestować i utrzymać dwie metody).

Ale oprócz tego, że mam dużo doświadczenia i tak dalej, radzę dość minimalistycznym OSUSZANIEM kodu. Refaktoryzacja oczywistych naruszeń nie jest trudna. A jeśli jesteś zbyt gorliwy , możesz skończyć z kodem „nadmiernie przesuszonym”, który jest jeszcze trudniejszy do odczytania, zrozumienia i utrzymania niż ekwiwalent „WET”.


2
Więc nie ma właściwej odpowiedzi na If destoryCity(City city) is better than destoryBaghdad(), is takeActionOnCity(Action action, City city) even better?? To pytanie tak / nie, ale nie ma odpowiedzi, prawda? Czy początkowe założenie jest błędne, destroyCity(City)niekoniecznie musi być lepsze i tak naprawdę zależy ? Więc to nie znaczy, że nie jestem niewykształconym etycznie inżynierem oprogramowania, ponieważ bezpośrednio wdrożyłem bez żadnych parametrów? Mam na myśli jaka jest odpowiedź na konkretne pytanie, które zadaję?
Koray Tugay

Twoje pytanie zawiera kilka pytań. Odpowiedź na pytanie tytułowe brzmi: „doświadczenie, znajomość domeny, recenzje kodu ... i… nie bój się refaktoryzować”. Odpowiedź na dowolny konkretny „czy są to właściwe parametry dla metody ABC” pytanie brzmi… „Nie wiem. Dlaczego pytasz? Czy coś jest nie tak z liczbą parametrów, jakie obecnie ma ??? Jeśli tak, to wymów to. Napraw to. ” ... mogę odesłać cię do „POAP” w celu uzyskania dalszych wskazówek: musisz zrozumieć, dlaczego robisz to, co robisz!
svidgen

To znaczy ... cofnijmy się nawet od tej destroyBaghdad()metody. Jaki jest kontekst? Czy to gra wideo, w której koniec gry powoduje zniszczenie Bagdadu? Jeśli tak, to destroyBaghdad()może być całkowicie rozsądna nazwa / podpis metody ...
svidgen

1
Więc nie zgadzasz się z cytowanym cytatem w moim pytaniu, prawda? It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure.Gdybyś był w pokoju z Nathanielem Borensteinem, kłóciłbyś się z nim, że tak naprawdę zależy, a jego wypowiedź jest nieprawidłowa? To znaczy, że to piękne, że wiele osób odpowiada, spędzając czas i energię, ale nigdzie nie widzę żadnej konkretnej odpowiedzi. Spidey-zmysły, recenzje kodu .. Ale na co jest odpowiedź is takeActionOnCity(Action action, City city) better? null?
Koray Tugay

1
@svidgen Oczywiście innym sposobem na wyodrębnienie tego bez dodatkowego wysiłku jest odwrócenie zależności - funkcja zwraca listę miast zamiast wykonywać na nich jakiekolwiek działania (np. „println” w oryginalnym kodzie). W razie potrzeby można to streścić dalej, ale tylko ta jedna zmiana sama zajmuje około połowy dodatkowych wymagań w pierwotnym pytaniu - i zamiast jednej nieczystej funkcji, która może robić różnego rodzaju złe rzeczy, po prostu masz funkcję która zwraca listę, a dzwoniący robi złe rzeczy.
Luaan

2

Ta sama odpowiedź, co w przypadku jakości, użyteczności, zadłużenia technicznego itp .:

Jako wielokrotnego użytku, jak ty, użytkownik, 1 trzeba je będzie

Zasadniczo jest to wezwanie do oceny - czy koszt zaprojektowania i utrzymania abstrakcji zostanie zwrócony przez koszt (= czas i wysiłek), to cię uratuje.

  • Zwróć uwagę na wyrażenie „w dół linii”: tutaj jest mechanika wypłaty, więc będzie to zależeć od tego, ile będziesz dalej pracował z tym kodem. Na przykład:
    • Czy jest to projekt jednorazowy, czy będzie on stopniowo ulepszany przez długi czas?
    • Czy jesteś pewien swojego projektu, czy prawdopodobnie będziesz musiał złomować lub w inny sposób drastycznie zmienić go na następny projekt / kamień milowy (np. Wypróbować inny framework)?
  • Przewidywana korzyść zależy również od Twojej zdolności do przewidywania przyszłości (zmiany w aplikacji). Czasami można w rozsądny sposób zobaczyć miejsce (miejsca), w które planuje się Twoja aplikacja. Więcej razy myślisz, że możesz, ale tak naprawdę nie możesz. Podstawowe zasady tutaj to zasada YAGNI i zasada trzech - oba kładą nacisk na wypracowanie tego, co wiesz, teraz.

1 To jest konstrukcja kodu, więc w tym przypadku jesteś „użytkownikiem” - użytkownikiem kodu źródłowego


1

Istnieje przejrzysty proces, który można wykonać:

  • Napisz niezaliczony test dla pojedynczej cechy, która sama w sobie jest „rzeczą” (tj. Nie jakimś arbitralnym podziałem cechy, w którym żadna połowa nie ma sensu).
  • Napisz absolutny minimalny kod, aby był zielony, a nie więcej linii.
  • Wypłukać i powtórzyć.
  • (W razie potrzeby bezlitośnie refaktoryzuj, co powinno być łatwe ze względu na świetny zasięg testu).

Wynika to z - przynajmniej zdaniem niektórych osób - dość optymalnego kodu, ponieważ jest tak mały, jak to możliwe, każda ukończona funkcja zajmuje tak mało czasu, jak to możliwe (co może, ale nie musi być prawdą, jeśli spojrzysz na gotowy produkt po refaktoryzacji) i ma bardzo dobre pokrycie testowe. W zauważalny sposób unika się również zbyt ogólnych metod lub klas.

Daje to również bardzo jasne instrukcje, kiedy należy uogólniać, a kiedy specjalizować.

Uważam przykład twojego miasta za dziwny; Prawdopodobnie nigdy nie zapisałbym na stałe nazwy miasta. Jest tak oczywiste, że dodatkowe miasta zostaną uwzględnione później, bez względu na to, co robisz. Ale innym przykładem mogą być kolory. W niektórych okolicznościach możliwe byłoby zakodowanie na czerwono lub zielono. Na przykład sygnalizacja świetlna jest tak wszechobecnym kolorem, że można po prostu uciec (i zawsze można zrefaktować). Różnica polega na tym, że „czerwony” i „zielony” mają uniwersalne, „zakodowane na stałe” znaczenie w naszym świecie, jest niewiarygodnie mało prawdopodobne, że kiedykolwiek się zmieni, i nie ma tak naprawdę alternatywy.

Twoja pierwsza metoda oszczędzania światła dziennego jest po prostu zepsuta. Mimo że jest zgodny ze specyfikacjami, zakodowany w 2018 r. Jest szczególnie zły, ponieważ a) nie jest wymieniony w „umowie” technicznej (w tym przypadku w metodzie), oraz b) wkrótce będzie nieaktualny, więc pęknięcie jest wliczony od samego początku. W przypadku rzeczy związanych z datą / godziną bardzo rzadko sensowne jest kodowanie konkretnej wartości, ponieważ czas płynie. Ale poza tym wszystko inne jest przedmiotem dyskusji. Jeśli podasz prosty rok, a następnie zawsze obliczysz cały rok, śmiało. Większość rzeczy, które wymieniłeś (formatowanie, wybór mniejszego zakresu itp.) Krzyczy, że twoja metoda robi zbyt wiele, i zamiast tego prawdopodobnie powinna zwrócić listę / tablicę wartości, aby osoba dzwoniąca mogła sama przeprowadzić formatowanie / filtrowanie.

Ale pod koniec dnia większość z nich to opinia, smak, doświadczenie i osobiste nastawienie, więc nie przejmuj się tym zbytnio.


Jeśli chodzi o akapit od drugiego do ostatniego - spójrz na początkowo podane „wymagania”, tj. Te, na których oparta była pierwsza metoda. Określa 2018, więc kod jest poprawny technicznie (i prawdopodobnie pasowałby do twojego podejścia opartego na funkcjach).
dwizum

@dwizum, jest poprawne w odniesieniu do wymagań, ale nazwa metody wprowadza w błąd. W 2019 r. Każdy programista, który patrzy tylko na nazwę metody, założyłby, że robi cokolwiek (być może zwróci wartości dla bieżącego roku), a nie 2018 ... Dodam zdanie do odpowiedzi, aby wyjaśnić, co miałem na myśli.
AnoE

1

Doszedłem do wniosku, że istnieją dwa rodzaje kodu wielokrotnego użytku:

  • Kod, który można ponownie wykorzystać, ponieważ jest to tak fundamentalna, podstawowa rzecz.
  • Kod, który można ponownie wykorzystać, ponieważ ma parametry, przesłonięcia i przechwyty dla wszystkich.

Pierwszy rodzaj ponownego użycia jest często dobrym pomysłem. Odnosi się do rzeczy takich jak listy, mapy skrótów, magazyny kluczy / wartości, dopasowania łańcuchów (np. Regex, glob, ...), krotki, unifikacja, drzewa wyszukiwania (najpierw głębokość, najpierw szerokość, pogłębianie iteracyjne, ...) , kombinatory parsera, pamięci podręczne / moduły pamięci, czytniki / moduły zapisujące dane (wyrażenia s, XML, JSON, protobuf, ...), kolejki zadań itp.

Te rzeczy są tak ogólne, w bardzo abstrakcyjny sposób, że są ponownie używane w każdym miejscu w codziennym programowaniu. Jeśli zauważysz, że piszesz kod specjalnego przeznaczenia, który byłby prostszy, gdyby był bardziej abstrakcyjny / ogólny (np. Jeśli mamy „listę zamówień klientów”, możemy wyrzucić rzeczy „zamówienia klientów”, aby uzyskać „listę” ), może to być dobry pomysł, aby to wyciągnąć. Nawet jeśli nie zostanie ponownie użyty, pozwala nam oddzielić niezwiązaną funkcjonalność.

Drugi rodzaj polega na tym, że mamy jakiś konkretny kod, który rozwiązuje prawdziwy problem, ale robi to poprzez podejmowanie wielu decyzji. Możemy uczynić go bardziej ogólnym / wielokrotnego użytku poprzez „miękkie kodowanie” tych decyzji, np. Przekształcanie ich w parametry, komplikowanie implementacji i pieczenie w jeszcze bardziej konkretnych szczegółach (tj. Wiedzy o tym, jakich haków możemy chcieć w celu zastąpienia). Twój przykład wydaje się być tego rodzaju. Problem z tego rodzaju możliwością ponownego użycia polega na tym, że możemy skończyć z odgadywaniem przypadków użycia innych ludzi lub nas samych w przyszłości. W końcu możemy mieć tak wiele parametrów, że nasz kod nie będzie użyteczny, nie mówiąc już o wielokrotnym użyciu! Innymi słowy, dzwonienie wymaga więcej wysiłku niż napisanie własnej wersji. W tym miejscu ważny jest YAGNI (Nie potrzebujesz go). Wiele razy takie próby „wielokrotnego użytku” kodu nie są ponownie wykorzystywane, ponieważ mogą być one niezgodne z tymi przypadkami użycia bardziej fundamentalnie, niż mogą to wyjaśnić parametry, lub ci potencjalni użytkownicy wolą rzucić własne (cholera, spójrz na wszystkie standardy i biblioteki, których autorzy poprzedzili słowo „Simple”, aby odróżnić je od poprzedników!).

Ta druga forma „wielokrotnego użytku” powinna być zasadniczo wykonywana w miarę potrzeb. Jasne, możesz tam umieścić pewne „oczywiste” parametry, ale nie próbuj przewidywać przyszłości. YAGNI.


Czy możemy powiedzieć, że zgadzasz się, że moje pierwsze ujęcie było w porządku, nawet jeśli rok był na stałe zakodowany? A jeśli początkowo wdrażasz ten wymóg, czy uczyniłbyś rok parametrem w swoim pierwszym ujęciu?
Koray Tugay

Twoje pierwsze ujęcie było w porządku, ponieważ wymagano jednorazowego skryptu, by „sprawdzić coś”. Nie zdaje testu „etycznego”, ale nie zdaje testu „bez dogmatów”. „Może powiedzieć…” wymyśla wymagania, których nie potrzebujesz.
Warbo

Nie możemy powiedzieć, które „zniszczyć miasto” jest „lepsze” bez dodatkowych informacji: destroyBaghdadjest to jednorazowy skrypt (a przynajmniej idempotentny). Może zniszczenie dowolnego miasta byłoby ulepszeniem, ale co, jeśli uda destroyBaghdadsię zalać Tygrys? Może to być wielokrotnego użytku dla Mosulu i Basry, ale nie dla Mekki i Atlanty.
Warbo

Rozumiem, więc nie zgadzasz się z Nathanielem Borensteinem, właścicielem cytatu. Staram się rozumieć powoli, myślę, czytając wszystkie te odpowiedzi i dyskusje.
Koray Tugay

1
Lubię to zróżnicowanie. Nie zawsze jest to jasne i zawsze występują „przypadki graniczne”. Ale ogólnie jestem też fanem „bloków konstrukcyjnych” (często w formie staticmetod), które są czysto funkcjonalne i na niskim poziomie, i w przeciwieństwie do tego, decydowanie o „konfigurowaniu parametrów i zaczepów” jest zwykle tym, gdzie musisz zbudować pewne struktury, które wymagają uzasadnienia.
Marco13

1

Istnieje już wiele doskonałych i skomplikowanych odpowiedzi. Niektóre z nich zagłębiają się w szczegółowe szczegóły, przedstawiają pewne poglądy na temat metodologii tworzenia oprogramowania w ogóle, a niektóre z nich z pewnością zawierają kontrowersyjne elementy lub „opinie”.

W odpowiedzi Warbo wskazano już na różne rodzaje ponownego użycia. Mianowicie, czy coś nadaje się do ponownego użycia, ponieważ jest podstawowym elementem składowym, czy też coś może być ponownie użyte, ponieważ jest w jakiś sposób „ogólne”. Odnosząc się do tego ostatniego, nie jest czymś, co Pomyślę jak jakiegoś środka do ponownego użycia:

Czy jedna metoda może naśladować inną.

Jeśli chodzi o przykład z pytania: Wyobraź sobie, że metoda

void dayLightSavings()

była implementacja funkcjonalności, której zażądał klient. Będzie to więc coś, z czego inni programiści powinni korzystać , a zatem metoda publiczna , jak w

publicvoid dayLightSavings()

Można to wdrożyć, jak pokazano w odpowiedzi. Teraz ktoś chce sparametryzować go z rokiem. Możesz więc dodać metodę

publicvoid dayLightSavings(int year)

i zmień oryginalną implementację na po prostu

public void dayLightSavings() {
    dayLightSavings(2018);
}

Kolejne „żądania funkcji” i uogólnienia są zgodne z tym samym schematem. Więc jeśli i tylko wtedy, gdy istnieje zapotrzebowanie na postaci najbardziej rodzajowego, można go realizować, wiedząc, że to najbardziej generic forma pozwala na trywialne implementacji bardziej konkretnych z nich:

public void dayLightSavings() {
    dayLightSavings(2018, 0, 12, 0, 12, new DateTimeFormatter(...));
}

Jeśli spodziewałeś się przyszłych rozszerzeń i próśb o funkcje, a miałeś do dyspozycji trochę czasu i chciałeś spędzić nudny weekend z (potencjalnie bezużytecznymi) uogólnieniami, od samego początku mogłeś zacząć od najbardziej ogólnego. Ale tylko jako metoda prywatna . Tak długo, jak ujawniłeś tylko prostą metodę, której zażądał klient jako metodę publiczną , jesteś bezpieczny.

tl; dr :

Pytanie w rzeczywistości nie polega na tym, „jak powinna być metoda wielokrotnego użytku”. Pytanie brzmi, jak duża część tego ponownego użycia jest ujawniona i jak wygląda interfejs API. Stworzenie niezawodnego API, który wytrzyma próbę czasu (nawet jeśli pojawią się dalsze wymagania) jest sztuką i rzemiosłem, a temat jest zbyt skomplikowany, aby go tutaj opisać. Na początek spójrz na tę prezentację Joshua Blocha lub wiki z książki projektowej API .


dayLightSavings()dzwonienie dayLightSavings(2018)nie wydaje mi się dobrym pomysłem.
Koray Tugay,

@KorayTugay Gdy początkowym żądaniem jest wydrukowanie „oszczędności na dzień w 2018 r.”, To jest w porządku. W rzeczywistości jest to dokładnie metoda, którą pierwotnie zaimplementowałeś. Gdyby wydrukowało „oszczędności czasu na bieżący rok , to oczywiście zadzwoniłbyś dayLightSavings(computeCurrentYear());. ...
Marco13

0

Dobrą zasadą jest: twoja metoda powinna być tak samo przydatna jak… wielokrotnego użytku.

Jeśli spodziewasz się, że będziesz wywoływać metodę tylko w jednym miejscu, powinna ona mieć tylko parametry znane stronie wywołującej i niedostępne dla tej metody.

Jeśli masz więcej dzwoniących, możesz wprowadzić nowe parametry, o ile inni dzwoniący mogą przekazać te parametry; w przeciwnym razie potrzebujesz nowej metody.

Ponieważ liczba dzwoniących może rosnąć w czasie, musisz być przygotowany na refaktoryzację lub przeładowanie. W wielu przypadkach oznacza to, że powinieneś czuć się bezpiecznie, wybierając wyrażenie i uruchamiając akcję „wyodrębnij parametr” swojego IDE.


0

Bardzo krótka odpowiedź: im mniej sprzężenia lub zależności od innego kodu, który ma moduł ogólny, tym bardziej może być wielokrotnego użytku.

Twój przykład zależy tylko od

import java.time.*;
import java.util.Set;

więc teoretycznie może być wysoce wielokrotnego użytku.

W praktyce nie sądzę, że kiedykolwiek będziesz mieć drugą skrzynkę użytkową, która potrzebuje tego kodu, więc zgodnie z zasadą yagni nie uczynię go wielokrotnego użytku, jeśli nie będzie więcej niż 3 różne projekty, które potrzebują tego kodu.

Inne aspekty ponownego użycia to łatwość użycia i dokumentacja, które są powiązane z Test Driven Development : Jest to przydatne, jeśli masz prosty test jednostkowy, który demonstruje / dokumentuje łatwe użycie twojego ogólnego modułu jako przykładu kodowania dla użytkowników twojej biblioteki lib.


0

To dobra okazja, aby podać regułę, którą ostatnio wymyśliłem:

Bycie dobrym programistą oznacza umiejętność przewidywania przyszłości.

Oczywiście jest to absolutnie niemożliwe! W końcu nigdy nie wiadomo na pewno, jakie uogólnienia okażą się przydatne później, jakie pokrewne zadania będziesz chciał wykonać, jakie nowe funkcje będą chcieli Twoi użytkownicy, i c. Ale doświadczenie czasami daje ogólne pojęcie o tym, co może się przydać.

Inne czynniki, z którymi musisz się zrównoważyć, to ilość dodatkowego czasu i wysiłku oraz o ile bardziej skomplikowany byłby twój kod. Czasami masz szczęście, a rozwiązanie bardziej ogólnego problemu jest w rzeczywistości prostsze! (Przynajmniej koncepcyjnie, jeśli nie w ilości kodu.) Ale częściej koszty komplikacji, a także czasu i wysiłku.

Więc jeśli uważasz, że uogólnienie jest bardzo potrzebne, często warto to zrobić (chyba że doda to dużo pracy lub złożoności); ale jeśli wydaje się to znacznie mniej prawdopodobne, to prawdopodobnie nie jest (chyba że jest to bardzo łatwe i / lub upraszcza kod).

(Na przykład: w zeszłym tygodniu otrzymałem specyfikację dla działań, które system powinien podjąć dokładnie 2 dni po tym, jak coś wygasło. Oczywiście ustawiłem 2-dniowy okres jako parametr. W tym tygodniu ludzie biznesu byli zachwyceni, ponieważ właśnie miałem prosić o to ulepszenie! Miałem szczęście: była to łatwa zmiana i domyśliłem się, że prawdopodobnie będzie ona pożądana. Często trudniej jest ją ocenić. Ale nadal warto próbować przewidzieć, a doświadczenie jest często dobrym przewodnikiem .)


0

Po pierwsze, najlepszą odpowiedzią na pytanie „Skąd mam wiedzieć, jak powinny być używane moje metody?” Jest „doświadczenie”. Zrób to kilka tysięcy razy, a zazwyczaj uzyskasz właściwą odpowiedź. Ale jako zwiastun mogę ci dać ostatnią linia tej odpowiedzi: Twój klient powie ci, ile elastyczności i ile warstw uogólnienia powinieneś szukać.

Wiele z tych odpowiedzi zawiera konkretne porady. Chciałem dać coś bardziej ogólnego ... ponieważ ironia jest zbyt zabawna, aby z niej zrezygnować!

Jak zauważyły ​​niektóre odpowiedzi, ogólność jest droga. Jednak tak naprawdę nie jest. Nie zawsze. Zrozumienie kosztów ma zasadnicze znaczenie dla grania w gry wielokrotnego użytku.

Skupiam się na przestawianiu skali na „nieodwracalne” na „odwracalne”. To płynna skala. Jedyną naprawdę nieodwracalną rzeczą jest „czas spędzony na projekcie”. Nigdy nie odzyskasz tych zasobów. Nieco odwracalne mogą być sytuacje „złotych kajdanek”, takie jak Windows API. Przestarzałe funkcje pozostają w tym interfejsie API przez dziesięciolecia, ponieważ wymaga tego model biznesowy Microsoft. Jeśli masz klientów, których relacje zostałyby trwale uszkodzone przez cofnięcie niektórych funkcji API, należy to traktować jako nieodwracalne. Patrząc na drugi koniec skali, masz na przykład prototypowy kod. Jeśli nie podoba ci się, dokąd idzie, możesz po prostu go wyrzucić. Nieco odwracalne mogą być interfejsy API do użytku wewnętrznego. Można je refaktoryzować bez przeszkadzania klientowi,czas (najbardziej nieodwracalny zasób ze wszystkich!)

Więc umieść je na skali. Teraz możesz zastosować heurystykę: im bardziej coś jest odwracalne, tym więcej możesz użyć do przyszłych działań. Jeśli coś jest nieodwracalne, używaj go tylko do konkretnych zadań kierowanych przez klienta. Dlatego widzisz zasady podobne do tych z ekstremalnego programowania, które sugerują robienie tylko tego, o co prosi klient, i nic więcej. Zasady te są dobre w upewnieniu się, że nie robisz czegoś, czego żałujesz.

Rzeczy takie jak zasada DRY sugerują sposób na zmianę tej równowagi. Jeśli się powtarzasz, jest to okazja do stworzenia zasadniczo wewnętrznego interfejsu API. Żaden klient tego nie widzi, więc zawsze możesz to zmienić. Gdy już będziesz mieć ten wewnętrzny interfejs API, możesz zacząć grać z przyszłościowymi rzeczami. Jak myślisz, ile różnych zadań związanych ze strefą czasową powierzy ci żona? Czy masz innych klientów, którzy mogą chcieć zadań opartych na strefie czasowej? Twoja elastyczność tutaj jest kupowana przez konkretne wymagania twoich obecnych klientów i wspiera potencjalne przyszłe wymagania przyszłych klientów.

To podejście do myślenia warstwowego, które naturalnie pochodzi z DRY, zapewnia generalizację, której potrzebujesz bez marnotrawstwa. Ale czy jest jakiś limit? Oczywiście że tak. Ale żeby to zobaczyć, musisz zobaczyć las dla drzew.

Jeśli masz wiele warstw elastyczności, często prowadzą one do braku bezpośredniej kontroli warstw napotykanych przez klientów. Miałem oprogramowanie, w którym miałem brutalne zadanie wyjaśnienia klientowi, dlaczego nie może mieć tego, czego chce ze względu na elastyczność wbudowaną w 10 warstw, których nigdy nie powinni widzieć. Napisaliśmy się w kąt. Zawiązaliśmy się w węzeł z całą elastycznością, która naszym zdaniem była potrzebna.

Więc kiedy wykonujesz tę sztuczkę uogólnienia / OSUSZANIA, zawsze miej puls na swoim kliencie . Jak myślisz, o co poprosi twoja żona? Czy jesteś w stanie zaspokoić te potrzeby? Jeśli masz talent, klient skutecznie powie ci o swoich przyszłych potrzebach. Jeśli nie masz talentu, cóż, większość z nas polega na zgadywaniu! (szczególnie w przypadku małżonków!) Niektórzy klienci będą chcieli dużej elastyczności i chętnie zaakceptują dodatkowe koszty związane z rozwojem wszystkich tych warstw, ponieważ bezpośrednio korzystają z elastyczności tych warstw. Inni klienci mają raczej stałe wymagania dotyczące niezachwiania i wolą bardziej bezpośredni rozwój. Twój klient powie ci, ile elastyczności i ile warstw uogólnienia powinieneś szukać.


Cóż, powinny być inne osoby, które zrobiły to 10000 razy, więc dlaczego miałbym to robić 10000 razy i zdobywać doświadczenie, skoro mogę uczyć się od innych? Ponieważ odpowiedź będzie inna dla każdej osoby, więc odpowiedzi od doświadczonych nie dotyczą mnie? Co Your customer will tell you how much flexibility and how many layers of generalization you should seek.to za świat?
Koray Tugay

@KorayTugay To świat biznesu. Jeśli Twoi klienci nie mówią Ci, co masz robić, to nie słuchasz wystarczająco dużo. Oczywiście, oni nie zawsze mówią ci słowami, ale mówią ci na inne sposoby. Doświadczenie pomaga słuchać bardziej subtelnych wiadomości. Jeśli nie masz jeszcze umiejętności, znajdź w swojej firmie kogoś, kto potrafi posłuchać subtelnych wskazówek klientów i nakieruj ich na wskazówki. Ktoś będzie miał tę umiejętność, nawet jeśli będzie dyrektorem generalnym lub w marketingu.
Cort Ammon

W twoim przypadku, gdybyś nie wyniósł śmieci, ponieważ byłeś zbyt zajęty kodowaniem uogólnionej wersji tego problemu strefy czasowej zamiast zhakowania konkretnego rozwiązania, jak by się czuł twój klient?
Cort Ammon

Zgadzasz się więc, że moje pierwsze podejście było słuszne, twarde kodowanie 2018 w pierwszym ujęciu zamiast parametryzowania roku? (przy okazji, tak naprawdę to nie słucha mojego klienta, myślę, że śmieciowy przykład. To znaczy znając twojego klienta. Nawet jeśli uzyskasz wsparcie od Wyroczni, nie ma subtelnych wiadomości do wysłuchania, gdy mówi, że potrzebuję listy światła dziennego zmiany na 2018 rok.) Dziękujemy za poświęcony czas i odpowiedź btw.
Koray Tugay

@KorayTugay Nie znając żadnych dodatkowych szczegółów, powiedziałbym, że kodowanie na stałe było właściwym podejściem. Nie wiedziałeś, czy będziesz potrzebować przyszłego kodu DLS, ani nie wiesz, jakiego rodzaju prośby mogłaby następnie udzielić. A jeśli twój klient próbuje cię przetestować, dostaje to, co dostaje = D
Cort Ammon

0

żaden etycznie wyszkolony inżynier oprogramowania nigdy nie wyraziłby zgody na napisanie procedury DestroyBaghdad. Podstawowa etyka zawodowa wymagałaby zamiast tego napisania procedury DestroyCity, której Bagdad mógłby zostać podany jako parametr.

Jest to coś znanego w kręgach zaawansowanych inżynierów oprogramowania jako „żart”. Żarty nie muszą być tym, co nazywamy „prawdziwym”, chociaż aby być zabawnym, zazwyczaj muszą wskazywać na coś prawdziwego.

W tym konkretnym przypadku „żart” nie jest „prawdziwy”. Praca polegająca na napisaniu ogólnej procedury niszczenia dowolnego miasta jest, możemy spokojnie założyć, o rząd wielkości przekraczający wymagany do zniszczenia jednego konkretnego miastaMiasto. W przeciwnym razie każdy, kto zniszczył jedno lub kilka miast (biblijny Joshua, powiedzmy, lub Prezydent Truman), mógłby w trywialny sposób uogólnić to, co zrobił i być w stanie zniszczyć absolutnie dowolne miasto do woli. W rzeczywistości tak nie jest. Metody, których ci dwaj ludzie znani używali do zniszczenia niewielkiej liczby konkretnych miast, niekoniecznie działałyby w każdym mieście w dowolnym momencie. Inne miasto, którego mury miały inną częstotliwość rezonansową lub których obrony powietrzne na dużych wysokościach były raczej lepsze, wymagałoby niewielkich lub fundamentalnych zmian podejścia (inaczej trąbka lub rakieta o innej wysokości).

Prowadzi to również do utrzymania kodu wbrew zmianom z upływem czasu: obecnie jest raczej wiele miast, które nie byłyby objęte żadnym z tych podejść, dzięki nowoczesnym metodom budowy i wszechobecnemu radarowi.

Opracowanie i przetestowanie całkowicie ogólnych środków, które zniszczą dowolne miasto, zanim zgodzisz się zniszczyć tylko jedno miasto, jest desperacko nieefektywnym podejściem. Żaden etycznie wyszkolony inżynier oprogramowania nie próbowałby uogólnić problemu w stopniu wymagającym zamówień o wielkości większej niż praca, za którą ich pracodawca / klient musiałby zapłacić, bez wykazanego wymogu.

Co jest prawdą? Czasami dodanie ogólności jest banalne. Czy powinniśmy zatem zawsze dodawać ogólności, gdy jest to trywialne? Nadal będę argumentować „nie, nie zawsze”, ze względu na problem z utrzymaniem długoterminowym. Przypuśćmy, że w momencie pisania tego tekstu wszystkie miasta są w zasadzie takie same, więc wybieram DestroyCity. Kiedyś to napisałem, wraz z testami integracji, które (ze względu na skończoną liczbę pól danych wejściowych) iterują po każdym znanym mieście i upewniają się, że funkcja działa na każdym (nie wiem, jak to działa. Prawdopodobnie wywołuje City.clone () i niszczy klon? W każdym razie).

W praktyce funkcja ta służy wyłącznie do zniszczenia Bagdadu, załóżmy, że ktoś buduje nowe miasto odporne na moje techniki (jest głęboko pod ziemią lub coś w tym rodzaju). Teraz mam błąd testu integracji dla przypadku użycia, który tak naprawdę nie istnieje , i zanim będę mógł kontynuować kampanię terroru przeciwko niewinnym cywilom Iraku, muszę wymyślić, jak zniszczyć Subterrania. Nieważne, czy jest to etyczne, czy nie, jest głupie i marnuje mój cholerny czas .

Czy naprawdę potrzebujesz funkcji, która może generować oszczędności światła dziennego na dowolny rok, tylko do danych na 2018 r.? Być może, ale z pewnością będzie to wymagało niewielkiego dodatkowego wysiłku po prostu zebranie przypadków testowych. Uzyskanie lepszej bazy danych stref czasowych może wymagać dużego wysiłku niż ta, którą faktycznie masz. Na przykład w 1908 r. W mieście Port Arthur w Ontario okres DST rozpoczął się 1 lipca. Czy to jest w bazie danych strefy czasowej twojego systemu operacyjnego? Nie myślałem, więc twoja uogólniona funkcja jest błędna . Nie ma nic szczególnie etycznego w pisaniu kodu, który składa obietnice, których nie może dotrzymać.

W porządku, więc z odpowiednimi zastrzeżeniami łatwo jest napisać funkcję, która wykonuje strefy czasowe przez szereg lat, powiedzmy od 1970 roku. Ale równie łatwo jest wziąć faktycznie napisaną funkcję i uogólnić ją, aby sparametryzować rok. Uogólnienie nie jest więc tak naprawdę bardziej etyczne / rozsądne, aby zrobić to, co zrobiłeś, a następnie uogólnić, jeśli i kiedy tego potrzebujesz.

Jeśli jednak wiesz, dlaczego twoja żona chciała sprawdzić tę listę DST, miałbyś świadomą opinię, czy ona może ponownie zadać to samo pytanie w 2019 r., A jeśli tak, to czy możesz wyjść z tej pętli, dając jej funkcję, którą może wywołać, bez konieczności jej ponownej kompilacji. Po wykonaniu tej analizy odpowiedź na pytanie „czy uogólnić to na ostatnie lata” może brzmieć „tak”. Ale stwarzasz sobie kolejny problem, że dane w strefie czasowej w przyszłości są tylko tymczasowe i dlatego jeśli uruchomi je dzisiaj na 2019 r., może, ale nie musi, zdawać sobie sprawę z tego, że podsyca ją. Więc nadal musisz napisać wiele dokumentacji, która nie byłaby potrzebna do mniej ogólnej funkcji („dane pochodzą z bazy danych stref czasowych bla bla bla bla link, aby zobaczyć ich zasady dotyczące przekazywania aktualizacji bla bla bla”). Jeśli odrzucisz ten szczególny przypadek, to przez cały czas to robisz, ona nie może zająć się zadaniem, dla którego potrzebowała danych z 2018 r., Z powodu bzdur o 2019 r., O których nawet nie dba.

Nie rób trudnych rzeczy bez odpowiedniego ich przemyślenia, tylko dlatego, że żart kazał ci to zrobić. To jest użyteczne? Czy jest wystarczająco tani jak na ten stopień przydatności?


0

Jest to łatwa do narysowania linia, ponieważ wielokrotność użytkowania zdefiniowana przez architektów-astronautów to bzdury.

Prawie cały kod stworzony przez twórców aplikacji jest wyjątkowo specyficzny dla domeny. To nie jest rok 1980. Prawie wszystko, co warto zawracać sobie głowę, jest już w ramach.

Abstrakcje i konwencje wymagają dokumentacji i wysiłku uczenia się. Uprzejmie przestańcie tworzyć nowe tylko ze względu na to. (Patrzę na ciebie , ludzie JavaScript!)

Pozwólmy sobie na nieprawdopodobną fantazję, że znalazłeś coś, co naprawdę powinno znaleźć się w twoim wyborze. Nie możesz tak po prostu podnieść kodu w zwykły sposób. O nie, potrzebujesz pokrycia testowego nie tylko do zamierzonego zastosowania, ale także do odstępstw od zamierzonego zastosowania, dla wszystkich znanych przypadków skrajnych, dla wszystkich możliwych trybów awarii, przypadków testowych do diagnostyki, danych testowych, dokumentacji technicznej, dokumentacji użytkownika, zarządzania wydaniami, wsparcia skrypty, testy regresji, zarządzanie zmianami ...

Czy Twój pracodawca chętnie za to wszystko płaci? Powiem nie.

Abstrakcja to cena, którą płacimy za elastyczność. To sprawia, że ​​nasz kod jest bardziej złożony i trudniejszy do zrozumienia. O ile elastyczność nie zaspokoi rzeczywistej i obecnej potrzeby, po prostu nie, ponieważ YAGNI.

Spójrzmy na przykład z prawdziwego świata, z którym właśnie miałem do czynienia: HTMLRenderer. To nie skalowało się poprawnie, gdy próbowałem renderować w kontekście urządzenia drukarki. Cały dzień zajęło mi odkrycie, że domyślnie używa GDI (który nie skaluje się), a nie GDI + (który robi, ale nie antialias), ponieważ musiałem przedzierać się przez sześć poziomów pośrednich w dwóch złożeniach, zanim znalazłem kod, który zrobił cokolwiek .

W takim przypadku wybaczę autorowi. Abstrakcja jest faktycznie konieczna, ponieważ jest to kod frameworka, który jest ukierunkowany na pięć bardzo różnych celów renderowania: WinForms, WPF, dotnet Core, Mono i PdfSharp.

Ale to tylko podkreśla mój punkt widzenia: prawie na pewno nie robisz czegoś niezwykle złożonego (renderowanie HTML z arkuszami stylów) na wielu platformach, z określonym celem wysokiej wydajności na wszystkich platformach.

Twój kod jest prawie na pewno kolejną siatką baz danych z regułami biznesowymi, które dotyczą tylko twojego pracodawcy, oraz przepisami podatkowymi, które obowiązują tylko w twoim stanie, w aplikacji, która nie jest na sprzedaż.

Cała ta pośrednia rozwiązuje problem, którego nie masz, i sprawia, że ​​twój kod jest znacznie trudniejszy do odczytania, co ogromnie zwiększa koszty utrzymania i jest ogromną szkodą dla twojego pracodawcy. Na szczęście ludzie, którzy powinni na to narzekać, nie są w stanie zrozumieć, co im robisz.

Kontrargumentem jest to, że ten rodzaj abstrakcji wspiera rozwój oparty na testach, ale myślę, że TDD to także bzdury, ponieważ zakłada, że ​​firma ma jasne, kompletne i prawidłowe zrozumienie swoich wymagań. TDD jest świetny dla NASA i oprogramowania sterującego sprzętem medycznym i samojezdnymi samochodami, ale o wiele za drogi dla wszystkich innych.


Nawiasem mówiąc, nie można przewidzieć wszystkich oszczędności światła dziennego na świecie. W szczególności Izrael ma około 40 przejść każdego roku, które przeskakują wszędzie, ponieważ nie możemy mieć ludzi modlących się w niewłaściwym czasie, a Bóg nie oszczędza na świetle dziennym.


Chociaż zgadzam się z tym, co powiedziałeś na temat abstrakcji i wysiłku uczenia się, a szczególnie, gdy jest on skierowany na ohydę zwaną „JavaScript”, zdecydowanie nie mogę zgodzić się z pierwszym zdaniem. Ponowne użycie może się zdarzyć na wielu poziomach, może spaść zbyt daleko , a programista może napisać kod wyrzucający. Ale ludzie, którzy są wystarczająco idealistyczni, aby przynajmniej dążyć do kodu wielokrotnego użytku. Szkoda, jeśli nie widzisz korzyści, jakie to może przynieść.
Marco13

@ Marco13 - Ponieważ twój sprzeciw jest uzasadniony, rozwinę tę kwestię.
Peter Wone

-3

Jeśli używasz co najmniej java 8, napiszesz klasę WorldTimeZones, aby dostarczyć coś, co w istocie wydaje się być zbiorem stref czasowych.

Następnie dodaj metodę filter (Predicate filter) do klasy WorldTimeZones. Daje to dzwoniącemu możliwość filtrowania wszystkiego, co chce, poprzez przekazanie wyrażenia lambda jako parametru.

Zasadniczo metoda pojedynczego filtra obsługuje filtrowanie wszystkiego zawartego w wartości przekazywanej do predykatu.

Alternatywnie dodaj metodę stream () do swojej klasy WorldTimeZones, która po wywołaniu tworzy strumień stref czasowych. Następnie dzwoniący może filtrować, mapować i zmniejszać według potrzeb bez pisania żadnych specjalizacji.


3
Są to dobre pomysły uogólniające, jednak ta odpowiedź całkowicie nie zgadza się z pytaniem. Pytanie nie dotyczy tego, jak najlepiej uogólnić rozwiązanie, ale gdzie wytyczamy granice uogólnień i jak ważymy te rozważania etycznie.
wałek klonowy

Mówię więc, że powinieneś je zważyć według liczby obsługiwanych przypadków użycia zmodyfikowanych przez złożoność stworzenia. Metoda obsługująca jeden przypadek użycia nie jest tak cenna jak metoda obsługująca 20 przypadków użycia. Z drugiej strony, jeśli wszystko, co wiesz, to jeden przypadek użycia, a jego kodowanie zajmuje 5 minut - idź. Często metody kodowania obsługujące określone zastosowania informują o sposobie generalizacji.
Rodney P. Barbati
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.