Termine (o "modello"?) Per "Fai qualcosa se non è già stato fatto" [chiuso]


54

Sembra piuttosto semplice, lo so, ma recentemente ho avuto un collega che mi ha detto che un metodo chiamato startHttpServerè troppo complicato per capire perché avvia il server solo se non è già in esecuzione. Trovo di trovarmi nei guai quando rispondo, "Seriamente? Lo sto facendo da decenni - è un modello comune nella programmazione." Più spesso di quanto mi interessi ammettere che ritorna con alcune prove documentate che dimostrano che l'intera comunità di programmatori è dietro il suo punto di vista e finisco per sentirmi imbarazzato.

Domanda : esiste un modello progettuale documentato dietro il concetto di un metodo che non è operativo se l'azione richiesta è già in vigore? Oppure, se non uno schema, ha anche un nome? E se no, c'è qualche motivo per pensare che sia troppo complicato considerare di scrivere un metodo in questo modo?


6
suona come la cache - ed è spesso considerato complicato (vedi anche Come posso spiegare $ {qualcosa} a $ {qualcuno}? )
moscerino

8
Hai 3 risposte diverse, ma la risposta migliore sarebbe applicarle tutte: rinominare la funzione originale, dividere il suo codice in due funzioni (dove uno dei due ora ottiene il nome startHttpServer), e sì, qui si applica il termine "idempotente" bene.
Doc Brown,

8
Che tipo di contro-prova fornisce il tuo collega? Puoi fornire un link ad esso?
Robert Harvey,

5
Sono curioso di vedere da dove proviene il tuo collega le sue citazioni. Almeno attorno allo StackTranslate.it e all'ingegneria del software, questa funzione sarebbe al massimo mal nominata ma non avrebbe alcun comportamento insolito. Tieni presente che non è perché qualcuno da qualche parte ha messo qualcosa su un blog che rappresenta l'intera visione della comunità di programmazione. Anche grandi nomi come Martin Fowler dicono di tanto in tanto cose molto strane. Siamo tutti solo umani.
T. Sar - Ripristina Monica il

4
Penso che un modo migliore di pensare a "Fai qualcosa se non è già stato fatto" sarebbe "Metti il ​​sistema su questo stato". Naturalmente, se il sistema si trova già in quello stato, il metodo non farebbe nulla, il che sarebbe il comportamento previsto.
T. Sar - Ripristina Monica il

Risposte:


127

Come NickWilliams ha già detto : il concetto descritto dall'OP si chiama idempotente (sostantivo Idempotency ). È davvero una pratica comune, specialmente nelle API di alto livello.

MA: Rinomina la funzione.

Invece di startHttpServerchiamarlo makeSureHttpServerIsRunningo ensureHttpServerIsRunning.

Quando viene chiamata una funzione startHttpServer, i lettori si aspettano che avvii un server HTTP; quando viene chiamato dieci volte di seguito, avrò dieci server in esecuzione. La tua funzione non lo fa per la maggior parte del tempo. Inoltre, il nome con "start" suggerisce che se voglio un solo server in esecuzione dovrò tenere traccia del fatto che la funzione sia già stata chiamata o meno.

Quando viene chiamata una funzione makeSureHttpServerIsRunning, suppongo che farà le cose necessarie per assicurarsi che un server HTTP sia in esecuzione, molto probabilmente controllando se è già in esecuzione e avviandolo altrimenti. Suppongo anche che la funzione assicuri che il server sia effettivamente in esecuzione (l'avvio di un server potrebbe richiedere del tempo in cui non è ancora in esecuzione).


83
Questo. Personalmente uso invece "Assicurare". Il punto è che questo dovrebbe diventare uno schema di denominazione che è compreso nella tua squadra.
Euforico

3
o iniziare a generare un'eccezione se è già iniziata. come sqlconnection.open
Ewan

9
Uso sempre la funzione "assicurati" per la funzione "se non già".
Kaz,

3
Un altro nome che vedo spesso è getOrCreateSomething, che crea solo alla prima chiamata e poi restituisce semplicemente qualcosa
Fabich

5
@aroth Sei sicuro di non aspettarti che tenti di trovare una porta disponibile e restituirla? O semplicemente essere scritto male? ;)
jpmc26,

33

Rinominalo in EnsureServerRunning.

Completamente inequivocabile ed è chiaro che si assicura che sia in esecuzione (se non lo è) senza implicare un riavvio se lo è.

(Alternativa StartServerIfNotRunning:?)


1
Alla maggior parte dei chiamanti interessa solo che il server sia in esecuzione dopo la chiamata. Come ciò viene realizzato, a loro non importa.
gnasher729,

29

Non proprio un modello di progettazione, ma definirei il tuo metodo idempotente . Il termine viene generalmente utilizzato per fare riferimento a chiamate remote, ma la descrizione sembra corrispondere a ciò che si sta facendo.

Metodi idempotenti. I metodi possono anche avere la proprietà di "idempotenza" in quanto (a parte errori o problemi di scadenza) gli effetti collaterali di richieste identiche N> 0 sono le stesse di una singola richiesta. ( Da W3.org )

L'effetto collaterale del server è che il server http viene avviato una volta chiamato il metodo. Non vedo nulla di sbagliato in un metodo che fa questo.

Se hai bisogno di un modello di progettazione, suppongo che potresti esporre il tuo httpServer come un singleton che viene avviato quando inizializzato.


5
La funzione non è idempotente se chiamarla una volta avvia il server http e non chiamarla una seconda volta. Questo è letteralmente l'opposto dell'idempotenza. Sarebbe idempotente se ogni chiamata avviasse un nuovo server http.
Poligono

36
@Polygnome Wikipedia definisce l'idempotenza come f (f (x)) = f (x) che generalmente corrisponde alla descrizione di Nick. Qui l'input e l'output della funzione sarebbero lo stato implicito del server, quindi lo stato "server in esecuzione" sarebbe un punto fisso per questa funzione. Forse sto fraintendendo l'articolo di Wikipedia qui, potresti collegare ad altri riferimenti?
Amon,

16
@Polygnome: questa risposta è corretta, idempotenza si riferisce a funzioni per le quali non importa se vengono chiamate una o più volte, il risultato rimane sempre lo stesso. Qui, il risultato è un servizio http in esecuzione, indipendentemente dal numero di volte in cui viene chiamata la funzione.
Doc Brown,

14
La confusione deriva dal fatto che l'idempotenza al suo interno è un concetto matematico applicato alle funzioni. La definizione è piacevole e semplice per quelli e funziona bene anche con linguaggi funzionali puri. Non appena si introducono effetti collaterali, diventa complicato: è necessario considerare l'ambiente in cui si esegue la funzione come argomento (implicito) e output della funzione. Se quello stato non viene modificato da ulteriori chiamate alla funzione, è idempotente, altrimenti non lo è. In questo caso lo stato rilevante è "è in esecuzione il server http", quindi la funzione è idempotente.
Voo,

10
@Polygnome Siamo spiacenti, ti sbagli. Idempotente significa che la richiesta ha lo stesso effetto se viene eseguita una volta o più di una volta. L'avvio di N server HTTP se vengono ricevuti N duplicati della richiesta non è assolutamente idempotente.
Kaz,

7

Come colui che implementa questo strumento , startHttpServerdovresti provare a renderlo il più semplice, fluido e senza soluzione di continuità da usare ...

La logica della funzione

Tecnicamente, dividendo startHttpServerla logica in 2 funzioni e chiamandole separatamente , tutto ciò che fai è spostare startHttpServer l' idempotenza nel codice chiamando entrambe le funzioni ... Inoltre, a meno che non avvolgi entrambe la logica in una terza funzione (che è ciò che fa startHttpServerin primo luogo), questo ti costringe a scrivere codice non DRY, duplicandolo esponenzialmente ovunque dovresti chiamare startHttpServer. In breve, startHttpServer deve chiamare se stessa la isHttpServerRunningfunzione.

Quindi il mio punto è:

  • Implementare la isHttpServerRunningfunzione perché potrebbe essere necessario in modo indipendente comunque ...
  • Implementalo startHttpServerfacendolo usare isHttpServerRunningper definire la sua prossima azione di conseguenza ...

Tuttavia, puoi startHttpServerrestituire qualsiasi valore di cui l'utente abbia bisogno in questa funzione, ad esempio:

  • 0 => errore di avvio del server
  • 1 => avvio server riuscito
  • 2 => il server era già stato avviato

La denominazione della funzione

Prima di tutto, qual è l' obiettivo principale dell'utente? Per avviare il server HTTP , giusto?

Fondamentalmente, non c'è nessun problema con l'intenzione di iniziare qualcosa che è già stato avviato, AKA 1*1=1. Quindi, almeno per me, chiamarlo " ensureHttpServerIsRunning" non sembra assolutamente necessario, mi preoccuperei di più di quanto sia lungo, naturale e memorabile il nome della funzione.

Ora, se vuoi sapere come funziona in dettaglio la funzione sotto il cofano, c'è la documentazione o la fonte del codice per questo, intendo come per qualsiasi altra funzione da libreria / framework / API / ecc ...

Si impara la funzione di una volta, mentre si scrive che più volte ...

Quindi comunque, rimarrei con quello startHttpServerche è più breve, più semplice ed esplicito di ensureHttpServerIsRunning.


1
Soprattutto dal momento che il metodo deve accettare alcuni argomenti di configurazione, come la directory principale, le impostazioni di sicurezza, possibilmente un numero di porta, sono al 100% a mio agio con "start" qui.
user949300,

@ user949300: Il chiamante di "sureHttpServerIsRunning" non si preoccupa della configurazione, della directory principale, ecc. Questo è il business della persona che lo implementa.
gnasher729,

2
@ gnasher729 Ciò presuppone che il server http sia un Singleton. I single sono cattivi. E forse inappropriato qui. Si potrebbero facilmente avere più server su più porte. Se esiste davvero un solo server http, l'intero metodo è, IMO, cattiva progettazione Meglio avviare il server una volta all'inizializzazione del programma.
user949300,

2

Suppongo che il tuo collega intendesse dire che startHttpServersta facendo troppo:

  • Verifica se il server è già in esecuzione,
  • Avvio del server, se necessario.

Quelle sono due parti non correlate del codice. Ad esempio, si verifica una situazione simile quando un'applicazione desktop dovrebbe assicurarsi che non sia già in esecuzione all'avvio; ci sarà una parte del codice che gestisce le istanze delle app (ad esempio usando un mutex) e il codice che avvierà il ciclo dei messaggi dell'applicazione.

Ciò significa che non devi avere uno, ma almeno¹ due metodi :

  • isHttpServerRunning: boolean
  • startHttpServer

Il punto di ingresso dell'applicazione chiamerà il primo metodo, quindi il secondo se il valore restituito è false. Ora, ogni metodo sta facendo una cosa sola ed è facile da capire.


¹ Se la logica necessaria per sapere se il server è già in esecuzione è troppo complessa, potrebbe essere necessaria un'ulteriore separazione in più metodi.


21
Ora qualcos'altro che chiama le due funzioni sta facendo due cose, inoltre, esporre le funzioni separatamente può introdurre condizioni di gara. Nessun pranzo libero.
whatsisname

1
Se è troppo per il collega, buona fortuna.
gnasher729,

@ gnasher729: questa risposta non contraddice la tua - al contrario, potrebbe davvero essere una buona idea combinare la ridenominazione del metodo con la sua suddivisione in due. E finché non vediamo il codice reale, non sappiamo quanto sia complesso il codice.
Doc Brown,

10
Questa risposta sembra opporsi all'astrazione e al SECCO. Cosa succede se startHttpServerviene chiamato più di un posto nel codice? È necessario incollare ovunque più righe simili? Questo dovrebbe essere fatto con tutte le funzioni? Molto presto il programma avrà dimensioni infinite.
Jacques B

3
Penso che il primo effetto collaterale di questo approccio sia che l'inizio del startHttpServermetodo sarà più o meno simile if (isHttpServerRunning()){ return; }. Stai affermando una regola aziendale secondo cui "non è valido per avviare il server http se è già in esecuzione", ma poi stai assumendo la responsabilità di qualcun altro di applicare tale regola. Ad-hoc e ripetutamente in ogni luogo in cui potrebbero chiamare startHttpServer.
aroth,

2

Dal momento che non si specifica una lingua, in JavaScript molte librerie hanno una funzione "una volta", ad esempio Underscore . Quindi, se ti sarebbe familiare, chiamalo modello "una volta" e probabilmente rinomina il tuo metodo.

Io stesso, venendo più da Java, mi vengono in mente i termini "cache" o "valutazione pigra". "Idempotent" è tecnicamente corretto e una buona scelta, esp. se hai uno sfondo più funzionale.


Potrebbe non essere "una volta", se il server può essere arrestato.
gnasher729,

@ gnasher729. Potrei immaginare un restartHttpServer()metodo usato raramente . Ma basta fermarsi : qual è il caso d'uso lì? Ti piacciono gli sporadici errori di connessione? :-)
user949300

Non è necessario un caso d'uso per l'arresto del server HTTP perché potrebbe essere stato arrestato da qualcosa di esterno al programma stesso: il server HTTP potrebbe essere stato segfault e morto, un amministratore potrebbe averlo arrestato manualmente per qualche motivo, ecc.
Dave Sherohman,

Dave, un incidente è "eccezionale" e dovrebbe essere gestito come tale. Inoltre, poiché un arresto anomalo può verificarsi in qualsiasi momento , non dovrebbe essere ogni riga del tuo codice ensureRunning()? :-) Per quanto riguarda l'amministratore che lo interrompe, avere questo altro codice che si riavvia costantemente mentre l'amministratore sta cercando di modificare o correggere qualcosa sarebbe incredibilmente fastidioso e sbagliato. Consenti all'amministratore di riavviarlo, non il codice.
user949300,

-2

Preferirei startHttpServerIfNotIsRunning.

In questo modo, la condizione è chiaramente menzionata già nel nome del metodo. Ensureo mi makeSuresembra un po 'vago, in quanto non è un'espressione tecnica. Sembra che non sappiamo esattamente cosa succederà.


Suppongo che tu non sia un madrelingua? Perché garantire ha la definizione esatta di ciò che stiamo cercando. Ma è comunque giusto che la documentazione e le API non debbano richiedere livelli comprensibili di madrelingua inglese.
Voo,

1
Grazie, non esattamente cosa Ensuresignifichi. Non mi piace ancora questa parola per un'espressione tecnica. Ensureè qualcosa di umano. Un sistema non può garantire nulla, farà solo ciò che dovrebbe fare.
Herr Derb,

2
@Voo - Sono un madrelingua e non sono sicuro di cosa significhi.
Jonathan Cast,

Dal momento che tutti gli articoli pubblicati qui hanno accesso a Internet, perché non dovrebbero cercare la definizione di "Garantire" se non fossero sicuri di cosa significasse? Un po 'di curiosità ... molte persone scambiano l'uso di "Assicurare" e "Assicurare" senza rendersi conto che l'uso di una di queste parole potrebbe inavvertitamente renderle "legalmente" responsabili, mentre l'altra no.
Dunk,

@jcast: Davvero?
gnasher729,

-3

Ciò che il tuo collega avrebbe dovuto dirti è che non hai affari a scrivere quel metodo. È già stato scritto molte volte e scritto meglio di quanto tu possa scriverlo. Ad esempio: http://docs.ansible.com/ansible/latest/systemd_module.html https://docs.saltstack.com/en/latest/ref/states/all/salt.states.service.html

Dal punto di vista architettonico, avere un po 'di codice arbitrario nella gestione di un web server è roba da incubi. A meno che la gestione dei servizi non sia esclusivamente ciò che fa il tuo codice. Ma suppongo che tu non abbia scritto monit (o kubernetes o ...).


2
Il linguaggio è Java e il metodo è stato progettato per avviare un server incorporato grizzly in un'applicazione Jersey autonoma. Non posso criticare l'accuratezza del tuo commento, dato che non ti ho dato molto contesto, ma hai assunto molto nel fare la tua dichiarazione. Va benissimo avere un metodo che chiama in molo o grizzly per avviare il server.
John Calcote,

1
Esistono milioni di applicazioni aziendali che includono server HTTP self-hosted per fornire un'API. E tutti questi avranno bisogno di un codice molto semplice che in realtà imposta la libreria che stanno usando e fornisce informazioni come quale interfaccia a cui legarsi, quale porta da usare, impostazioni di sicurezza, ecc. Avere una startServerfunzione o simile è tutt'altro che raro . Ciò non significa che scriverai i dettagli di basso livello.
Voo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.