Termin (czy „wzór”?) Dla „Zrób coś, jeśli jeszcze tego nie zrobiono” [zamknięte]

54

Brzmi dość prosto, wiem, ale ostatnio mój kolega powiedział mi, że wywoływana metoda startHttpServerjest zbyt skomplikowana, aby ją zrozumieć, ponieważ uruchamia serwer tylko wtedy, gdy jeszcze nie działa. Czuję, że wpadam w kłopoty, kiedy odpowiadam: „Poważnie? Robiłem to od dziesięcioleci - to powszechny schemat programowania”. Częściej niż chciałbym przyznać, że powraca z udokumentowanymi dowodami, które pokazują, że cała społeczność programistów stoi za jego punktem widzenia i ostatecznie czuję się zakłopotana.

Pytanie : Czy istnieje udokumentowany wzorzec projektowy za koncepcją metody, która nie działa, jeśli wymagane działanie jest już skuteczne? A może, jeśli nie jest wzorem, ma też nazwę? A jeśli nie, to czy jest jakiś powód, by sądzić, że zbyt skomplikowane jest rozważenie napisania metody w ten sposób?

John Calcote
źródło
6
brzmi jak buforowanie - i jest to często uważane za skomplikowane (zobacz także Jak wyjaśnić $ {coś} do $ {ktoś}? )
komnata
8
Masz 3 różne odpowiedzi, ale najlepszą odpowiedzią byłoby, zastosuj je wszystkie: zmień nazwę oryginalnej funkcji, podziel jej kod na dwie funkcje (gdzie jedna z nich otrzymuje teraz nazwę startHttpServer), i tak, tutaj stosuje się określenie „idempotent” dobrze.
Doc Brown
8
Jakiego rodzaju kontr-dowód dostarcza twój kolega? Czy możesz podać link do niego?
Robert Harvey
5
Jestem ciekawy, skąd twój kolega pozyskuje swoje cytaty. Przynajmniej w przypadku przepełnienia stosu i inżynierii oprogramowania funkcja ta byłaby co najwyżej źle nazwana, ale nie miałaby żadnego niezwykłego zachowania. Należy pamiętać, że nie dlatego, że ktoś umieścił coś na blogu, co reprezentuje cały pogląd społeczności programistów. Nawet wielkie nazwiska, jak Martin Fowler, od czasu do czasu mówią kilka bardzo dziwnych rzeczy. Wszyscy jesteśmy tylko ludźmi.
T. Sar - Przywróć Monikę
4
Myślę, że lepszym sposobem myślenia o „Zrób coś, jeśli jeszcze tego nie zrobiono” byłoby „Ustaw system w tym stanie”. Oczywiście, jeśli system jest już w tym stanie, metoda nie zrobiłaby nic - byłoby to oczekiwane zachowanie.
T. Sar - Przywróć Monikę

Odpowiedzi:

127

Jak już powiedział NickWilliams : koncepcja OP opisana jest jako idempotent (rzeczownik Idempotency ). Jest to rzeczywiście powszechna praktyka, szczególnie w interfejsach API wysokiego poziomu.

ALE: Zmień nazwę funkcji.

Zamiast startHttpServernazywać to makeSureHttpServerIsRunninglub ensureHttpServerIsRunning.

Po wywołaniu funkcji startHttpServerczytelnicy oczekują, że uruchomi serwer HTTP; kiedy zostanie wywołany dziesięć razy z rzędu, będę miał dziesięć serwerów działających. Twoja funkcja nie robi tego przez większość czasu. Dodatkowo nazwa z „start” sugeruje, że jeśli chcę, aby działał tylko jeden serwer, muszę śledzić, czy funkcja została już wywołana, czy nie.

Kiedy funkcja jest wywoływana makeSureHttpServerIsRunning, zakładam, że wykona niezbędne czynności, aby upewnić się, że serwer HTTP działa, najprawdopodobniej sprawdzając, czy już działa, i uruchamiając go w inny sposób. Zakładam również, że funkcja zapewnia, że ​​serwer faktycznie działa (uruchomienie serwera może zająć trochę czasu, gdy jeszcze nie działa).

gnasher729
źródło
83
To. Osobiście używam zamiast tego słowa „Zapewnij”. Chodzi o to, że powinien on stać się schematem nazewnictwa zrozumiałym dla Twojego zespołu.
Euforyczny
3
lub zacznij zgłaszać wyjątek, jeśli został już uruchomiony. jak sqlconnection.open
Ewan
9
Zawsze używam funkcji „upewnij się”, że nazwa „zrób, jeśli jeszcze nie”.
Kaz
3
Inną nazwą, którą często widzę, jest getOrCreateSomething, która tworzy się tylko przy pierwszym połączeniu, a następnie po prostu zwraca coś
Fabich
5
@aroth Czy na pewno nie spodziewałbyś się, że spróbuje znaleźć dostępny port i go zwróci? A może źle napisane? ;)
jpmc26
33

Zmień nazwę na EnsureServerRunning.

Całkowicie jednoznaczne i jasne, że zapewnia, że ​​działa (jeśli nie jest), bez sugerowania ponownego uruchomienia, jeśli tak jest.

(Alternatywnie StartServerIfNotRunning:?)

Stilez
źródło
1
Większość dzwoniących po prostu dba o to, aby serwer działał po połączeniu. Jak to się dzieje, nie obchodzi ich to.
gnasher729
29

Nie jest to wzorzec projektowy, ale nazwałbym tę metodę idempotentną . Termin ten jest zwykle używany w odniesieniu do połączeń zdalnych, ale opis wydaje się pasować do tego, co robisz.

Idempotentne metody. Metody mogą również mieć właściwość „idempotence”, ponieważ (oprócz błędów związanych z błędem lub wygasaniem) skutki uboczne N> 0 identycznych żądań są takie same jak w przypadku pojedynczego żądania. ( Z W3.org )

Efektem ubocznym serwera jest tutaj to, że serwer HTTP jest uruchamiany po wywołaniu metody. Nie widzę nic złego w tej metodzie.

Jeśli potrzebujesz wzorca projektowego, myślę, że możesz udostępnić swój serwer HTTP jako singleton, który jest uruchamiany po zainicjowaniu.

Nick Williams
źródło
5
Ta funkcja nie jest idempotentna, jeśli wywołanie jej raz uruchomi serwer HTTP, a wywołanie go po raz drugi nie. To dosłownie przeciwieństwo idempotencji. Byłoby idempotentne, gdyby każde połączenie uruchamiało nowy serwer http.
Polygnome
36
@Polygnome Wikipedia definiuje idempotencję jako f (f (x)) = f (x), które ogólnie odpowiada opisowi Nicka. Tutaj wejście i wyjście funkcji byłoby domyślnym stanem serwera, więc stan „serwer działa” byłby stałym punktem dla tej funkcji. Może nie rozumiem tutaj artykułu Wikipedii, czy mógłbyś zamieścić linki do innych odnośników?
amon
16
@Polygnome: ta odpowiedź jest poprawna, idempotencja odnosi się do funkcji, dla których nie ma znaczenia, czy zostaną one wywołane raz czy więcej, wynik pozostaje zawsze taki sam. Tutaj wynikiem jest jedna działająca usługa http, niezależnie od liczby wywołań funkcji.
Doc Brown
14
Zamieszanie wynika z faktu, że idempotencja u podstaw jest matematyczną koncepcją stosowaną do funkcji. Definicja jest dla nich ładna i prosta, a także działa dobrze dla czysto funkcjonalnych języków. Gdy tylko wprowadzisz efekty uboczne, komplikuje się - musisz rozważyć środowisko, w którym wykonujesz funkcję, jako (niejawny) argument i wynik funkcji. Jeśli ten stan nie zostanie zmodyfikowany przez kolejne wywołania funkcji, jest idempotentny, w przeciwnym razie tak nie jest. W tym przypadku odpowiednim stanem jest „działa serwer HTTP” - więc funkcja jest idempotentna.
Voo
10
@Polygnome Przepraszamy, mylisz się. Idempotent oznacza, że ​​żądanie ma taki sam efekt, niezależnie od tego, czy zostanie wykonane raz, czy więcej niż raz. Uruchomienie N serwerów HTTP, jeśli otrzymano N duplikatów żądania, zdecydowanie nie jest idempotentne.
Kaz
7

Jako ten, który wdraża to narzędzie , startHttpServerpowinieneś starać się, aby było to najprostsze, płynne i bezproblemowe w użyciu ...

Logika funkcji

Technicznie rzecz biorąc, przez rozszczepienie startHttpServerjest logika w 2 funkcje i nazywając je oddzielnie , wszystko co robisz jest w ruchu startHttpServer 's idempotentność do kodu wzywając obie funkcje zamiast ... Poza tym, o ile nie zawijać zarówno logikę w trzeciej funkcji (co jest, co robi startHttpServerpo pierwsze), to zmusza cię do napisania nieSUSZONEGO kodu, powielając go wykładniczo wszędzie, gdzie będziesz musiał zadzwonić startHttpServer. Krótko mówiąc, startHttpServer musi nazywać się isHttpServerRunningfunkcją.

Więc mam na myśli:

  • Zaimplementuj isHttpServerRunningfunkcję, ponieważ i tak może być potrzebna niezależnie ...
  • Zaimplementuj startHttpServer, używając go isHttpServerRunningdo odpowiedniego zdefiniowania następnego działania ...

Mimo to możesz startHttpServerzwrócić dowolną wartość, której użytkownik tej funkcji może potrzebować, np .:

  • 0 => błąd uruchamiania serwera
  • 1 => serwer rozpoczął sukces
  • 2 => serwer został już uruchomiony

Nazewnictwo funkcji

Po pierwsze, jaki jest główny cel użytkownika? Aby uruchomić serwer HTTP , prawda?

Zasadniczo nie ma problemu, zamierzając rozpocząć coś, co już rozpoczęto, AKA 1*1=1. Tak więc, przynajmniej dla mnie, nazwanie go „ ensureHttpServerIsRunning” nie wydaje się konieczne, bardziej zależałoby mi na tym, jak długa, naturalna i niezapomniana jest nazwa funkcji.

Teraz, jeśli chcesz wiedzieć, jak szczegółowo działa funkcja pod maską, istnieje do tego dokumentacja lub źródło kodu, mam na myśli, jak w przypadku każdej innej funkcji z biblioteki / frameworka / API / etc ...

Nauczysz się tej funkcji raz, pisząc ją wiele razy ...

W każdym razie trzymałbym się tego, startHttpServerco jest krótsze, prostsze i bardziej wyraźne niż ensureHttpServerIsRunning.

ClemC
źródło
1
Zwłaszcza, że ​​metoda musi wziąć kilka argumentów konfiguracyjnych, takich jak katalog główny, ustawienia zabezpieczeń, być może numer portu, jestem w 100% zadowolony z „startu” tutaj.
user949300,
@ user949300: Osoba wywołująca „zapewnienieHttpServerIsRunning” nie dba o konfigurację, katalog główny itp. To jest sprawa osoby, która ją implementuje.
gnasher729
2
@ gnasher729 Zakłada, że ​​serwer HTTP to Singleton. Singletony są złe. I prawdopodobnie nieodpowiednie tutaj. Można łatwo mieć wiele serwerów na wielu portach. Jeśli naprawdę istnieje tylko jeden serwer HTTP, cała ta metoda to: IMO, zły projekt Lepiej po prostu uruchomić serwer raz podczas inicjalizacji programu.
user949300,
2

Przypuszczam, że twój kolega miał na myśli, że startHttpServerrobi zbyt wiele:

  • Sprawdzanie, czy serwer już działa,
  • Uruchamianie serwera w razie potrzeby.

To dwie niepowiązane części kodu. Na przykład podobna sytuacja występuje, gdy aplikacja komputerowa powinna upewnić się, że nie jest uruchomiona po uruchomieniu; będzie część kodu, która obsługuje instancje aplikacji (na przykład za pomocą muteksu) oraz kod, który uruchomi pętlę komunikatów aplikacji.

Oznacza to, że musisz mieć nie jedną, ale co najmniej dwie metody :

  • isHttpServerRunning: boolean
  • startHttpServer

Punkt wejścia aplikacji wywoła pierwszą metodę, a następnie drugą, jeśli zwracana jest wartość false. Teraz każda metoda robi jedną i jedną rzecz i jest łatwa do zrozumienia.


¹ Jeśli logika potrzebna do sprawdzenia, czy serwer już działa, jest zbyt złożona, może wymagać dalszego rozdzielenia na wiele metod.

Arseni Mourzenko
źródło
21
Teraz coś innego, nazywając te dwie funkcje, robi dwie rzeczy, a dodatkowo ujawnienie funkcji oddzielnie może wprowadzić warunki wyścigu. Bez darmowego lunchu.
whatsisname
1
Jeśli to za dużo dla kolegi, powodzenia.
gnasher729
@ gnasher729: ta odpowiedź nie jest sprzeczna z twoją - wręcz przeciwnie, może być dobrym pomysłem połączenie zmiany nazwy metody z podzieleniem jej na dwie części. I dopóki nie widzimy prawdziwego kodu, nie wiemy, jak skomplikowany jest kod.
Doc Brown
10
Ta odpowiedź wydaje się przeciwstawiać abstrakcji i SUCHU. Co jeśli startHttpServernazywa się więcej niż jedno miejsce w kodzie? Czy wiele podobnych wierszy należy wszędzie wkleić? Czy należy to zrobić ze wszystkimi funkcjami? Już wkrótce twój program będzie miał nieskończony rozmiar.
JacquesB
3
Myślę, że pierwszym efektem ubocznym tego podejścia jest to, że początek startHttpServermetody będzie mniej więcej taki if (isHttpServerRunning()){ return; }. Zapewniasz regułę biznesową, że „uruchomienie serwera HTTP, jeśli już działa”, nie jest prawidłowe, ale nakładasz na kogoś odpowiedzialność za egzekwowanie tej reguły. Ad-hoc i wielokrotnie w każdym miejscu, do którego mogą zadzwonić startHttpServer.
aroth
2

Ponieważ nie określasz języka, w JavaScript wiele bibliotek ma funkcję „raz”, np . Underscore . Więc jeśli byłoby ci to znane, nazwij to „raz” i prawdopodobnie zmień nazwę swojej metody.

Ja, pochodząc bardziej z Javy, przychodzą mi na myśl określenia „buforowanie” lub „leniwa ocena”. „Idempotent” jest technicznie poprawny i jest dobrym wyborem, szczególnie. jeśli masz bardziej funkcjonalne tło.

użytkownik949300
źródło
Może to nie być „jeden raz”, jeśli serwer można zatrzymać.
gnasher729
@ gnasher729. Mogłem sobie wyobrazić rzadko stosowaną restartHttpServer()metodę. Ale po prostu się zatrzymuję - jaki jest tam przypadek użycia? Lubisz sporadyczne awarie połączeń? :-)
user949300
Nie musi istnieć przypadek użycia tylko do zatrzymania serwera HTTP, ponieważ mógł on zostać zatrzymany przez coś zewnętrznego w stosunku do samego programu - serwer HTTP mógł ulec awarii i zmarł, administrator mógł z jakiegoś powodu ręcznie go zatrzymać, itp.
Dave Sherohman
Dave, awaria jest „wyjątkowa” i powinna być traktowana jako taka. Poza tym, ponieważ awaria może zdarzyć się w dowolnym momencie , czy nie musiałaby to być każda linia kodu ensureRunning()? :-) Jeśli chodzi o zatrzymanie go przez administratora, to ciągłe ponowne uruchamianie tego drugiego kodu, gdy administrator próbuje zmodyfikować lub naprawić coś, byłoby niezwykle irytujące i złe. Niech administrator go zrestartuje, a nie kod.
user949300,
-2

Wolę startHttpServerIfNotIsRunning.

W ten sposób warunek jest wyraźnie wymieniony już w nazwie metody. Ensurelub makeSurewydaje mi się nieco niejasny, ponieważ nie jest to wyrażenie techniczne. Wygląda na to, że nie wiemy dokładnie, co się wydarzy.

Herr Derb
źródło
Zakładam, że nie jesteś native speakerem? Ponieważ sure ma dokładną definicję tego, do czego zmierzamy. Ale nadal słuszne jest to, że dokumentacja i interfejsy API nie powinny wymagać znajomości języka ojczystego angielskiego.
Voo
1
Dzięki, dokładnie nie to, co Ensureznaczy. Nadal nie lubię tego słowa jako technicznego wyrażenia. Ensurejest czymś ludzkim. System nic nie może zapewnić, zrobi tylko to, co powinien.
Herr Derb
2
@Voo - Jestem native speakerem i nie jestem pewien, co to oznacza.
Jonathan Cast
Ponieważ wszyscy, którzy tu zamieszczają, mają dostęp do Internetu, dlaczego nie sprawdziliby definicji „Zapewnij”, gdyby nie byli pewni, co to znaczy? Trochę ciekawostek ... wiele osób zamienia użycie słów „Zapewnij” i „Zapewnij”, nie zdając sobie sprawy, że użycie jednego z tych słów mogłoby przypadkowo uczynić ich „prawnie” odpowiedzialnymi, podczas gdy drugie nie.
Dunk
@jcast: Poważnie?
gnasher729,
-3

Twój kolega powinien był ci powiedzieć, że nie masz biznesu piszącego tą metodą. Jest już napisany wiele razy i napisany lepiej, niż można to napisać. Na przykład: http://docs.ansible.com/ansible/latest/systemd_module.html https://docs.saltstack.com/en/latest/ref/states/all/salt.states.service.html

Z architektonicznego punktu widzenia posiadanie dowolnego kodu zarządzającego serwerem WWW to koszmar. Chyba że zarządzanie usługami zależy wyłącznie od kodu użytkownika. Ale zgaduję, że nie napisałeś monit (ani kubernetes lub ...).

Andrzej
źródło
2
Językiem jest Java, a metoda została zaprojektowana w celu uruchomienia wbudowanego serwera grizzly w autonomicznej aplikacji Jersey. Nie mogę nic zarzucić dokładności twojego komentarza, biorąc pod uwagę, że nie dałem ci wiele kontekstu, ale dużo wziąłeś na siebie, składając swoje oświadczenie. Zupełnie dobrze jest mieć metodę wywołującą jetty lub grizzly, aby uruchomić serwer.
John Calcote
1
Istnieją miliony aplikacji biznesowych, które zawierają hostowany serwer HTTP w celu zapewnienia interfejsu API. Wszystkie będą potrzebowały bardzo prostego kodu, który faktycznie konfiguruje używaną bibliotekę i dostarcza informacji, takich jak interfejs do połączenia, port do użycia, ustawienia zabezpieczeń itp. Posiadanie startServerfunkcji lub podobnego jest niczym niezwykłym . To nie znaczy, że napiszesz drobiazgowe szczegóły niskiego poziomu.
Voo