Jeg er i en knipe. Jeg jobber i et arkiv som gjør mange databaseendringer innenfor funksjonene.
Funksjonen jeg har med å returnere responsid (men fra en transformasjon, ikke fra noen databasehandling). Som en bivirkning legger den imidlertid til et objekt som inneholder disse responsIdene i databasen.
Så skal jeg gi den navnet:
-
getResponseIds
: Dette fremhever returverdiene. Det er en veldig funksjonell måte å tenke på, men hvis jeg har funksjonpostToDB
, gir det ingen mening å brukegetStatusOfPost
-
addResponseIdToDB
: Dette fremhever bivirkningen, selv om jeg virkelig tror at mange av funksjonene mine bare fungerer på databasen (og har en tendens til ikke å returnere noe) -
getAndAddResponseIdsToDB
: Veldig informativ, men veldig lang.
Hva er fordeler og ulemper med forslagene ovenfor? Eller kan du komme med et bedre forslag selv?
Kommentarer
Svar
Et funksjonsnavn som inneholder and
er på feil abstraksjonsnivå .
Jeg lener meg mot addResponseIdToDB()
fordi ellers er ‘bivirkningen’ en fullstendig overraskelse. Imidlertid:
responseIds = addResponseIdToDB();
lar ingen bli overrasket.
kommandosøket ansvar segregeringsprinsipp argumenterer for at dette ikke skal være den eneste måten å få responsId-objektet på. Det bør også være et spørsmål som ikke endrer DB som kan få dette objektet.
I motsetning til Bertrand Meyer , tror jeg ikke dette betyr at tillegget må returneres ugyldig. Bare betyr at en ekvivalent ren spørring burde eksistere og være lett å finne, slik at DB ikke blir misbrukt unødvendig ved bruk av tilstandsendrende spørsmål.
Gitt at getResponseIds()
burde eksistere og ikke snakke med databasen, er det beste navnet på en metode som gjør begge deler faktisk addToDB(getResponseId())
. Men det er bare hvis du vil ha all funksjonell sammensetning om det.
Kommentarer
- Legger til det som er blitt sagt, men å legge til et ord som ikke har blitt brukt ‘ t, vil jeg si at » og » er passende når en funksjon gjør noe som er atomisk . Hvis det er en fordel å oppnå ved at to ting skjer atomisk / sammen / samtidig, så er det eksplisitt hva disse to tingene er i funksjon navnet er bra. Så jeg er enig med din første uttalelse generelt, men er uenig uten noen kvalifikasjoner.
- » … etterlater ingen overrasket. «. Jeg er uenig, jeg ‘ er igjen overrasket over at en boolsk variabel heter
responseIds
og jeg må da gjøre et mentalt skifte for å innse at det legger til og blir alt i ett. Men jeg er uenig med @tintintong atgetAndAddResponseIdsToDB
er for lang; det ‘ er omtrent halvparten av lengden på mange av funksjonsnavnene mine. Hvis en funksjon gjør to ting, så si det i navnet. - @Luaan OK, hva skal du gi nytt navn til
getAndIncrement
til da?otherIncrement
? - @Luaan Så hvilken verdi returnerer
Increment
, verdien som øker eller etter økningen? Det ‘ er ikke klart hva det ville være uten å dykke inn i dokumenter.Jeg trorincrementAndGet
oggetAndIncrement
er langt bedre metodenavn, noe jeg mener gir et godt tilfelle for » OG » i metodenavn, i det minste i det spesifikke tilfellet av metoder som har bivirkninger som kan påvirke returverdien .. - @ Luaan Når du sier » Increment returnerer den økte verdien » mener du verdien før trinnet? Gotcha, misforsto deg først .. som faktisk er et godt eksempel på hvorfor
getAndIncrement
ogincrementAndGet
er bedre navn enn bareincrement
, selv når det vurderes individuelt
Svar
Som andre har nevnt, innebærer bruk av and
i et funksjonsnavn automatisk at en funksjon gjør minst to ting, noe som vanligvis indikerer at den gjør for mye eller gjør noe på feil sted.
Å bruke and
-satsen i et funksjonsnavn kan imidlertid, etter min erfaring, være fornuftig når det er noen trinn i en viss flyt som må utføres i rekkefølge eller når hele beregningen blir meningsfull.
I numerisk databehandling kan dette gi mye mening:
normalizeAndInvert(Matrix m)
Jeg mener, hvem vet, ikke sant? Hvis du trenger å beregne noen … si, lysbaner i datagrafikk og på et sertifikat i scenen må du normalisere og invertere en bestemt matrise, og du finner deg selv hele tiden å skrive:
m = normalize(m)
, etterfulgt av invert(m)
, som et enkelt eksempel, å introdusere abstraksjonen av normalisering og invertering, kan være bra fra et lesbarhetsperspektiv.
Når vi snakker semantisk, ting som divideAndConquer()
etc, er sannsynligvis motet til å bli eksplisitt skrevet ut, men vel, And
det er i utgangspunktet nødvendig.
Kommentarer
- Funksjoner gjør regelmessig mer enn én ting, men det burde være små ting som sammenlegger en stor ting som ‘ er meningsfylt på et høyere nivå.
normalizeAndInvert
kan værecomputeLighting
ogdivideAndConquer
kan væreapplyMachiavelli
- Tydeligvis 🙂 Bare å si at du først oppretter en funksjon med en » og » i navnet kan komme som en naturlig abstraksjon basert på en bestemt kontekst. Refactoring via » Endre navnemetode » skal komme umiddelbart etter;)
- @BrunoOliveira Bortsett fra at de to trinnene noen ganger er selv en komponent av mange ting, endre navn er ikke ‘ t riktig tilnærming. Og burde være sjelden, men å aldri bruke det betyr at du ‘ gjentar deg selv.
Svar
Jeg vil si at det er greit generelt, men er ikke akseptabelt i alle tilfeller.
For eksempel kan man argumentere mot and
i et funksjonsnavn basert på enkeltansvarsprinsippet aka S i SOLID – fordi and
kunne antyde flere ansvarsområder. Hvis and
virkelig betyr at funksjonen gjør to ting, vil du sannsynligvis tenke nøye gjennom hva som skjer.
Et eksempel på et bibliotek som bruker and
i funksjonsnavn kan bli funnet i Java «s samtidighet og her and
er faktisk en veldig viktig del av det som skjer, og samsvarer nøye med det du beskriver i innlegget ditt der staten endres og staten returneres. Så tydelig er det noen mennesker ( og noen brukstilfeller) der det anses akseptabelt.
Kommentarer
- »
and
kan antyde flere ansvarsområder «. Og i dette tilfellet indikerer det faktisk det. Funksjonen får noen svar-ID-er fra et sted, skriver dem til en database og returnerer dem. Behovet for at » og » burde i det minste heve ideen med OP om at to funksjoner er nødvendige: en for å få ID-ene og en for å skrive dem til databasen. - Selv om jeg er enig i at
and
er en kodelukt, virker den grunnleggende oppførselen legitim: Den er generelt ikke dårlig ide om å returnere noen tilleggsverdier fra en metode som tilfeldigvis er nødvendige (mellomliggende) resultater for å utføre hovedfunksjonen. Hvis hovedfunksjonen til denne metoden er å legge til svar-ID-ene i databasen, er det i tillegg bare en fin og enkel brukervennlighet ved metoden å returnere ID-ene som den har lagt til innringeren. - Jeg tror ikke ‘ t mener dette nødvendigvis bryter med SRP. Du trenger alltid funksjoner som gjør flere ting. Den viktige delen er at disse funksjonene kaller en annen funksjon for alle ting de vil gjøre i stedet for å inneholde den nødvendige koden direkte.
- Hvis funksjonen gjør to ting, bør navnet gjenspeile den.
Svar
Det er faktisk et vanskelig spørsmål å svare på som mange kommentarer kan vitne om. Folk ser ut til å ha motstridende meninger og råd på hva som er et godt navn.
Jeg vil gjerne legge til mine 2 cent i denne 2 måneder gamle tråden fordi jeg tror jeg kan legge til flere farger til det som allerede ble foreslått.
Navngivning er en prosess
Alt dette minner meg om den utmerkede guiden med 6 trinn: Navngivning som en prosess .
Et dårlig funksjonsnavn er et som er overraskende og du skjønner at du ikke kan stole på. God navngiving er vanskelig å få rett ved første skudd. Det blir lettere med erfaring.
6 iterative trinn for å bygge et godt navn
- Erstatt overraskende navn med åpenbart tull som
appleSauce()
. Høres dumt ut, men det er midlertidig og det gjør det åpenbart at navnet ikke kan stole på. - Få et ærlig navn , basert på hva du forstår av hva funksjonen gjør. Si at du ikke forsto ennå innsettingen i DB-delen,
getResponseIdsAndProbablyUseDb
ville være et alternativ. - Få til helt ærlig , så funksjonsnavnet sier alt funksjonen gjør (
getAndAddResponseIdsToDB
ellergetResponsesButAlsoAddCacheOfTheResponsesToTheDb
fra @Fattie er gode eksempler) - Kom til «gjør det rette» som egentlig er delen der du deler funksjonen din langs «AND». Så du har faktisk
getResponseIds
ogaddResponseIdsToDb
. - Kom til et «Intention Revealing» -navn som løser poenget med «Jeg vil alltid få svar-ID jeg satt inn i DB etter at jeg har gjort det «. Slutt å tenke på implementeringsdetaljene og bygg en abstraksjon som bruker de to minste funksjonene til å bygge noe annet. Dette er det høyere abstraksjonsnivået nevnt av @candied_orange. For e xample
createResponseIds
kan gjøre det og vil være sammensetningen avgetResponseIds
ogaddResponseIdsToDb
. - Gå til domeneabstraksjonen din . Dette er vanskelig, det avhenger av virksomheten din. Det tar tid å lytte til forretningsspråket ditt for å komme riktig. Etter hvert kan du ende opp med konseptet med en
Response
ogResponse.createIds()
vil være fornuftig. Eller kanskjeResponseId
er noe verdifullt, og du vil ha en fabrikk for å skape mange. Innsetting i DB vil være en detalj for implementeringen av et depot osv. Ja, designet vil være mer abstrakt. Det ville være rikere og la deg uttrykke mer. Ingen utenfor teamet ditt kan fortelle deg hva den rette domeneabstraksjonen skal være i din situasjon. Det kommer an .
I ditt tilfelle har du allerede funnet ut 1 og 2, så du bør sannsynligvis i det minste gå til 3 (bruk «OG») eller videre. Men å gå videre er ikke bare et spørsmål om navngivning, du vil faktisk dele opp ansvaret.
Dermed er de forskjellige forslagene her gyldige
I hovedsak:
- Ja, overraskende funksjonsnavn er forferdelig, og ingen vil takle det
- Ja, «AND» er akseptabelt i et funksjonsnavn fordi det er bedre enn et misvisende navn
- Ja, abstraksjonsnivået er et beslektet konsept og det betyr noe
Du trenger ikke å gå til trinn 6 med en gang, det er greit å bo på trinn 2, 3 eller 4 til du vet bedre!
Jeg håper det vil være nyttig å gi et annet perspektiv på hva som ser ut som motstridende råd. Jeg tror at alle sikter mot det samme målet ( ikke overraskende navn), men stopp på forskjellige stadier, basert på egen erfaring.
Glad for å svare hvis du har spørsmål 🙂
Svar
Funksjonen din gjør to ting:
- Får et sett med ID-er via en transformasjon og returnerer dem til innringeren,
- Skriver settet med ID-er til en database.
Husk prinsippet om enkeltansvar her: du bør sikte på en funksjon å ha ett ansvar, ikke to. Du har to ansvarsområder, så har to funksjoner:
getResponseIds
– Får et sett med ID-er via en transform og returnerer dem til den som ringer
addResponseIdToDB
– Tar et sett med ID-er og skriver dem til en database.
Og hele spørsmålet om hva man skal kalle en enkelt funksjon med flere ansvarsområder forsvinner og fristelsen til å sette and
i funksjonsnavnene forsvinner også.
Som en ekstra bonus, fordi den samme funksjonen ikke lenger er ansvarlig for to ikke-relaterte handlinger, kan getResponseIds
flyttes ut av repokoden din (der den ikke hører hjemme ettersom den ikke gjør noen DB-relatert aktivitet), inn i en egen klasse / modul på forretningsnivå osv.
Kommentarer
- At ‘ er sikkert sant, men du oppdager at du gjentar en kodebit ved hjelp av disse to SRP-metodene mange ganger, så bør du sannsynligvis skrive en triviell metode for å gjøre dette slik at du TØRRER, ikke ‘ t deg?
- @maaartinus, hvis du finner at du kaller disse to metodene mange steder, så har du sannsynligvis et problem med manglende sammenheng i koden din. Å lage en » TØRR » -funksjon, så maskerer du bare denne mangelen på sammenheng.
Svar
Å ha et sammensatt funksjonsnavn er akseptabelt, så hvilket navneskjema du bruker, avhenger av konteksten din. Det er purister som vil fortelle deg å bryte det opp, men jeg vil argumentere for noe annet.
Det er to grunner til å prøve å unngå slike ting:
- Renhet – En funksjon som gjør to ting, skal konverteres til to funksjoner som gjør én ting hver.
- Leksisk størrelse – en
bigUglyCompositeFunctionName
blir vanskelig å lese.
Renhetsargumentet er vanligvis det som er fokusert på. Som en vag generell heuristikk er det bra å bryte opp funksjoner. Det hjelper med å dele opp hva som skjer, noe som gjør det lettere å forstå. Det er mindre sannsynlig at du gjør «smarte» triks med deling av variabler som fører til kobling mellom de to atferdene.
Så det å spørre deg selv er «skal jeg gjøre det uansett?» Jeg sier at å bryte opp ting er en vag heuristikk, så du trenger egentlig bare et anstendig argument for å gå imot det.
En hovedårsak er at det er gunstig å tenke på de to operasjonene som en. Det endelige eksemplet på dette finnes i atomoperasjoner: compare_and_set
. compare_and_set
er en grunnleggende hjørnestein i atomene (som er en måte å gjøre multitrading uten låser på) som er vellykket nok til at mange er villige til å tenke på det som the hjørnestein. Det er «og» i det navnet. Det er en veldig god grunn til dette. Hele poenget med denne operasjonen er at den gjør sammenligningen og innstillingen som en udelelig operasjon. Hvis du brøt den opp i compare()
og set()
, ville du beseire hele grunnen til at funksjonen eksisterte i utgangspunktet, fordi to compares()
kan forekomme rygg mot rygg før set()
s.
Vi ser dette også i forestillinger. Mitt favoritteksempel er fastInvSqrt . Dette er en kjent algoritme fra Quake som beregner 1 / sqrt (x). Vi kombinerer operasjonene «invers» og «kvadratrot» fordi det gir oss en massiv ytelse. De gjør en Newtons tilnærming som utfører begge operasjonene i ett enkelt trinn (og tilfeldigvis gjør det i heltalsmatematikk i stedet for flytende punkt, noe som gjaldt i tiden). Hvis du skulle gjøre inverse(sqrt(x))
, ville det vært mye tregere!
Det er noen ganger resultatet er mer tydelig. Jeg har skrevet flere API-er som involverer threading der man må være fryktelig forsiktig med ting som fastlås. Jeg ville ikke at brukeren min skulle være oppmerksom på de interne detaljene for implementeringen (spesielt siden jeg kan endre dem), så jeg skrev API med noen få «og» funksjoner som forhindret brukeren i å måtte holde en lås i mer enn en funksjonsanrop. Dette betyr at de aldri trengte å vite hvordan jeg håndterte flertrådet synkronisering under panseret. Faktisk var de fleste av brukerne mine ikke engang klar over at de befant seg i et flertrådet miljø!
Så mens den generelle regelen er å bryte opp ting, kan det alltid være en grunn til å koble dem sammen. For eksempel , kan brukeren bryte arkitekturen ved å legge til i en database flere ganger uten «får» imellom? Hvis hvert av svarene som er logget inn i DB, trenger å ha en unik identifikator, kan du få problemer hvis brukeren må gjøre dem nilly. Likeledes, vil du noen gang la en bruker «få» uten å logge resultatene i DB? Hvis svaret er «ja», kan du dele dem opp slik at brukeren kan få tilgang til «få» -funksjonen. sikkerheten til applikasjonen din er ødelagt hvis brukeren kan «få» uten at resultatet blir logget inn i DB, bør du virkelig holde funksjonene sammen.
Nå som for de leksikale problemene, skal funksjonsnavnet ditt beskrive hva brukeren trenger å vite om det. La oss starte med «få» -delen.Det er ganske enkelt å fjerne «get» fra et funksjonsnavn, fordi alle eksemplene dine viser en oppgave: int id = addResponseIdToDB()
«. Alle steder der funksjonen brukes, vil du avslutte opp å dokumentere det faktum at funksjonen returnerte en verdi.
På samme måte kan «legge til» være valgfritt. Vi bruker begrepet «bivirkning» som et altomfattende begrep, men det er forskjellige nyanser av det. Hvis DB-oppføringene bare er en logg, kan det være ingen grunn til å markere den. Vi ser aldri funksjoner som playMusicAndSendPersonalInformationToOurCorporateServers
. Det er bare playMusic
, og hvis du er heldig, kan dokumentasjonen nevne noe om en stikkontakt over internett. På den annen side, hvis det forventes at brukerne kaller denne funksjonen for å legge til ting i DB, så er «add» viktig. Ikke ta det ut.
Nå har jeg skrevet mange grunner til å gjøre alle mulige kombinasjoner av det du spør. Jeg har bevisst ikke svart på spørsmålet fordi det er et valg som skal tas. Det er ikke en regel å følge. Du lager en API.
Når det er sagt, er instinktet mitt at addResponseIdToDB sannsynligvis vil være det beste svaret. I de fleste tilfeller er «get» -delen en åpenbar nok bivirkning til at den ikke tjener ekstra støy forårsaket av å skrive den overalt. Imidlertid er det noen få steder der jeg anser det som viktig:
- Hvis «get» er dyrt – Hvis du må gjøre et «get» som innebærer å hente noe over internett og deretter legge det til en lokal cache-DB, er «get» for all del viktig. Det er tingen brukeren prøver å gjøre.
- Hvis du trenger å gjøre det klart at brukeren trenger å ta hensyn til verdien. Hvis brukeren vil bruke variabelen, vil de forstå at API-en returnerer den, og de vil bruke den. Du vil imidlertid se opp for brukeren som ikke vet at de trenger det. Hvis du for eksempel må «lukke» denne operasjonen med ID senere for å frigjøre minne, kan det være lurt å gjøre oppmerksom på at du «gjør noe. I disse tilfellene vil du kanskje se på et annet verb enn «få». «Get» innebærer ofte idempotente funksjoner (å kalle det igjen gjør ikke noe). Hvis det returnerer ressurser, er andre verb som «create» eller «erhverve» fine. Denne spesielle feilmekanismen er et viktig problem i C når du gjør unntaksbehandling C mangler fangst / kast-mekanismen til C ++, så den er avhengig av returkoder. Utviklere er berømte for bare å ikke kontrollere disse returkodene og komme i virkelig dårlige situasjoner som bufferoverløp på grunn av det.
- Symmetri – Noen ganger designer du et API for å ha en leksikal symmetri. Du setter opp ordene slik at de kommer i par, eller andre mønstre, og lager kommandoene slik at det er lett å visuelt identifisere om mønsteret har blitt fulgt eller ikke. Jeg vil si at dette er sjeldent, men det er ikke uhørt. Det er en grunn til å lukke XML-koder, gjenta navnet på koden (for eksempel < foo > og < / foo >).
Kommentarer
- For folk som ikke ‘ vet ikke matematikken: Både 1 / x og sqrt (x) er ganske langsomme funksjoner. Ved å bruke noen smarte matematikker kan vi beregne 1 / sqrt (x) raskere enn enten en inndeling eller en kvadratrot. Egentlig så mye raskere at den raskeste måten å beregne sqrt (x) er å beregne 1 / sqrt (x) med en smart implementering, og multiplisere resultatet med x.
- Som i fysikk, der elementære enheter ikke lenger er meter og andre, men andre og lysets hastighet, fordi vi kan måle lysets hastighet med mer presisjon enn lengden på en meter.
Svar
Hva er det ultimate intensjon av denne metoden od? Hvorfor legge til disse transformerte ID-ene i DB, er det for caching? Jeg vil jobbe under den forutsetningen.
Det høres ut som at hensikten med metoden egentlig bare er å få de transformerte respons-IDene (enten å gjøre transformasjonen eller få dem fra en cache), og så er det metodens navn:
getTransformedResponseIds
eller mindre ordlig getResponseIds()
En metode med dette navnet kan cache eller kanskje ikke, og det kan dokumenteres, men metodenavnet ditt burde ikke knytte deg til en bestemt implementering. Hva om du bestemmer deg for å slutte å bruke en DB og i stedet cache dem andre steder, for eksempel bare midlertidig i minnet?
Bivirkninger bør være nettopp det, bivirkninger, de burde sannsynligvis være dokumentert, men de burde egentlig ikke ha innvirkning på hovedintensjonen (eller suksessen) eller navnet på funksjonen. Hvis verdien av hurtigbufferen mislykkes (eller hvis du konfigurerer å hoppe over hurtigbufring) som ikke burde være et kritisk problem, bør metoden bare beregne på nytt, cache (eller ikke), og returnere den nye verdien.
Noen ganger kan bruk av «AND» være nyttig for å formidle hensikter som med Java-metodene getAndIncrement
og incrementAndGet
at «absolutt ikke er utenfor bordet.
Svar
Du sier at funksjonen returnerer noe» fra en transformasjon, ikke fra hvilken som helst db-handling «.
Dette antyder at funksjonen oppfører seg mer som en konstruktør enn en getter. Imidlertid burde -konstruktører ikke også forårsake bivirkninger (takk for lenken, @ MechMK1 ).
Fabrikkmetoder forutsetter derimot allerede noe rot . For eksempel er en av motivasjonene for å bruke fabrikkmetoder at en fabrikkmetode:
Tillater mer lesbar kode i tilfeller der flere konstruktører eksisterer, hver for en annen grunn. – wikipedia
Likeledes,
Hvis du trenger å gjøre noe med bivirkninger, kan en fabrikkmetode navngis på en slik måte at det er tydeligere hva disse bivirkningene er. – ruakh
Vurder å gjøre funksjonen til en fabrikkmetode ved å bruke begrepet «logget» for å varsle programmerere om databaselagringen:
- Erklæring:
createLoggedResponseid(...) {...} // log object to db
- Ring:
responseid = createLoggedResponseid(...);
Kommentarer
- Konstruktører skal ikke forårsake bivirkninger
- @ MechMK1 Jeg ‘ har redigert for å fjerne konstruktøren forslaget, og for å gi mer støtte for en passende fabrikkmetode. Gi meg beskjed om hva du tenk.
persistResponseIds
?getStatusOfPost
🙂responseIds = createResponseIds()
virker tydelig og kortfattet. Duget()
noe som ikke eksisterte ‘ t før, såcreate
er riktig verb. De nyopprettede tingene er det du ‘ forventer at encreate()
-funksjon skal returnere. Eller kanskje jeg ‘ savner noe åpenbart?