Sono in una situazione difficile. Sto lavorando in un repository che apporta molte modifiche al database allinterno delle sue funzioni.
La funzione con cui ho a che fare restituisce responseid (ma da una trasformazione, non da alcuna azione del database). Tuttavia, come effetto collaterale, aggiunge un oggetto contenente quegli ID di risposta al database.
Quindi dovrei chiamarlo:
-
getResponseIds
: evidenzia i valori restituiti. È un modo di pensare molto funzionale, ma ovviamente se ho la funzionepostToDB
non ha senso usaregetStatusOfPost
-
addResponseIdToDB
: questo evidenzia leffetto collaterale, anche se penso veramente che molte delle mie funzioni operano solo sul database (e tendono a non restituire nulla) -
getAndAddResponseIdsToDB
: molto informativo, ma molto lungo.
Quali sono i pro e i contro dei suggerimenti precedenti? Oppure puoi dare tu stesso un suggerimento migliore?
Commenti
Risposta
Un nome di funzione che contiene and
è al livello di astrazione errato .
Preferisco addResponseIdToDB()
perché altrimenti l “effetto collaterale” è una sorpresa completa. Tuttavia:
responseIds = addResponseIdToDB();
non lascia nessuno sorpreso.
Il principio di segregazione della responsabilità della query del comando sostiene che questo non dovrebbe essere lunico modo per ottenere loggetto responseId. Dovrebbe esserci anche una query che non modifica il DB che può ottenere questo oggetto.
Contrariamente a Bertrand Meyer , non credo che questo significhi che laggiunta deve restituire void. Significa solo che dovrebbe esistere una query pura equivalente ed essere facile da trovare in modo che il DB non venga abusato inutilmente dalluso di query di cambio di stato.
Dato che getResponseIds()
dovrebbe esistere e non parlare con il database, il nome migliore per un metodo che fa entrambe le cose è in realtà addToDB(getResponseId())
. Ma questo è solo se vuoi ottenere tutta la composizione funzionale al riguardo.
Commenti
- Aggiungendo a ciò che è stato detto, ma aggiungendo una parola che non è stata utilizzata ‘, direi che ” e ” è appropriato quando una funzione fa qualcosa che è atomico . Se cè un vantaggio da ottenere da due cose che accadono atomicamente / insieme / contemporaneamente, allora essere esplicito con ciò che queste due cose sono nella funzione nome è buono. Quindi sono daccordo con la tua prima affermazione in generale, ma non sono daccordo senza alcuna precisazione.
- ” … non lascia nessuno sorpreso. “. Non sono daccordo, ‘ sono rimasto sorpreso dal fatto che una variabile booleana si chiami
responseIds
e poi devo fare un cambiamento mentale per rendermi conto che aggiunge e ottiene tutto in uno. Ma non sono daccordo con @tintintong chegetAndAddResponseIdsToDB
è troppo lungo; ‘ è lungo circa la metà di molti dei miei nomi di funzioni. Se una funzione fa due cose, dillo nel suo nome. - @Luaan OK, cosa intendi rinominare in
getAndIncrement
?otherIncrement
? - @Luaan Quindi quale valore restituisce
Increment
, il valore pre-incremento o post-incremento? ‘ non è chiaro quale sarebbe senza approfondire i documenti.Penso cheincrementAndGet
egetAndIncrement
siano molto migliori nomi di metodi, il che credo sia un buon argomento per ” AND ” nei nomi dei metodi, almeno nel caso specifico di metodi che hanno effetti collaterali che potrebbero influire sul valore restituito .. - @Luaan Quando dici ” Incremento restituisce il valore incrementato ” intendi il valore pre-incremento? Gotcha, allinizio ti ho frainteso .. che è in realtà un ottimo esempio del perché
getAndIncrement
eincrementAndGet
sono nomi migliori diincrement
, anche se considerato individualmente
Risposta
Come altri hanno già detto, luso di and
nel nome di una funzione, implica automaticamente che una funzione stia facendo almeno due cose, il che di solito indica che sta facendo troppo o sta facendo qualcosa nel posto sbagliato.
Luso della clausola and
in un nome di funzione, tuttavia, nella mia esperienza, può avere senso quando ci sono alcuni passaggi in un certo flusso che devono essere eseguiti in sequenza o quando lintero calcolo diventa significativo.
Nel calcolo numerico questo può avere molto senso:
normalizeAndInvert(Matrix m)
Voglio dire, chi lo sa, giusto? Se hai bisogno di calcolare alcune … diciamo, traiettorie di illuminazione nella computer grafica e con una certa nella fase devi normalizzare e invertire una certa matrice, e ti ritrovi a scrivere costantemente:
m = normalize(m)
, seguito da invert(m)
, proprio come un semplice esempio, che introduce lastrazione tra normalizzazione e inversione, può essere utile dal punto di vista della leggibilità.
Parlando semanticamente, cose come divideAndConquer()
ecc, è probabilmente sconsigliato di essere scritto esplicitamente, ma, beh, il And
è fondamentalmente necessario.
Commenti
- Le funzioni fanno regolarmente più di una cosa, ma quelle dovrebbero essere piccole cose che si sommano a una cosa importante che ‘ è significativo a un livello più alto.
normalizeAndInvert
potrebbe esserecomputeLighting
edivideAndConquer
potrebbe essereapplyMachiavelli
- Ovviamente 🙂 Sto solo affermando che creando prima una funzione con ” e ” nel nome potrebbe venire come unastrazione naturale basata su un contesto particolare. Il refactoring tramite il ” metodo di ridenominazione ” dovrebbe venire subito dopo;)
- @BrunoOliveira Tranne che a volte i due passaggi sono essi stessi sono un componente di molte cose, rinominare non è ‘ t lapproccio giusto. E dovrebbe essere raro, ma non usarlo mai significa che ‘ stai ripetendo te stesso.
Rispondi
Direi che “va bene in generale, ma non è” accettabile in tutti i casi.
Ad esempio, si potrebbe discutere contro and
in un nome di funzione basato sul principio di responsabilità unica noto anche come S in SOLID, perché and
potrebbe implicare molteplici responsabilità. Se and
significa davvero che la funzione sta facendo due cose, allora probabilmente vorrai pensare attentamente a cosa sta succedendo.
Un esempio di una libreria che utilizza and
nei nomi delle funzioni può essere trovato in Java “s simultaneità e qui and
è in realtà una parte molto importante di ciò che sta succedendo e corrisponde strettamente a ciò che descrivi nel tuo post in cui lo stato viene cambiato e lo stato viene restituito. Quindi chiaramente ci sono alcune persone ( e alcuni casi duso) in cui è ritenuto accettabile.
Commenti
- ”
and
potrebbe implicare più responsabilità “. E in questo caso lo indica davvero. La funzione ottiene alcuni ID di risposta da qualche parte, li scrive in un database e li restituisce. La necessità di ” e ” dovrebbe in effetti almeno sollevare lidea con lOP che sono necessarie due funzioni: una per ottenere gli ID e uno per scriverli nel database. - Anche se sono daccordo che
and
è un odore di codice, il comportamento di base sembra legittimo: generalmente non è un cattivo idea di restituire alcuni valori aggiuntivi da un metodo che sono risultati (intermedi) necessari per svolgere la sua funzione principale. Se la funzione principale di questo metodo è quella di aggiungere gli ID di risposta al database, la restituzione degli ID aggiunti al chiamante è solo una caratteristica di usabilità chiara e semplice del metodo. - Non ‘ penso che questo necessariamente violi SRP. Avrai sempre bisogno di funzioni che facciano più cose. La parte importante è che queste funzioni chiamino unaltra funzione per ogni cosa che vogliono fare invece di contenere direttamente il codice richiesto.
- Se la funzione fa due cose, il nome dovrebbe rifletterla.
Risposta
In effetti, questa “è una domanda difficile a cui rispondere, come possono attestare numerosi commenti. Le persone sembrano avere opinioni e consigli contraddittori su quello che è un buon nome.
Vorrei aggiungere i miei 2 centesimi a questo thread vecchio di 2 mesi perché penso di poter aggiungere più colori a ciò che è stato già suggerito.
La denominazione è un processo
Tutto questo mi ricorda leccellente guida in 6 passaggi: Denominazione come processo .
Un nome di funzione scadente è sorprendente e ti rendi conto di non poterti fidare. È difficile dare un buon nome al primo scatto. Diventa più facile con lesperienza.
6 passaggi iterativi per creare un buon nome
- Sostituisci il nome sorprendente con ovvie sciocchezze come
appleSauce()
. Sembra sciocco, ma è temporaneo e rende ovvio che il nome non può “essere considerato attendibile. - Trova un nome onesto , in base a ciò che capisci da ciò che fa la funzione. Supponiamo che tu non abbia ancora capito linserimento nella parte DB,
getResponseIdsAndProbablyUseDb
sarebbe unopzione. - Ottieni completamente onesto , quindi il nome della funzione dice tutto ciò che fa la funzione (
getAndAddResponseIdsToDB
ogetResponsesButAlsoAddCacheOfTheResponsesToTheDb
da @Fattie sono ottimi esempi) - Get to “fa la cosa giusta” che è fondamentalmente la parte in cui dividi la funzione lungo “AND”. Quindi in realtà hai
getResponseIds
eaddResponseIdsToDb
. - Ottieni un nome “Intention Revealing” che risolve il punto di “Voglio sempre ottenere lID di risposta I inserito nel DB dopo averlo fatto “. Smetti di pensare ai dettagli dellimplementazione e costruisci unastrazione che utilizza le 2 funzioni più piccole per costruire qualcosaltro. Questo è il livello di astrazione più alto menzionato da @candied_orange. Per e xample
createResponseIds
potrebbe farlo e sarà la composizione digetResponseIds
eaddResponseIdsToDb
. - Accedi allastrazione del tuo dominio . Questo è difficile, dipende dalla tua attività. Ci vuole tempo per ascoltare il tuo linguaggio commerciale per avere ragione. Alla fine, potresti ritrovarti con il concetto di
Response
eResponse.createIds()
avrebbe senso. O forseResponseId
è qualcosa di prezioso e avresti una fabbrica per crearne molti. Linserimento nel DB sarebbe un dettaglio di implementazione di un Repository, ecc. Sì, il design sarebbe più astratto. Sarebbe più ricco e ti permetterebbe di esprimere di più. Nessuno al di fuori del tuo team può dirti quale dovrebbe essere lastrazione di dominio corretta nella tua situazione. Dipende .
Nel tuo caso, hai già capito 1 e 2, quindi dovresti probabilmente andare almeno a 3 (usa “AND”) o oltre. Ma andare oltre non è solo una questione di denominazione, in realtà dovresti dividere le responsabilità.
Pertanto, i diversi suggerimenti qui sono validi
Essenzialmente:
- Sì, i nomi di funzione sorprendenti sono terribili e nessuno vuole occuparsene
- Sì, “AND” è accettabile in un nome di funzione perché è meglio di un nome fuorviante
- Sì, il livello di astrazione è un concetto correlato ed è importante
Non devi passare subito alla fase 6, va bene restare a fase 2, 3 o 4 fino a quando non ne saprai di più!
Spero che possa essere utile per dare una prospettiva diversa su ciò che sembra un consiglio contraddittorio. Credo che tutti aspirino allo stesso obiettivo ( nomi non sorprendenti) ma si fermano a fasi diverse, in base alla propria esperienza.
Felice di rispondere in caso di domande 🙂
Risposta
La tua funzione fa due cose:
- Ottiene una serie di ID tramite una trasformazione e li restituisce al chiamante,
- Scrive quella serie di ID in un database.
Ricorda qui il principio di responsabilità unica: dovresti mirare a una funzione avere una responsabilità, non due. Hai due responsabilità, quindi hai due funzioni:
getResponseIds
– Ottiene una serie di ID tramite una trasformazione e li restituisce al chiamante
addResponseIdToDB
– Prende una serie di ID e li scrive su un database.
E lintera questione di come chiamare una singola funzione con più responsabilità scompare e anche la tentazione di inserire and
nei nomi delle funzioni scompare.
Come bonus aggiuntivo, poiché la stessa funzione non è più responsabile di due azioni non correlate, getResponseIds
potrebbe essere spostato fuori dal tuo codice repo (a cui non appartiene dato che non sta svolgendo alcuna attività relativa al DB), in una classe / modulo a livello aziendale separato, ecc.
Commenti
- Quello ‘ è sicuramente vero, ma ti ritrovi a ripetere uno snippet usando questi due metodi SRP molte volte, quindi probabilmente dovresti scrivere un metodo banale in modo da ASCIUTTO, non dovrebbe ‘ vero?
- @maaartinus, se ti ritrovi a chiamare questi due metodi in molti posti, allora probabilmente hai un problema con una mancanza di coesione nel tuo codice. La creazione di una funzione ” DRY ” maschera semplicemente questa mancanza di coesione.
Risposta
Avere un nome di funzione composto è accettabile, quindi lo schema di denominazione che usi dipende dal tuo contesto. Ci sono puristi che ti diranno di smetterla, ma io sosterrò diversamente.
Ci sono due ragioni per cercare di evitare queste cose:
- Purezza – Una funzione che fa due cose dovrebbe essere convertito in due funzioni che fanno una cosa ciascuna.
- Dimensione lessicale – un
bigUglyCompositeFunctionName
diventa difficile da leggere.
Largomento della purezza è tipicamente quello su cui ci si concentra. Come una vaga euristica generale, suddividere le funzioni è una buona cosa. Aiuta a compartimentare ciò che sta accadendo, rendendolo più facile da capire. È meno probabile che tu faccia trucchi “intelligenti” con la condivisione di variabili che portano allaccoppiamento tra i due comportamenti.
Quindi la cosa da chiedersi è “dovrei farlo comunque?” Dico che spezzare le cose è una vaga euristica, quindi hai solo bisogno di un argomento decente per andare contro di essa.
Uno dei motivi principali è che è vantaggioso pensare alle due operazioni come una sola. Lesempio definitivo di ciò si trova nelle operazioni atomiche: compare_and_set
. compare_and_set
è una pietra angolare fondamentale dellatomica (che è un modo per eseguire il multithreading senza blocchi) che ha abbastanza successo che molti sono disposti a considerarlo come il pietra angolare. Ci sono “una” e “in quel nome. Cè una buona ragione per questo. Il punto centrale di questa operazione è che fa il confronto e limpostazione come ununica operazione indivisibile. Se lo suddividessi in compare()
e set()
, avresti “sconfitto lintera ragione per cui la funzione esisteva in primo luogo, perché due compares()
potrebbe verificarsi uno dopo laltro prima dei set()
.
Lo vediamo anche nelle prestazioni. Il mio esempio preferito è fastInvSqrt . Questo è un famoso algoritmo di Quake che calcola 1 / sqrt (x). Combiniamo le operazioni “inversa” e “radice quadrata” perché così facendo noi un enorme aumento delle prestazioni. Fanno unapprossimazione di Newton che esegue entrambe le operazioni in un unico passaggio (e capita di farlo in matematica intera invece che in virgola mobile, cosa importante nellera). Se dovessi eseguire inverse(sqrt(x))
, sarebbe molto più lento!
Ci sono volte in cui il risultato è più chiaro. Ho scritto diverse API che coinvolgono il threading in cui si deve essere terribilmente attenti a cose come i deadlock. Non volevo che il mio utente fosse a conoscenza dei dettagli di implementazione interna (soprattutto perché potrei cambiarli), quindi ho scritto lAPI con alcune funzioni “e” che impedivano allutente di dover tenere premuto un lucchetto per più di una chiamata di funzione. Ciò significa che non hanno mai avuto bisogno di sapere come gestivo la sincronizzazione multithread sotto il cofano. In effetti, la maggior parte dei miei utenti “non era nemmeno a conoscenza di trovarsi in un ambiente multithread!
Quindi, sebbene la regola generale sia rompere le cose, ci può sempre essere un motivo per accoppiarli insieme. Ad esempio , il tuo utente può rompere la tua architettura aggiungendo più volte a un database senza il “get” intermedio? Se ciascuna delle risposte registrate nel DB deve avere un identificatore univoco, potresti finire nei guai se lutente dovesse farle volentieri nullo. Allo stesso modo, vorresti mai che un utente “ottenga” senza registrare i risultati nel DB? Se la risposta è “sì”, suddividili in modo che lutente possa accedere alla funzione “ottieni”. Tuttavia, se il la sicurezza della tua applicazione è danneggiata se lutente può “ottenere” senza che il risultato venga registrato nel DB, dovresti davvero tenere insieme le funzioni.
Ora, per quanto riguarda le questioni lessicali, il nome della tua funzione dovrebbe descrivere cosa lutente deve saperlo. Cominciamo con la parte “get”.È abbastanza facile rimuovere “get” dal nome di una funzione, perché tutti i tuoi esempi mostreranno unassegnazione: int id = addResponseIdToDB()
“. In tutti i punti in cui viene utilizzata la funzione, finirai documentando il fatto che la funzione ha restituito un valore.
Allo stesso modo “add” può essere facoltativo. Usiamo il termine “effetto collaterale” come termine onnicomprensivo, ma ci sono diverse sfumature. Se le voci del database sono solo un registro, potrebbe non esserci alcun motivo per evidenziarlo. Non vediamo mai funzioni come playMusicAndSendPersonalInformationToOurCorporateServers
. È “solo playMusic
e, se sei fortunato, la documentazione potrebbe menzionare qualcosa su una connessione socket su Internet. Daltra parte, se ci si aspetta che gli utenti chiamino questa funzione allo scopo di aggiungere elementi al DB, è essenziale “aggiungere”. Non tirarlo fuori.
Ora, ho scritto molte ragioni per fare ogni possibile combinazione di ciò che stai chiedendo. Non ho intenzionalmente risposto alla domanda perché cè una scelta da fare. Non è una regola da seguire. Stai creando unAPI.
Detto questo, il mio istinto è che addResponseIdToDB sia probabilmente la risposta migliore. Nella maggior parte dei casi la parte “get” è un effetto collaterale abbastanza ovvio da non guadagnare il rumore extra causato dalla digitazione ovunque. Tuttavia, ci sono alcuni punti in cui potrei considerarla importante:
- Se “get” è costoso – Se devi eseguire un “get” che implica il recupero di qualcosa su Internet e quindi aggiungerlo a un database cache locale, significa che “get” è importante. È la cosa che lutente sta cercando di fare.
- Se è necessario chiarire che lutente deve prestare attenzione al valore. Se lutente desidera utilizzare la variabile, si renderà conto che lAPI la restituisce e la utilizzerà. Tuttavia, si desidera fare attenzione allutente che non sa di averne bisogno. Ad esempio, se in un secondo momento è necessario “chiudere” questa operazione tramite ID per liberare memoria, è possibile attirare lattenzione sul fatto che si “stai facendo qualcosa. In questi casi, potresti voler guardare un verbo diverso da “get”. “get” spesso implica funzioni idempotenti (chiamarlo di nuovo non “fa nulla). Se restituisce risorse, altri verbi come” creare “o” acquisire “sono utili. Questo particolare meccanismo di errore è un grosso problema in C quando si fa la gestione delle eccezioni . C manca del meccanismo di cattura / lancio di C ++, quindi si basa sui codici di ritorno. Gli sviluppatori sono famosi semplicemente per non riuscire a controllare questi codici di ritorno e per trovarsi in situazioni davvero brutte come loverflow del buffer a causa di ciò.
- Simmetria: a volte si progetta unAPI in modo che abbia una simmetria lessicale. Si impostano le parole in modo che siano a coppie o altri modelli e si creano i comandi in modo che sia facile identificarli visivamente se lo schema è stato seguito o meno. Direi che è raro, ma non è inaudito. Cè “un motivo per chiudere i tag XML, ripetere il nome del tag (come < foo > e < / foo >).
Commenti
- Per le persone che non ‘ t conoscere la matematica: sia 1 / x che sqrt (x) sono funzioni piuttosto lente. Usando alcuni calcoli matematici intelligenti, possiamo calcolare 1 / sqrt (x) più velocemente di una divisione o di una radice quadrata. In realtà, molto più velocemente che il modo più veloce per calcolare sqrt (x) è calcolare 1 / sqrt (x) con unimplementazione intelligente e moltiplicare il risultato per x.
- Come in fisica, dove le unità elementari non sono più metro e secondi, ma secondi e velocità della luce, perché possiamo misurare la velocità della luce con maggiore precisione rispetto alla lunghezza di un metro.
Risposta
Qual è il massimo intent di questo meth od? Perché aggiungere questi ID trasformati al DB, è a scopo di memorizzazione nella cache? Lavorerò in base a questo presupposto.
Sembra che lo scopo del metodo sia in realtà solo quello di ottenere gli ID di risposta trasformati (facendo la trasformazione o recuperandoli da una cache), e così ci sono il nome del metodo:
getTransformedResponseIds
o meno verbalmente getResponseIds()
Un metodo con questo nome potrebbe memorizzare nella cache o forse no, e questo può essere documentato, ma il nome del tuo metodo non dovrebbe legarti a unimplementazione specifica; cosa succede se decidi di smettere di usare un DB e invece di metterlo in cache altrove, ad esempio solo temporaneamente in memoria?
Gli effetti collaterali dovrebbero essere proprio questo, gli effetti collaterali, dovrebbero probabilmente essere documentati ma non dovrebbero avere un impatto reale sullintento principale (o il successo) o sul nome della funzione. Se non riesce a ottenere il valore dalla cache (o se si configura per saltare la memorizzazione nella cache) che non dovrebbe essere un problema critico, il metodo dovrebbe semplicemente ricalcolare in modo trasparente, memorizzare nella cache (o meno) e restituire il nuovo valore.
A volte luso di “AND” è utile per comunicare lintento come con i metodi Java getAndIncrement
e incrementAndGet
quindi non è certo fuori discussione.
Risposta
Dici che la funzione restituisce qualcosa “da una trasformazione, non da any db action “.
Questo suggerisce che la funzione si comporta più come un costruttore che come un getter. Tuttavia, anche i costruttori non dovrebbero” causare effetti collaterali (grazie per il collegamento, @ MechMK1 ).
I metodi di fabbrica, daltra parte, presuppongono già un certo livello di confusione . Ad esempio, una delle motivazioni per lutilizzo dei metodi factory è che un metodo factory:
Consente un codice più leggibile nei casi in cui esistono più costruttori, ciascuno per un motivo diverso. – wikipedia
Allo stesso modo,
Se devi lavorare con gli effetti collaterali, un metodo di fabbrica può essere denominato in modo tale che sia più chiaro quali sono questi effetti collaterali. – ruakh
Considera lidea di rendere la funzione un metodo di fabbrica, utilizzando il termine “loggato” per avvisare i programmatori della memoria del database:
- Dichiarazione:
createLoggedResponseid(...) {...} // log object to db
- Chiama:
responseid = createLoggedResponseid(...);
Commenti
- I costruttori non dovrebbero causare effetti collaterali
- @ MechMK1 Ho ‘ modificato per rimuovere il suggerimento del costruttore e per fornire maggiore supporto per un metodo factory dal nome appropriato. Fammi sapere cosa vuoi pensa.
persistResponseIds
?getStatusOfPost
🙂responseIds = createResponseIds()
sembra chiaro e conciso.get()
qualcosa che ‘ non esisteva prima, quindicreate
è il verbo appropriato. Il materiale appena creato è ciò che ‘ ti aspetti che ti venga restituita una funzionecreate()
. O forse ‘ mi manca qualcosa di ovvio?