Sunt într-o situație dificilă. Lucrez într-un depozit care face o mulțime de modificări ale bazei de date în cadrul funcțiilor sale.

Funcția cu care mă ocup returnează răspunsuri (dar dintr-o transformare, nu dintr-o acțiune a bazei de date). Cu toate acestea, ca efect secundar, adaugă un obiect care conține acele răspuns ID-uri la baza de date.

Deci ar trebui să-l numesc:

  • getResponseIds: Aceasta evidențiază valorile returnate. Este un mod foarte funcțional de gândire, dar, evident, dacă am funcție postToDB nu are sens să folosesc getStatusOfPost
  • addResponseIdToDB: Acest lucru evidențiază efectul secundar, deși cred cu adevărat că multe dintre funcțiile mele funcționează doar pe baza de date (și tind să nu returneze nimic)
  • getAndAddResponseIdsToDB: Foarte informativ, dar foarte lung.

Care sunt avantajele și dezavantajele sugestiilor de mai sus? Sau puteți face dvs. o sugestie mai bună?

Comentarii

  • Ce zici de persistResponseIds?
  • S-ar putea să fiți surprinși de cât de mult a ajuns mulțimea funcțională cu echivalentul getStatusOfPost 🙂
  • getAndSaveResponseIds
  • responseIds = createResponseIds() pare clar și concis. get() ceva care nu ‘ nu exista înainte, deci create este verbul potrivit. Lucrurile nou create sunt ceea ce ați ‘ așteptați să revină o funcție create(). Sau poate ‘ îmi lipsește ceva evident?
  • @fattie Sunt de acord că codul ar trebui să fie auto-comentat. Comentariile nu trebuie să vă spună ce face codul. Comentariile ar trebui să vă spună de ce există codul. Dacă refactorizarea codului înseamnă refacerea comentariilor ‘, aveți comentarii proaste. Sunt de acord că numele pot fi fraze complete lungi. Un nume bun ar trebui să continue să caute în interiorul unei funcții pentru a nu te surprinde. Nu ar trebui să ‘ să vă spună cum să îl implementați. Ar trebui să vă spună la ce se așteaptă clientul. Faceți acest lucru și aveți o abstracție care merită.

Răspundeți

Un nume de funcție care conține se află la nivel greșit de abstractizare .

Mă aplec spre addResponseIdToDB() pentru că altfel ‘efectul secundar’ este o surpriză completă. Cu toate acestea:

responseIds = addResponseIdToDB(); 

nu lasă pe nimeni surprins.

principiul de separare a responsabilității interogării comenzii susține că aceasta nu ar trebui să fie singura modalitate de a obține obiectul responseId. De asemenea, ar trebui să existe o interogare care să nu schimbe baza de date care poate obține acest obiect.

Contrar lui Bertrand Meyer , nu cred că acest lucru înseamnă că adăugarea trebuie să revină nulă. Doar înseamnă că ar trebui să existe o interogare pură echivalentă și să fie ușor de găsit, astfel încât DB să nu fie abuzat inutil prin utilizarea interogărilor care schimbă starea.

Având în vedere că getResponseIds() ar trebui să existe și să nu vorbească cu baza de date, cel mai bun nume pentru o metodă care face ambele este de fapt addToDB(getResponseId()). Dar asta este doar dacă doriți să obțineți toate compozițiile funcționale.

Comentarii

  • Adăugând la cele spuse, dar adăugând un cuvânt care nu a fost folosit ‘, aș spune că ” și ” este adecvat atunci când o funcție face ceva care este atomic . Dacă există un beneficiu de obținut de la două lucruri care se întâmplă atomic / împreună / simultan, atunci fiind explicit cu ceea ce sunt aceste două lucruri în funcție numele este bun. Deci sunt de acord cu prima dvs. afirmație în general, dar nu sunt de acord fără nicio calificare.
  • … nu lasă pe nimeni surprins. „. Nu sunt de acord, am ‘ am rămas surprins că o variabilă booleană se numește responseIds și apoi trebuie să fac o schimbare mentală pentru a-mi da seama că adaugă și obține totul într-unul. Dar nu sunt de acord cu @tintintong că getAndAddResponseIdsToDB este prea lung; ‘ are aproximativ jumătate din lungimea multor nume ale funcțiilor mele. Dacă o funcție face două lucruri, spune-o în numele său.
  • @Luaan OK, cu ce vei redenumi getAndIncrement? otherIncrement?
  • @Luaan Deci, ce valoare returnează Increment, valoarea pre-increment sau post-increment? ‘ nu este clar care ar fi fără să vă scufundați în documente.Cred că incrementAndGet și getAndIncrement sunt nume de metode mult mai bune , ceea ce cred că este un caz bun pentru ” ȘI ” în numele metodelor, cel puțin în cazul specific al metodelor care au efecte secundare care ar putea avea un impact asupra valorii returnate ..
  • @Luaan Când spui ” Increment returnează valoarea incrementată ” vrei să spui valoarea pre-increment? Gotcha, te-am înțeles greșit la început .. ceea ce este de fapt un exemplu excelent de ce getAndIncrement și incrementAndGet sunt nume mai bune decât doar increment, chiar și atunci când este considerat individual

Răspuns

După cum au menționat alții, utilizarea and într-un nume de funcție implică automat că o funcție face cel puțin două lucruri, ceea ce indică de obicei că face prea mult sau că face ceva în locul greșit.

Utilizarea clauzei and într-un nume de funcție, totuși, din experiența mea, poate avea sens atunci când există câțiva pași într-un anumit flux care trebuie efectuate în ordine sau când întregul calcul devine semnificativ.

În calculul numeric, acest lucru poate avea mult sens:

normalizeAndInvert(Matrix m)

Adică, cine știe, nu? Dacă trebuie să calculezi câteva … să zicem, traiectorii de iluminare în grafică pe computer și la o anumită în etapă trebuie să normalizați și să inversați o anumită matrice și vă aflați în mod constant scriind:

m = normalize(m), urmat de invert(m), ca un simplu exemplu, introducând abstractizarea normalizării și inversării, poate fi bun din perspectiva lizibilității.

Vorbind semantic, lucruri precum divideAndConquer() etc, este probabil descurajat să fie scris în mod explicit, dar And este practic necesar.

Comentarii

  • Funcțiile fac în mod regulat mai mult de un lucru, dar acestea ar trebui să fie lucruri mici care se adună la un lucru mare pe care ‘ este semnificativ la un nivel superior. normalizeAndInvert ar putea fi computeLighting și divideAndConquer ar putea fi applyMachiavelli
  • Evident 🙂 Doar afirmând că mai întâi se creează o funcție cu un ” și ” în nume ar putea veni ca o abstractizare naturală bazată pe un anumit context. Refactorizarea prin ” Metoda de redenumire ” ar trebui să vină imediat după;)
  • @BrunoOliveira Cu excepția uneori, cei doi pași sunt ele însele o componentă a multor lucruri, redenumirea nu este ‘ t abordarea corectă. Și ar trebui să fie rar, dar niciodată să-l folosiți înseamnă că ‘ vă repetați.

Răspundeți

Aș spune că este bine în general, dar nu este acceptabil în toate cazurile.

De exemplu, s-ar putea argumenta împotriva and într-un nume de funcție bazat pe principiul responsabilității unice aka S în SOLID – deoarece and ar putea implica responsabilități multiple. Dacă and într-adevăr înseamnă că funcția face două lucruri, atunci probabil că doriți să vă gândiți cu atenție la ceea ce se întâmplă.

Un exemplu de bibliotecă care utilizează and în numele funcțiilor poate fi găsit în Java „s simultanitate și aici and este de fapt o parte foarte importantă a ceea ce se întâmplă și se potrivește îndeaproape cu ceea ce descrieți în postarea dvs. în care starea este modificată și starea este returnată. și unele cazuri de utilizare) în care este considerat acceptabil.

Comentarii

  • and ar putea implica multiple responsabilități „. Și în acest caz într-adevăr indică asta. Funcția primește câteva ID-uri de răspuns de undeva, le scrie într-o bază de date și le returnează. Necesitatea ca ” și ” ar trebui să ridice cel puțin ideea cu OP că sunt necesare două funcții: una pentru a obține ID-urile și unul pentru a le scrie în baza de date.
  • Deși sunt de acord că and este un miros de cod, comportamentul de bază pare legitim: în general nu este rău ideea de a returna unele valori suplimentare dintr-o metodă care se întâmplă a fi rezultatele necesare (intermediare) ale îndeplinirii funcției sale principale. În cazul în care funcția principală a acestei metode este de a adăuga ID-urile de răspuns la baza de date, returnarea suplimentară a ID-urilor pe care le-a adăugat apelantului este doar o caracteristică simplă de utilizare a metodei.
  • Nu cred ‘ nu cred că acest lucru încalcă în mod necesar SRP. Veți avea întotdeauna nevoie de funcții care să facă mai multe lucruri. Partea importantă este ca aceste funcții să apeleze la o altă funcție pentru fiecare lucru pe care doresc să îl facă în loc să conțină codul necesar direct.
  • Dacă funcția face două lucruri, atunci numele ar trebui să o reflecte.

Răspuns

Într-adevăr, „o întrebare dificilă de răspuns, atestând numeroase comentarii. Oamenii par să aibă opinii și sfaturi contradictorii despre ceea ce este un nume bun.

Aș dori să adaug 2 cenți la acest fir vechi de 2 luni, deoarece cred că pot adăuga mai multe culori la ceea ce a fost deja sugerat.

Denumirea este un proces

Toate acestea îmi amintesc de ghidul excelent în 6 pași: Denumirea ca proces .

Un nume de funcție slab este unul surprinzător și îți dai seama că nu poți avea încredere. Numirea bună este greu de obținut la prima lovitură. Devine mai ușor odată cu experiența.

6 pași iterativi pentru a construi un nume bun

  1. Înlocuiți numele surprinzător cu prostii evidente ca appleSauce(). Sună prostesc, dar este temporar și face evident că numele nu poate fi de încredere.
  2. Obțineți un nume onest , pe baza a ceea ce înțelegeți din ceea ce face funcția. Să presupunem că nu ați înțeles încă inserarea în partea DB, getResponseIdsAndProbablyUseDb ar fi o opțiune.
  3. Obțineți la complet onest , astfel încât numele funcției spune tot ceea ce face funcția (getAndAddResponseIdsToDB sau getResponsesButAlsoAddCacheOfTheResponsesToTheDb de la @Fattie sunt exemple grozave)
  4. Accesați „face ceea ce trebuie” care este în esență partea în care vă împărțiți funcția de-a lungul „ȘI”. Deci aveți de fapt getResponseIds și addResponseIdsToDb.
  5. Accesați un nume „Revelare a intenției” care rezolvă punctul „Vreau întotdeauna să obțin ID-ul de răspuns I inserat în DB după ce o fac „. Nu vă mai gândiți la detaliile implementării și construiți o abstractizare care utilizează cele mai mici 2 funcții pentru a construi altceva. Acesta este nivelul de abstractizare mai înalt menționat de @candied_orange. Pentru e xample createResponseIds ar putea face acest lucru și va fi compoziția getResponseIds și addResponseIdsToDb.
  6. Accesați abstractizarea domeniului dvs. . Este greu, depinde de afacerea ta. Este nevoie de timp pentru a vă asculta limba de afaceri pentru a vă corecta. În cele din urmă, puteți ajunge la conceptul de Response și Response.createIds() ar avea sens. Sau poate ResponseId este ceva valoros și ai avea o fabrică pentru a crea multe. Inserarea în DB ar fi un detaliu de implementare a unui depozit, etc. Da, designul ar fi mai abstract. Ar fi mai bogat și vă va permite să exprimați mai mult. Nimeni din afara echipei dvs. nu vă poate spune care ar trebui să fie abstractizarea corectă a domeniului în situația dvs. Depinde .

În cazul dvs., ați aflat deja 1 și 2, deci probabil ar trebui să mergeți cel puțin la 3 (utilizați „ȘI”) sau mai departe. Dar a merge mai departe nu este doar o chestiune de numire, ați fi împărțit de fapt responsabilitățile.

Astfel, diferitele sugestii de aici sunt valabile

În esență:

  • Da, numele funcțiilor surprinzătoare sunt teribile și nimeni nu vrea să se ocupe de asta
  • Da, „ȘI” este acceptabil într-un nume de funcție, deoarece este „mai bun decât un nume înșelător
  • Da, nivelul de abstractizare este un concept înrudit și contează

Nu trebuie să mergeți imediat la etapa 6, este bine să stați la etapa 2, 3 sau 4 până când știți mai bine!


Sper că acest lucru ar fi util pentru a oferi o perspectivă diferită asupra a ceea ce pare a sfătui în contradicție. Cred că toată lumea își propune același obiectiv ( nume care nu sunt surprinzătoare), dar opriți-vă în diferite etape, pe baza propriei experiențe.

Cu plăcere vă răspund dacă aveți întrebări 🙂

Răspuns

Funcția dvs. face două lucruri:

  1. Obține un set de ID-uri printr-o transformare și le returnează apelantului,
  2. Scrie acel set de ID-uri într-o bază de date.

Amintiți-vă aici principiul unic al responsabilității: ar trebui să vizați o funcție să aibă o responsabilitate, nu două. Aveți două responsabilități, deci aveți două funcții:

getResponseIds – Obține un set de ID-uri printr-o transformare și le returnează apelantului
addResponseIdToDB – preia un set de ID-uri și le scrie într-o bază de date.

Și întreaga problemă a ceea ce se numește o singură funcție cu responsabilități multiple dispare și tentația de a pune and în numele funcțiilor dispare și ea.

Ca bonus suplimentar, deoarece aceeași funcție nu mai este responsabilă pentru două acțiuni fără legătură, getResponseIds ar putea fi mutat din codul dvs. de repo (unde nu aparține) deoarece nu face nicio activitate legată de DB), într-o clasă / modul separat la nivel de business etc.

Comentarii

  • Că ‘ este cu siguranță adevărat, dar vă aflați că repetați un fragment folosind aceste două metode SRP de mai multe ori, atunci probabil ar trebui să scrieți o metodă banală făcând acest lucru pentru a vă usca, nu ar trebui să ‘ nu ești?
  • @maaartinus, dacă te găsești apelând aceste două metode în multe locuri, atunci ai probabil o problemă cu o lipsă de coeziune în cod. Crearea unei funcții ” DRY ” apoi maschează această lipsă de coeziune.

Răspuns

Este acceptabil să aveți un nume de funcție compusă, astfel încât schema de denumire utilizată depinde de contextul dvs. Există puriști care vă vor spune să o despărțiți, dar voi argumenta contrariul.

Există două motive pentru a încerca să evitați astfel de lucruri:

  • Puritate – O funcție care face două lucruri ar trebui convertit în două funcții care fac câte un lucru fiecare.
  • Dimensiune lexicală – un bigUglyCompositeFunctionName devine greu de citit.

Argumentul purității este de obicei cel pe care este axat. Ca o euristică generală vagă, separarea funcțiilor este un lucru bun. Ajută la compartimentarea a ceea ce se întâmplă, facilitând înțelegerea. Este mai puțin probabil să faceți trucuri „inteligente” cu partajarea variabilelor care duc la cuplarea între cele două comportamente.

Deci, chestia de întrebat este „ar trebui să o fac oricum?” Spun că despărțirea lucrurilor este o euristică vagă, așa că într-adevăr aveți nevoie doar de un argument decent pentru a vă împotrivi.

Un motiv principal este că este benefic să vă gândiți la cele două operații ca la una. Exemplul extrem de final este găsit în operațiile atomice: compare_and_set. compare_and_set este o piatră de temelie fundamentală a atomicii (care sunt o modalitate de a face multi-threading fără blocări), care este suficient de reușită încât mulți sunt dispuși să o considere ca the piatră de temelie. Există „un” și „în acest nume. Există un motiv foarte bun pentru asta. Întregul punct al acestei operații este că face comparația și setarea ca o operație indivizibilă. Dacă ați divizat-o în compare() și set(), ați învinge întregul motiv pentru care funcția a existat în primul rând, deoarece două compares() ar putea apărea spate în spate înainte de set() s.

De asemenea, vedem acest lucru în performanță. Exemplul meu preferat este fastInvSqrt . Acesta este un celebru algoritm de la Quake care calculează 1 / sqrt (x). Combinăm operațiunile „inversă” și „rădăcină pătrată”, deoarece acest lucru oferă ne face o creștere masivă a performanței. Ei fac o aproximare a lui Newton, care face ambele operații într-un singur pas (și se întâmplă să o facă în matematică întreagă, mai degrabă decât în virgulă mobilă, ceea ce contează în epocă). Dacă ar fi să faci inverse(sqrt(x)), ar fi mult mai lent!

Există unele momente în care rezultatul este mai clar. Am scris mai multe API-uri care implică threading în care trebuie să fii îngrozitor de atent la lucruri precum blocaje. Nu doream ca utilizatorul meu să fie conștient de detaliile interne de implementare (mai ales că aș putea să le schimb), așa că am scris API-ul cu câteva funcții „și” care au împiedicat utilizatorul să fie nevoit să țină vreodată o blocare pentru mai multe apeluri de funcții. Asta înseamnă că nu au avut nevoie niciodată să știe cum gestionam sincronizarea cu mai multe fire sub capotă. De fapt, majoritatea utilizatorilor mei nici măcar nu știau că se află într-un mediu cu mai multe fire!

Deci, deși regula generală este de a sparge lucrurile, poate exista întotdeauna un motiv pentru a le asocia. De exemplu, , poate utilizatorul dvs. să vă rupă arhitectura adăugând la o bază de date de mai multe ori fără „obține” între ele? Dacă fiecare dintre răspunsurile înregistrate în DB trebuie să aibă un identificator unic, ați putea avea probleme dacă utilizatorul ar trebui să le facă voit În mod similar, ați dori vreodată să lăsați un utilizator să „obțină” fără a înregistra rezultatele în DB? Dacă răspunsul este „da”, rupeți-le astfel încât utilizatorul să poată accesa funcția „obțineți”. Cu toate acestea, dacă securitatea aplicației dvs. este întreruptă dacă utilizatorul poate „obține” fără ca rezultatul să fie conectat în DB, ar trebui să păstrați funcțiile împreună.

Acum, în ceea ce privește problemele lexicale, numele funcției dvs. ar trebui să descrie ce utilizatorul trebuie să știe despre asta. Să începem cu partea „obține”.Este destul de ușor să eliminați „obțineți” din numele unei funcții, deoarece toate exemplele dvs. vor afișa o atribuire: int id = addResponseIdToDB() „. În toate locurile în care funcția este utilizată, veți termina documentând faptul că funcția a returnat o valoare.

La fel „adăugare” poate fi opțională. Folosim termenul „efect secundar” ca un termen cuprinzător, dar există diferite nuanțe ale acestuia. Dacă intrările DB sunt doar un jurnal, este posibil să nu existe niciun motiv pentru ao evidenția. Nu vedem niciodată funcții precum playMusicAndSendPersonalInformationToOurCorporateServers. Este doar playMusic și, dacă aveți noroc, documentația poate menționa ceva despre o conexiune socket pe internet. Pe de altă parte, dacă se așteaptă ca utilizatorii să apeleze această funcție în scopul adăugării de lucruri la baza de date, atunci „adăugarea” este esențială. Nu-l scoateți.

Acum, am scris o mulțime de motive pentru a face fiecare combinație posibilă a ceea ce cereți. Nu am răspuns în mod intenționat la întrebare, deoarece există o alegere de făcut. Nu este o regulă de urmat. Creați un API.

Acestea fiind spuse, instinctul meu este că addResponseIdToDB este probabil cel mai bun răspuns. În majoritatea cazurilor, porțiunea „obțineți” este un efect secundar suficient de evident încât să nu câștige zgomotul suplimentar cauzat de tastarea acestuia peste tot. Cu toate acestea, există câteva locuri în care aș putea considera că este important:

  • Dacă „obțineți” este scump – Dacă trebuie să faceți o „obținere” care implică preluarea ceva pe internet și apoi adăugarea acestuia la o bază de date cache locală, cu siguranță „obținerea” este importantă. Este lucrul pe care utilizatorul încearcă să îl facă.
  • Dacă trebuie să clarificați faptul că utilizatorul are nevoie să acorde atenție valorii. Dacă utilizatorul dorește să utilizeze variabila, își va da seama că API-ul o returnează și o va folosi. Cu toate acestea, doriți să aveți grijă de utilizatorul care nu știe că au nevoie de ea. De exemplu, dacă trebuie să „închideți” această operațiune prin ID mai târziu pentru a elibera memoria, vă recomandăm să atrageți atenția asupra faptului că faci ceva. În aceste cazuri, s-ar putea să doriți să priviți un alt verb decât „obțineți”. „obține” implică deseori funcții idempotente (apelarea din nou nu face nimic). Dacă returnează resurse, alte verbe precum „creați” sau „dobândiți” sunt frumoase. Acest mecanism special de eșec este o problemă majoră în C atunci când faceți manipularea excepțiilor . C nu are mecanismul de captare / aruncare al C ++, deci se bazează pe coduri de returnare. Dezvoltatorii sunt celebri pentru că pur și simplu nu verifică aceste coduri de returnare și intră în situații foarte proaste, cum ar fi depășirea bufferului din cauza acestuia.
  • Simetrie – Uneori proiectați un API pentru a avea o simetrie lexicală. Configurați cuvintele astfel încât să vină în perechi sau alte tipare și creați comenzile astfel încât să fie ușor de identificat vizual dacă modelul a fost urmat sau nu. Aș spune că acest lucru este rar, dar nu este nemaiauzit. Există „motivul pentru care închiderea etichetelor XML repetă numele etichetei (cum ar fi < foo > și < / foo >).

Comentarii

  • Pentru persoanele care nu au ‘ nu cunoașteți matematica: Atât 1 / x cât și sqrt (x) sunt funcții destul de lente. Folosind unele matematici inteligente, putem calcula 1 / sqrt (x) mai rapid decât o diviziune sau o rădăcină pătrată. De fapt, atât de rapid, încât cel mai rapid mod de a calcula sqrt (x) este de a calcula 1 / sqrt (x) cu o implementare inteligentă și de a înmulți rezultatul cu x.
  • La fel ca în fizică, unde unitățile elementare nu mai sunt metru și al doilea, ci al doilea și viteza luminii, deoarece putem măsura viteza luminii cu mai multă precizie decât lungimea unui metru.

Răspuns

Care este ultimul intent al acestei metanfe od? De ce să adăugați aceste ID-uri transformate în DB, este în scopul memorării în cache? Voi lucra sub această ipoteză.

Se pare că intenția metodei este cu adevărat doar să obțin ID-urile de răspuns transformate (fie făcând transformarea, fie obținându-le dintr-o memorie cache), și așa există numele metodei dvs.:

getTransformedResponseIds sau mai puțin verbos getResponseIds()

O metodă cu acest nume ar putea cache sau poate nu, și acest lucru poate fi documentat, dar numele metodei dvs. nu ar trebui să vă lege de o implementare specifică; Ce se întâmplă dacă decideți să nu mai utilizați un DB și să le cache în altă parte, cum ar fi doar temporar în memorie?

Efectele secundare ar trebui să fie doar că, efectele secundare, ar trebui probabil să fie documentate, dar nu ar trebui să aibă un impact asupra intenției de bază (sau succesului) sau asupra numelui funcției. Dacă obținerea valorii din cache nu reușește (sau dacă configurați să omiteți cache-ul) care nu ar trebui să fie o problemă critică, metoda ar trebui să recalculeze în mod transparent, să cache (sau nu) și să returneze noua valoare.

Uneori, utilizarea unui „ȘI” este utilă pentru a transmite intenții, cum ar fi cu metodele Java getAndIncrement și incrementAndGet că „cu siguranță nu este în afara tabelului.

Răspuns

Spuneți că funcția returnează ceva” dintr-o transformare, nu din orice acțiune db „.

Acest lucru sugerează că funcția se comportă mai mult ca un constructor decât ca un getter. Cu toate acestea, constructorii nu ar trebui, de asemenea, să provoace efecte secundare (mulțumesc pentru link, @ MechMK1 ).

Metodele din fabrică, pe de altă parte, presupun deja un anumit nivel de dezordine . De exemplu, una dintre motivațiile pentru utilizarea metodelor din fabrică este aceea că o metodă din fabrică:

Permite coduri mai lizibile în cazurile în care există mai mulți constructori, fiecare pentru un motiv diferit. – wikipedia

La fel,

Dacă trebuie să lucrați cu efecte secundare, o metodă din fabrică poate fi numită astfel încât să fie mai clar care sunt acele efecte secundare. – ruakh

Luați în considerare transformarea funcției într-o metodă din fabrică, folosind termenul „înregistrat” pentru a alerta programatorii cu privire la stocarea bazei de date:

  • Declarație: createLoggedResponseid(...) {...} // log object to db
  • Apel: responseid = createLoggedResponseid(...);

Comentarii

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *