Jag befinner mig i en svår situation. Jag arbetar i ett arkiv som gör många databasändringar inom dess funktioner.
Funktionen jag har att göra med returnerar responsid (men från en transformation, inte från någon databasåtgärd). Som en bieffekt lägger det dock till ett objekt som innehåller dessa responseIds i databasen.
Så ska jag namnge det:
-
getResponseIds
: Detta belyser returvärdena. Det är ett mycket funktionellt sätt att tänka, men självklart om jag har funktionpostToDB
är det ingen mening att användagetStatusOfPost
-
addResponseIdToDB
: Detta belyser bieffekten, även om jag verkligen tror att många av mina funktioner bara fungerar i databasen (och tenderar att inte returnera någonting) -
getAndAddResponseIdsToDB
: Mycket informativt, men väldigt långt.
Vilka är för- och nackdelarna med förslagen ovan? Eller kan du göra ett bättre förslag själv?
Kommentarer
Svar
Ett funktionsnamn som innehåller and
är på fel abstraktionsnivå .
Jag lutar mig mot addResponseIdToDB()
för annars är ”bieffekten” en fullständig överraskning. Men:
responseIds = addResponseIdToDB();
lämnar ingen förvånad.
kommandofrågan ansvarsfördelningsprincip hävdar att detta inte borde vara det enda sättet att få responseId-objektet. Det bör också finnas en fråga som inte ändrar DB: n som kan få detta objekt.
I motsats till Bertrand Meyer , tror jag inte att det betyder att tillägget måste återkallas. Det betyder bara att en motsvarande ren fråga borde finnas och vara lätt att hitta så att DB inte missbrukas onödigt genom användning av tillståndsförändringar.
Med tanke på att getResponseIds()
borde finnas och inte prata med databasen, är det bästa namnet på en metod som gör båda addToDB(getResponseId())
. Men det är bara om du vill få all funktionell komposition om det.
Kommentarer
- Lägg till vad som har sagts, men att lägga till ett ord som inte har använts ’, skulle jag säga att ” och ” är lämpligt när en funktion gör något som är atomiskt . Om det finns en fördel att få från två saker som sker atomiskt / tillsammans / på en gång, då är det tydligt med vad dessa två saker är i funktionen namnet är bra. Så jag håller med ditt första uttalande i allmänhet, men håller inte utan några kvalifikationer.
- ” … lämnar ingen förvånad. ”. Jag håller inte med, jag ’ är kvar förvånad över att en boolesk variabel heter
responseIds
och jag måste sedan göra ett mentalt skifte för att inse att det lägger till och blir allt i ett. Men jag håller inte med @tintintong attgetAndAddResponseIdsToDB
är för lång; det ’ är ungefär halva längden på många av mina funktionsnamn. Om en funktion gör två saker, säg så i dess namn. - @Luaan OK, vad ska du byta namn på
getAndIncrement
till då?otherIncrement
? - @Luaan Vilket värde returnerar
Increment
, värdet före inkrement eller efter inkrement? Det ’ är inte klart vad det skulle vara utan att dyka in i dokument.Jag tror attincrementAndGet
ochgetAndIncrement
är mycket bättre metodnamn, vilket jag tror ger ett bra argument för ” OCH ” i metodnamn, åtminstone i det specifika fallet med metoder som har biverkningar som kan påverka returvärdet .. - @Luaan När du säger ” Inkrement returnerar det ökade värdet ” menar du värdet före inkrementet? Gotcha, förstod dig först .. vilket faktiskt är ett bra exempel på varför
getAndIncrement
ochincrementAndGet
är bättre namn än baraincrement
, även om det betraktas individuellt
Svar
Som andra har nämnt innebär användning av and
i ett funktionsnamn automatiskt att en funktion gör minst två saker, vilket vanligtvis indikerar att den gör för mycket eller gör något på fel plats.
Att använda and
i ett funktionsnamn kan dock enligt min erfarenhet vara meningsfullt när det finns några steg i ett visst flöde som måste utföras i följd eller när hela beräkningen blir meningsfull.
I numerisk beräkning kan det vara mycket meningsfullt:
normalizeAndInvert(Matrix m)
Jag menar, vem vet, eller hur? Om du behöver beräkna några … säg, ljusbanor i datorgrafik och på ett cert i scenen måste du normalisera och invertera en viss matris och du befinner dig ständigt att skriva:
m = normalize(m)
, följt av invert(m)
, precis som ett enkelt exempel, att introducera abstraktion av normalisering och invertera, kan vara bra ur ett läsbarhetsperspektiv.
Somantiskt talar saker som divideAndConquer()
etc, är troligen avskräckt från att uttryckligen skrivas ut, men väl, And
där är i grunden nödvändigt.
Kommentarer
- Funktioner gör regelbundet mer än en sak, men det borde vara små saker att lägga till en stor sak som ’ är meningsfullt på en högre nivå.
normalizeAndInvert
kan varacomputeLighting
ochdivideAndConquer
kan varaapplyMachiavelli
- Uppenbarligen 🙂 Att bara säga att först skapa en funktion med en ” och ” i namnet kan komma som en naturlig abstraktion baserad på ett visst sammanhang. Refactoring via ” Byt namnmetod ” ska komma omedelbart efter;)
- @BrunoOliveira Förutom ibland är de två stegen själva en del av många saker, byt namn är inte ’ t rätt tillvägagångssätt. Och bör vara sällsynt men att aldrig använda det betyder att du ’ upprepar dig själv.
Svar
Jag skulle säga att det är bra i allmänhet, men det är inte acceptabelt i alla fall.
Till exempel kan man argumentera mot and
i ett funktionsnamn baserat på principen om enskilt ansvar aka S i SOLID – eftersom and
kan innebära flera ansvarsområden. Om and
verkligen betyder att funktionen gör två saker, vill du förmodligen tänka noga över vad som händer.
Ett exempel på ett bibliotek som använder and
i funktionsnamn finns i Java ”s concurrency och här and
är faktiskt en mycket viktig del av vad som händer och matchar nära det du beskriver i ditt inlägg där staten ändras och tillstånd returneras. Så tydligt finns det vissa människor ( och vissa användningsfall) där det anses acceptabelt.
Kommentarer
- ”
and
kan innebära flera ansvarsområden ”. Och i detta fall indikerar det verkligen det. Funktionen får några svars-ID från någonstans, skriver dem till en databas och returnerar dem. Behovet av att ” och ” borde verkligen åtminstone lyfta tanken med OP att två funktioner behövs: en för att få ID: n och en för att skriva dem till databasen. - Även om jag håller med om att
and
är en kodlukt, verkar det grundläggande beteendet legitimt: det är i allmänhet inte dåligt idé att returnera några ytterligare värden från en metod som råkar vara nödvändiga (mellanliggande) resultat för att utföra dess huvudfunktion. Om huvudfunktionen för denna metod är att lägga till svars-ID: n till databasen är det bara en enkel och enkel användbarhetsfunktion för metoden att returnera ID: n som den har lagt till den som ringer. - Jag tror inte ’ att detta inte nödvändigtvis bryter mot SRP. Du behöver alltid funktioner som gör flera saker. Den viktiga delen är att dessa funktioner anropar en annan funktion för varje sak de vill göra istället för att innehålla den nödvändiga koden direkt.
- Om funktionen gör två saker ska namnet återspegla den.
Svar
Det är verkligen en svår fråga att besvara som många kommentarer kan intyga. Människor verkar ha motstridiga åsikter och råd på vad som är ett bra namn.
Jag skulle vilja lägga till mina 2 cent i denna 2 månader gamla tråd eftersom jag tror att jag kan lägga till fler färger till det som redan föreslogs.
Namngivning är en process
Allt detta påminner mig om den utmärkta 6-stegsguiden: Namngivning som en process .
Ett dåligt funktionsnamn är ett som är överraskande och du inser att du inte kan lita på det. Bra namngivning är svårt att få rätt vid första skottet. Det blir lättare med erfarenhet.
6 iterativa steg för att bygga ett bra namn
- Ersätt överraskande namn med uppenbart nonsens som
appleSauce()
. Låter dumt, men det är tillfälligt och det gör det uppenbart att namnet inte kan lita på. - Gå till ett ärligt namn , baserat på vad du förstår av vad funktionen gör. Säg att du inte förstod att infogningen i DB-delen,
getResponseIdsAndProbablyUseDb
skulle vara ett alternativ. - Skaffa för att helt ärligt , så funktionsnamnet säger allt som funktionen gör (
getAndAddResponseIdsToDB
ellergetResponsesButAlsoAddCacheOfTheResponsesToTheDb
från @Fattie är fantastiska exempel) - Gå till ”gör rätt” den del där du delar din funktion längs ”AND”. Så du har faktiskt
getResponseIds
ochaddResponseIdsToDb
. - Gå till ett ”Intention Revealing” -namn som löser poängen med ”Jag vill alltid få svar-ID I infogad i DB efter att jag gjort det ”. Sluta tänka på implementeringsdetaljen och bygg en abstraktion som använder de två minsta funktionerna för att bygga något annat. Detta är den högre abstraktionsnivå som nämns av @candied_orange. För e xample
createResponseIds
kan göra det och kommer att vara sammansättningen avgetResponseIds
ochaddResponseIdsToDb
. - Gå till din domänabstraktion . Det här är svårt, det beror på ditt företag. Det tar tid att lyssna på ditt affärsspråk för att komma rätt. Så småningom kan du sluta med begreppet
Response
ochResponse.createIds()
vettigt. Eller kanske ärResponseId
något värdefullt och du skulle ha en fabrik för att skapa många. Införande i DB skulle vara en implementeringsdetalj för ett arkiv etc. Ja, designen skulle vara mer abstrakt. Det skulle vara rikare och låta dig uttrycka mer. Ingen utanför ditt team kan berätta vad rätt domänabstraktion ska vara i din situation. Det beror .
I ditt fall har du redan tänkt ut 1 och 2, så du borde antagligen åtminstone gå till 3 (använd ”OCH”) eller längre. Men att gå längre är inte bara en fråga om namngivning, du skulle faktiskt dela ansvarsområdena.
Således är de olika förslagen här giltiga
I huvudsak:
- Ja, förvånande funktionsnamn är fruktansvärda och ingen vill hantera det
- Ja, ”OCH” är acceptabelt i ett funktionsnamn eftersom det är bättre än ett vilseledande namn
- Ja, abstraktionsnivån är ett relaterat begrepp och det spelar roll
Du behöver inte gå till steg 6 direkt, det är bra att stanna på steg 2, 3 eller 4 tills du vet bättre!
Jag hoppas att det skulle vara till hjälp för att ge ett annat perspektiv på vad som ser ut som motsägelsefulla råd. Jag tror att alla siktar på samma mål ( icke-överraskande namn) men stannar vid olika steg, baserat på deras egna erfarenheter.
Glad att svara om du har några frågor 🙂
Svar
Din funktion gör två saker:
- Får en uppsättning ID via en transform och returnerar dem till den som ringer,
- Skriver den uppsättningen ID: n till en databas.
Kom ihåg principen om enstaka ansvar här: du bör sikta på en funktion att ha ett ansvar, inte två. Du har två ansvarsområden, så har två funktioner:
getResponseIds
– Får en uppsättning ID: n via en transform och returnerar dem till den som ringer
addResponseIdToDB
– Tar en uppsättning ID och skriver dem till en databas.
Och hela frågan om vad man ska kalla en enda funktion med flera ansvarsområden försvinner och frestelsen att sätta and
i funktionsnamnen försvinner också.
Som en extra bonus, eftersom samma funktion inte längre är ansvarig för två orelaterade åtgärder, kan getResponseIds
flyttas från din repokod (där den inte hör hemma eftersom det inte gör någon DB-relaterad aktivitet), till en separat affärsnivåklass / modul etc.
Kommentarer
- Att ’ är säkert sant, men du upplever att du upprepar ett utdrag med dessa två SRP-metoder många gånger, då borde du antagligen skriva en trivial metod för att göra det så att du TORKAR, ska inte ’ t du?
- @maaartinus, om du befinner dig att anropa dessa två metoder på många ställen, har du förmodligen ett problem med bristande sammanhållning i din kod. Skapa en ” TORR ” -funktion maskerar bara denna brist på sammanhållning.
Svar
Att ha ett sammansatt funktionsnamn är acceptabelt, så vilket namngivningsschema du använder beror på ditt sammanhang. Det finns purister som säger att du ska bryta upp det, men jag kommer att argumentera för annat.
Det finns två skäl att försöka undvika sådana saker:
- Renhet – En funktion som gör två saker bör omvandlas till två funktioner som gör en sak vardera.
- Lexikalisk storlek – en
bigUglyCompositeFunctionName
blir svår att läsa.
Renhetsargumentet är vanligtvis det som fokuseras på. Som en vag allmän heuristik är det bra att bryta upp funktioner. Det hjälper till att dela upp vad som händer, vilket gör det lättare att förstå. Det är mindre troligt att du gör ”smarta” knep med delning av variabler som leder till koppling mellan de två beteenden.
Så att fråga dig själv är ”ska jag göra det ändå?” Jag säger att bryta upp saker är en vag heuristik, så du behöver egentligen bara ett anständigt argument för att gå emot det.
En viktig anledning är att det är fördelaktigt att tänka på de två operationerna som en. Det slutliga exemplet på detta finns i atomoperationer: compare_and_set
. compare_and_set
är en grundläggande hörnsten i atomerna (som är ett sätt att göra multitrådning utan lås) vilket är tillräckligt framgångsrikt för att många är villiga att tänka på det som the hörnsten. Det finns ”och” i det namnet. Det finns en mycket bra anledning till detta. Hela poängen med denna operation är att den gör jämförelsen och inställningen som en odelbar operation. Om du delade upp det i compare()
och set()
skulle du besegra hela anledningen till att funktionen fanns i första hand, eftersom två compares()
kan förekomma rygg mot rygg innan set()
s.
Vi ser detta också i prestanda. Mitt favoritexempel är fastInvSqrt . Detta är en berömd algoritm från Quake som beräknar 1 / sqrt (x). Vi kombinerar operationerna ”invers” och ”kvadratrot” eftersom detta ger oss en massiv prestandaförstärkning. De gör en Newtons approximation som gör båda operationerna i ett enda steg (och råkar göra det i heltalsmatematik snarare än flytande punkt, vilket betydde i eran). Om du skulle göra inverse(sqrt(x))
skulle det vara mycket långsammare!
Det finns vissa tillfällen där resultatet blir tydligare. Jag har skrivit flera API: er som involverar trådning där man måste vara fruktansvärt försiktig med saker som blockeringar. Jag ville inte att min användare skulle vara medveten om de interna implementeringsuppgifterna (speciellt eftersom jag kan ändra dem), så jag skrev API med några ”och” funktioner som hindrade användaren från att någonsin behöva hålla ett lås i mer än ett funktionssamtal. Det betyder att de aldrig behövde veta hur jag hanterade den multitrådade synkroniseringen under huven. Faktum är att de flesta av mina användare inte ens var medvetna om att de befann sig i en multitrådad miljö!
Så medan den allmänna regeln är att bryta upp saker kan det alltid finnas en anledning att para ihop dem. Till exempel , kan din användare bryta din arkitektur genom att lägga till i en databas flera gånger utan att ”blir” däremellan? Om vart och ett av svaren som är inloggade i DB: n måste ha en unik identifierare kan du komma i problem om användaren måste göra det nilly. På samma sätt vill du låta en användare ”få” utan att logga in resultaten i DB? Om svaret är ”ja”, dela upp dem så att användaren kan komma åt ”få” -funktionen. Men om säkerheten för din applikation är trasig om användaren kan ”få” utan att resultatet loggas i DB, du borde verkligen hålla funktionerna ihop.
Nu när det gäller de lexikala problemen bör ditt funktionsnamn beskriva vad användaren behöver veta om det. Låt oss börja med ”get” -delen.Det är ganska enkelt att ta bort ”get” från ett funktionsnamn, eftersom alla dina exempel visar en uppgift: int id = addResponseIdToDB()
”. På alla platser där funktionen används kommer du att avsluta upp att dokumentera det faktum att funktionen returnerade ett värde.
På samma sätt kan ”lägga till” vara valfritt. Vi använder termen ”bieffekt” som en heltäckande term, men det finns olika nyanser av den. Om DB-posterna bara är en logg kan det inte finnas någon anledning att markera den. Vi ser aldrig funktioner som playMusicAndSendPersonalInformationToOurCorporateServers
. Det är bara playMusic
, och om du är lycklig kan dokumentationen nämna något om en uttagsanslutning över internet. Å andra sidan, om användarna förväntas ringa den här funktionen i syfte att lägga till saker i DB, är ”lägg till” viktigt. Ta inte ut det.
Nu har jag skrivit många skäl att göra alla möjliga kombinationer av vad du frågar. Jag har medvetet inte svarat på frågan eftersom det finns ett val att göra. Det är ingen regel att följa. Du skapar ett API.
Med detta sagt är min instinkt att addResponseIdToDB sannolikt är det bästa svaret. I de flesta fall är ”get” -delen en uppenbar tillräcklig bieffekt för att den inte tjänar det extra bullret som orsakas av att skriva överallt. Det finns dock några ställen där jag anser det viktigt:
- Om ”get” är dyrt – Om du måste göra ett ”get” som innebär att du hämtar något över internet och sedan lägger till det i en lokal cache-DB, är ”get” i alla fall viktigt. Det är det som användaren försöker göra.
- Om du behöver göra det klart att användaren behöver vara uppmärksam på värdet. Om användaren vill använda variabeln kommer de att inse att API: n returnerar den och de kommer att använda den. Du vill dock se upp för användaren som inte vet att de behöver det. Om du till exempel måste ”stänga” den här åtgärden med ID senare för att frigöra minne, kanske du vill uppmärksamma det faktum att du ”gör något. I dessa fall kanske du vill titta på ett annat verb än ”få”. ”get” innebär ofta idempotenta funktioner (att ringa det igen gör ingenting). Om det returnerar resurser är andra verb som ”skapa” eller ”förvärva” trevliga. Denna specifika felmekanism är en stor fråga i C när man gör undantagshantering C saknar fångst- / kastmekanismen för C ++, så det förlitar sig på returkoder. Utvecklare är berömda för att de helt enkelt inte kontrollerar dessa returkoder och hamnar i riktigt dåliga situationer som buffertöverflöd på grund av det.
- Symmetri – Ibland utformar du ett API för att ha en lexikal symmetri. Du ställer in orden så att de kommer i par eller andra mönster och utformar kommandona så att det är lätt att visuellt identifiera om mönstret har följts eller inte. Jag skulle säga att det här är sällsynt, men det är inte ovanligt. Det finns en anledning att stänga XML-taggar upprepa taggnamnet (som < foo > och < / foo >).
Kommentarer
- För personer som inte ’ vet inte matematiken: Både 1 / x och sqrt (x) är ganska långsamma funktioner. Med hjälp av några smarta matematiker kan vi beräkna 1 / sqrt (x) snabbare än antingen en uppdelning eller en kvadratrot. Egentligen så mycket snabbare att det snabbaste sättet att beräkna sqrt (x) är att beräkna 1 / sqrt (x) med en smart implementering och multiplicera resultatet med x.
- Som i fysik, där elementära enheter inte längre är meter och andra, utan andra och ljusets hastighet, för vi kan mäta ljusets hastighet med mer precision än längden på en meter.
Svar
Vad är det ultimata avsikt av denna metod od? Varför lägga till dessa transformerade id: er i DB, är det för caching? Jag kommer att arbeta under det antagandet.
Det låter som att metodens syfte egentligen bara är att få de transformerade svars-id: n (antingen göra omvandlingen eller få dem från en cache), och så finns det ditt metodnamn:
getTransformedResponseIds
eller mindre ordligt getResponseIds()
En metod med detta namn kan cache eller kanske inte, och det kan dokumenteras, men ditt metodnamn borde inte knyta dig till en specifik implementering. Vad händer om du bestämmer dig för att sluta använda en DB och istället cache dem någon annanstans som bara tillfälligt i minnet?
Biverkningar borde vara just det, biverkningar, de borde antagligen dokumenteras men de borde inte påverka funktionens kärnintention (eller framgång) eller namn. Om hämtning av värdet från cachen misslyckas (eller om du konfigurerar att hoppa över cachning) som inte borde vara en kritisk fråga, ska metoden bara omberäkna, cache (eller inte) transparent och returnera det nya värdet.
Ibland är det bra att använda en ”AND” för att förmedla avsikter som med Java-metoderna getAndIncrement
och incrementAndGet
att ”är verkligen inte utanför bordet.
Svar
Du säger att funktionen returnerar något” från en transformation, inte från någon db-åtgärd ”.
Detta antyder att funktionen beter sig mer som en konstruktör än en getter. -konstruktörer borde dock inte orsaka biverkningar (tack för länken, @ MechMK1 ).
Fabriksmetoder, å andra sidan, förutsätter redan en viss nivå av rörighet . En av motivationerna för att använda fabriksmetoder är till exempel att en fabriksmetod:
Tillåter mer läsbar kod i fall där flera konstruktörer finns, var och en en annan anledning. – wikipedia
Likaså,
Om du behöver arbeta lite med biverkningar kan en fabriksmetod benämnas på ett sådant sätt att det är tydligare vad dessa biverkningar är. – ruakh
Överväg att göra funktionen till en fabriksmetod med termen ”inloggad” för att varna programmerare för databaslagring:
- Deklaration:
createLoggedResponseid(...) {...} // log object to db
- Samtal:
responseid = createLoggedResponseid(...);
Kommentarer
- Konstruktörer får inte orsaka biverkningar
- @ MechMK1 Jag ’ har redigerat för att ta bort konstruktör -förslaget och för att ge mer stöd för en lämpligt namngiven fabriksmetod. Låt mig veta vad du tänka.
persistResponseIds
?getStatusOfPost
🙂responseIds = createResponseIds()
verkar tydligt och koncist. Duget()
något som inte fanns ’ t före, såcreate
är rätt verb. De nyligen skapade grejerna är vad du ’ förväntar dig att encreate()
-funktion ska återvända. Eller kanske jag ’ saknar något självklart?