Am văzut o practică din când în când că "simte" gresit, dar nu pot't destul de bine articulat ce este în neregulă cu el. Sau poate l's doar prejudecățile. Aici merge:
Un dezvoltator definește o metodă cu un boolean ca unul dintre parametrii săi, și această metodă solicită un alt, și așa mai departe, și în cele din urmă că boolean este folosit doar pentru a determina dacă sau nu să ia o anumită acțiune. Acest lucru ar putea fi folosit, de exemplu, pentru a permite o acțiune numai dacă utilizatorul are anumite drepturi, sau, poate, dacă suntem (sau nu't) în modul de testare sau în modul de lot sau modul live, sau poate doar atunci când sistemul este într-un anumit stat.
Ei bine, există întotdeauna un alt mod de a o face, fie prin interogarea atunci când este timpul să ia măsuri (mai degrabă decât trecerea parametru), sau de a avea mai multe versiuni de metoda, sau mai multe implementări de clasa, etc. Intrebarea mea este't atât de mult modul de a îmbunătăți acest lucru, ci mai degrabă dacă este sau nu este într-adevăr greșit (cum banuiesc), și dacă este, ce este în neregulă cu el.
M-am oprit folosind acest model o lungă perioadă de timp în urmă, pentru un motiv foarte simplu; costurile de întreținere. De mai multe ori am constatat că am avut o funcție spun frobnicate(ceva, forwards_flag)
care a fost numit de mai multe ori în codul meu, și este necesar pentru a localiza toate locurile din cod în care valoarea "false" a fost adoptată ca valoare de forwards_flag`. Puteți't căuta cu ușurință pentru cei, astfel încât aceasta devine o problemă de întreținere. Și dacă aveți nevoie pentru a face un bugfix la fiecare dintre aceste site-uri, este posibil să aveți o problemă nefericită dacă ți-e dor unul.
Dar această problemă specifică este ușor de stabilit fără a modifica în mod fundamental de abordare:
enum FrobnicationDirection {
FrobnicateForwards,
FrobnicateBackwards;
};
void frobnicate(Object what, FrobnicationDirection direction);
Cu acest cod, ar fi nevoie doar pentru a căuta cazuri de FrobnicateBackwards
. În timp ce-l's posibil nu există un cod care atribuie acest lucru la o variabilă de aceea trebuie să urmați câteva fire de control, am găsi, în practică, acest lucru este destul de rar ca această alternativă funcționează OK.
Cu toate acestea, există o altă problemă cu care trece de pavilion în acest fel, cel puțin în principiu. Acest lucru este faptul că unele (doar unele) sisteme cu acest design poate fi expunerea prea multe cunoștințe despre detaliile de implementare profund imbricate părți ale codului (care folosește flag) pentru straturile exterioare (care trebuie să știe ce valoare a trece în acest flag). Pentru a utiliza Larry Constantine's terminologie, acest design poate fi supra-puternică de cuplare între setter și utilizatorul boolean flag. Franky, deși l's greu de spus cu certitudine la această întrebare fără să știe mai multe despre codebase.
Pentru a aborda anumite exemple pe care le da, mi-ar fi de un anumit grad de îngrijorare în fiecare, dar în principal pentru motive de risc/corectitudinea. Asta este, dacă sistemul dumneavoastră are nevoie pentru a trece în jurul valorii de steaguri care indică în ce stare a sistemului este, s-ar putea găsi pe care le'am codul care ar trebui să țină cont de acest lucru, dar nu't verificați parametrul (pentru că nu a fost trecut la această funcție). Deci, aveți un bug pentru că cineva a omis să treacă parametru.
L's, de asemenea, în valoare de admițând că un sistem de stat-indicator care trebuie să fie trecut la aproape fiecare funcție este, de fapt, în esență, o variabilă globală. Multe dintre dezavantajele unei variabile globale se vor aplica. Cred că, în multe cazuri, este o practică bună pentru a îngloba cunoștințe a sistemului de stat (sau de utilizator's de acreditare, sau sistemul's de identitate) în termen de un obiect care este responsabil pentru a acționa corect în baza de date. Apoi trece în jurul valorii de o referință la acest obiect, spre deosebire de datele primare. Conceptul cheie aici este encapsulation.
Da, acest lucru este probabil un cod de miros, ceea ce ar duce la unmaintainable cod care este dificil de înțeles și că are o șansă mai mică de a fi cu ușurință re-utilizate.
Ca și alte postere au remarcat contextul este totul (don't merge în forță dacă-l's un caz izolat sau dacă practică a fost recunoscută ca fiind suportate în mod deliberat tehnice datoria de a fi re-luat mai târziu), dar în general, dacă nu există un parametru a trecut într-o funcție care selectează un anumit comportament să fie executat apoi pas înțelept rafinament este necesar; Rupere în sus această funcție în funcții mai mici vor produce mai multe foarte coeziv altele.
Deci, ce este un foarte coeziv funcție?
L's o funcție care face un singur lucru și numai un singur lucru.
Problema cu un parametru a trecut la cum ai descris-o, este că funcția este de a face mai mult decât două lucruri; acesta poate sau nu poate verifica utilizatorilor drepturi de acces în funcție de starea de parametru Boolean, apoi, în funcție de această decizie copac se va efectua o bucata de funcționalitate.
Ar fi mai bine să se separe preocupările de Control Acces de la preocupările de Sarcină, o Acțiune sau o Comandă.
După cum ați observat deja, împletirea dintre aceste preocupări se pare ciudat.
Deci, noțiunea de Coerență ne ajută să identificăm că funcția în cauză nu este foarte coeziv și că am putea refactor codul pentru a produce un set de mai mult de coeziune funcții.
Deci, întrebarea ar putea fi reformulată; Având în vedere că suntem cu toții de acord trecerea comportamentale parametrii de selecție este cel mai bine evitate, cum putem îmbunătăți lucrurile?
Mi-ar scăpa de parametrul complet. Având capacitatea de a opri de control acces chiar și pentru testarea este un potențial risc de securitate. Pentru scopuri de testare, fie [tag:stub] sau [tag:mock] accesul verificați pentru a testa atât accesul este permis și accesul interzis scenarii.
Acest lucru nu este neapărat greșit, dar el poate reprezenta o cod miros.
Scenariul de bază care ar trebui să fie evitate cu privire boolean parametri este:
public void foo(boolean flag) {
doThis();
if (flag)
doThat();
}
Atunci când te sună'd numesc de obicei foo(false) " și " foo(adevărat)
în funcție de comportamentul exact ce vrei.
Aceasta este într-adevăr o problemă, pentru că's un caz de rău de coeziune. Te're crearea unei dependențe între metodele de care nu este cu adevărat necesar.
Ce ar trebui să faci în acest caz este de a pleca `asta " și " fac așa ca separate și metode publice apoi face:
doThis();
doThat();
sau
doThis();
Așa că lăsați corectă decizia de a apelantului (exact ca și cum ai trece un parametru boolean) fără a crea cuplare.
Desigur, nu toate boolean parametri sunt utilizate în astfel de modalitate de rău, dar's cu siguranta un cod de miros și te're dreptul de a deveni suspicios dacă ai vedea că o mulțime în codul sursă.
Acesta este doar un exemplu de cum să rezolve această problemă pe baza exemplelor am scris. Există și alte cazuri în care o abordare diferită va fi necesar.
Există o bun art. de la Martin Fowler explicând în continuare detalii aceeași idee.
PS: dacă metoda de " foo "în loc de asteptare două metode simple avut un grad mai complex de aplicare, atunci tot ce trebuie sa faci este sa aplici un mic refactoring extragerea metode deci, codul rezultat arată similar cu punerea în aplicare a" foo " pe care am scris-o.
În primul rând: programare nu este o știință atât de mult ca este o arta. Deci nu este foarte rar un "prost" și "dreapta" cum să program. Cele mai codificare-standardele sunt doar "preferințe" că unii programatori consideră utile; dar în cele din urmă sunt mai degrabă arbitrare. Așa că n-ar eticheta-o gamă de parametru pentru a fi "prost", în și de la sine-și cu siguranță nu atât de generic și util ca un parametru boolean. Utilizarea unui "boolean" (sau o int
, pentru care contează) pentru a îngloba stat este perfect justificată în multe cazuri.
Codificare decizii, de & mare, ar trebui să se bazeze în primul rând pe performanță și întreținere. Daca performanta e't în joc (și am putea't imaginați-vă cum ar putea fi vreodată în exemple), atunci următoarea ar trebui să fie: cât de ușor va fi pentru mine (sau un viitor redactor) să-și mențină? Este intuitiv și ușor de înțeles? Este izolat? Exemplul tau legat solicită funcția nu, de fapt, părea potențial fragile în acest sens: dacă vă decideți să schimbați bIsUp " la " bIsDown
, cum multe alte locuri din codul va trebui să fie schimbat? De asemenea, este paramater lista cu balonul? Dacă funcția dumneavoastră a 17 parametri, atunci lizibilitatea este o problemă, și trebuie să-și reconsidere dacă sunteți apreciind beneficiile orientat-obiect de arhitectură.
Cred că Robert C Martins cod Curat articol prevede că tu ar trebui să elimine boolean argumentele în cazul în care este posibil ca ele arată o metodă face mai mult decât un singur lucru. O metodă ar trebui să facă un singur lucru și numai un singur lucru cred că este unul dintre motto-urile.
Cred că cel mai important lucru aici este de a fi practic.
Când boolean determină întregul comportament, doar face o a doua metodă.
Când boolean determină doar un pic de comportament în mijloc, ar putea să doriți să-l păstrați într-un singur pentru a reduce duplicarea de cod. În cazul în care este posibil, ar putea fi chiar posibilitatea de a împărți metodă în trei: Două metode de asteptare pentru boolean opțiune, și una care are cel mai mult de lucru.
De exemplu:
private void FooInternal(bool flag)
{
//do work
}
public void Foo1()
{
FooInternal(true);
}
public void Foo2()
{
FooInternal(false);
}
Desigur, în practică,'ll au întotdeauna un punct între aceste extreme. De obicei, eu merg doar cu ce se simte bine, dar eu prefer să greșească pe partea de mai puțin de duplicare de cod.
Îmi place abordarea de personalizarea comportamentului prin constructor metode care returnează imuabil cazuri. Aici'cum s Guava Splitter
se folosește:
private static final Splitter MY_SPLITTER = Splitter.on(',')
.trimResults()
.omitEmptyStrings();
MY_SPLITTER.split("one,,,, two,three");
Beneficiile de acest lucru sunt:
Splitter
. Te'd pus niciodată someVaguelyStringRelatedOperation(ListaSplitter
, dar'd gândi pune ca o metodă statică într-un `StringUtils clasa.TL;DR: nu't folosi boolean argumente.
Vezi mai jos ce ei sunt rele, și cum să le înlocuiască (cu aldine).
Boolean argumentele sunt foarte greu de citit, și, astfel, dificil să se mențină. Problema principală este că scopul este clar, în general, atunci când ai citit semnătura metodă în cazul în care argumentul este numit. Cu toate acestea, numirea unui parametru este, în general, nu este necesar în cele mai multe limbi. Așa că va trebui anti-modele, cum ar fi [RSACryptoServiceProvider#cripta(Byte[], Boolean)
](https://msdn.microsoft.com/en-us/library/f17a0e2k(v=vs. 110).aspx) în cazul în care parametru boolean determină ce fel de criptare este de a fi utilizate în funcție.
Deci, veți obține un apel de genul:
rsaProvider.encrypt(data, true);
în cazul în care cititorul trebuie să căutare metoda's semnătură pur și simplu pentru a determina ce dracu adevărat
ar putea de fapt să spun. Trecerea unui număr întreg este, desigur, la fel de rău:
rsaProvider.encrypt(data, 1);
aș spune doar cât de mult - sau, mai degrabă: doar cât de puțin. Chiar dacă ai defini constante pentru a fi utilizate pentru întreg atunci utilizatorii din funcție poate pur și simplu ignora-le și păstrați folosind valori literale.
Cel mai bun mod de a rezolva acest lucru este de a utiliza un enumerare. Dacă trebuie să treci un enum RSAPadding cu două valori:
OAEP " sau " PKCS1_V1_5` atunci ar fi imediat capabil să citească codul:
rsaProvider.encrypt(data, RSAPadding.OAEP);
Booleans poate avea doar două valori. Acest lucru înseamnă că, dacă aveți o a treia opțiune, apoi'd trebuie să refactor semnătura ta. În general, acest lucru nu poate fi realizată cu ușurință dacă compatibilitate este o problemă, așa că'd trebuie să se extindă orice public class cu un alt public. Aceasta este ceea ce Microsoft a făcut în cele din urmă, când au introdus [RSACryptoServiceProvider#cripta(Byte[], RSAEncryptionPadding)
](https://msdn.microsoft.com/en-us/library/mt132683(v=vs. 110).aspx) în cazul în care au folosit-o enumerare (sau cel puțin o clasă mimand o enumerare) în loc de un boolean.
Acesta poate fi chiar mai ușor de a utiliza o full obiect sau interfata ca parametru, în cazul în care parametrul de sine trebuie să fie parametrizate. În exemplul de mai sus OAEP umplutură în sine ar putea fi parametrizate cu valoarea hash pentru a utiliza pe plan intern. Rețineți că există acum 6 SHA-2 algoritmi de hash și 4 SHA-3 algoritmi de hash, astfel încât numărul de enumerare valori pot exploda dacă utilizați doar un singur enumerare, mai degrabă decât de parametri (acest lucru este, probabil, urmatorul lucru pe care Microsoft este de gând să aflu).
Boolean parametri pot indica, de asemenea, că metoda sau clasa nu este proiectat bine. Ca exemplul de mai sus: orice criptografice bibliotecă, altele decât .NET unu't utiliza o umplutură de pavilion în metoda de semnătură, la toate.
Aproape toate software-ul guru care îmi place să avertizeze împotriva boolean argumente. De exemplu, Joshua Bloch avertizează împotriva lor în foarte apreciat cartea "Java". În general, ele nu ar trebui folosite. Ai putea argumenta că acestea ar putea fi folosite în cazul în care există un parametru care este ușor de înțeles. Dar chiar și atunci: Bit.set(boolean) este, probabil, mai bine implementat folosind **două metode**:
Bit.set () " și " Bit.unset()`.
Dacă nu puteți direct refactor codul ai putea definirea constantelor la cel puțin a le face mai ușor de citit:
const boolean ENCRYPT = true;
const boolean DECRYPT = false;
...
cipher.init(key, ENCRYPT);
este mult mai ușor de citit decât:
cipher.init(key, true);
chiar daca'd mai degrabă:
cipher.initForEncryption(key);
cipher.initForDecryption(key);
în schimb.
Cu siguranta o cod miros. Dacă nu't încalcă Singur Responsabilitatea Principiu atunci probabil că încalcă Spune, Don't Cere. Ia în considerare:
Dacă se dovedește a nu încălca una dintre aceste două principii, trebuie să utilizați un enum. Boolean flag-urile sunt cele booleene echivalente de numere de magie. foo(false)
face la fel de mult sens ca bar(42)
. Enum poate fi util pentru Strategia Model și au flexibilitatea de a permițându-vă să adăugați o altă strategie. (Amintiți-vă doar să - nume-le în mod corespunzător.)
Dvs. de exemplu particular mai ales ma deranjeaza. De ce este acest steag a trecut prin atât de multe metode? Acest lucru se pare ca ai nevoie de a împărți parametru în subclase.
Am'm-a surprins nimeni nu a menționat nume-parametri.
Problema, cred eu, cu boolean-steaguri este că au rănit lizibilitate. De exemplu, ce înseamnă "adevărat" în
myObject.UpdateTimestamps(true);
face? Nu am nici o idee. Dar ceea ce despre:
myObject.UpdateTimestamps(saveChanges: true);
Acum's clar ce parametrul am're de trecere este menit sa faca: am're spus funcție pentru a salva modificările. În acest caz, în cazul în care clasa este non-publice, cred ca parametru boolean este bine.
Desigur, puteți't forța utilizatorii de clasa a folosi nume-parametrii. Pentru acest motiv, nici un enum
sau două metode sunt, de obicei, de preferat, în funcție de cât de comun implicit caz este. Acest lucru este exact de ce .Net face:
//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);
//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key);
Sunt de acord cu toate preocupările folosind Boolean Parametri pentru a determina performanța în scopul de a ; de a îmbunătăți, de Lizibilitate, de Fiabilitate, reducerea Complexității, Scăderea riscurilor de săraci Încapsulare & Coeziune și mai mic Cost Total de Proprietate cu Mentenabilitatea.
Am început proiectarea hardware de la mijlocul anilor 70's pe care o numim acum SCADA (supervisory control and data acquisition), iar acestea au fost reglat fin hardware cu cod mașină în EPROM rularea macro-control de la distanță și colectarea de date de mare viteză.
Logica este numit Mealey & Moore mașini pe care noi o numim acum Finite state Machines. Acestea, de asemenea, trebuie să se facă în aceleași reguli ca mai sus, cu excepția cazului în care este o adevărată mașină a timpului cu un anumit timp de executie si apoi comenzi rapide trebuie să fie făcut pentru a servi scopului.
Datele sunt sincrone, dar comenzile sunt asincrone și logica de comandă urmează memoryless logica Booleană, dar cu comenzi secvențiale, bazate pe memorie anterioare, prezente și dorite de următorul stat. Pentru ca asta să funcționeze în cel mai eficient limbajul mașină (doar 64 kb), mare grijă a fost luată pentru a defini fiecare proces într-un euristică IBM HIPO moda. Că, uneori, a însemnat trecerea variabile Booleene și de a face indexate ramuri.
Dar acum cu o mulțime de memorie și ușurința de OOK, Încapsularea este un ingredient esențial astăzi, dar o sancțiune atunci când codul a fost numărat în octeți pentru timp real si SCADA cod mașină..
nu pot't destul de bine articulat ce este în neregulă cu el.
Dacă arată ca un cod de miros, se simte ca un cod de miros și bine miroase ca un cod de miros, l's, probabil, un cod de miros.
Ce vrei să faci este:
Evita metodele cu efecte secundare.
Mâner necesar statelor cu o centrală, formale stat-mașină (ca aceasta).
Nu neapărat greșit, dar în exemplu concret de acțiune în funcție de un atribut al "de utilizare" mi-ar trece printr-o referință pentru utilizator, mai degrabă decât un steag.
Aceasta clarifică și ajută într-un număr de moduri.
Oricine a citit invocarea declarație veți da seama că rezultatul se va schimba în funcție de utilizator.
În funcție de ceea ce este în cele din urmă numit puteți pune în aplicare cu ușurință de afaceri mai complexe reguli, pentru că puteți accesa oricare dintre utilizatorii de atribute.
Dacă o funcție/metodă în "lanț" face ceva diferit în funcție de un atribut de utilizator, este foarte probabil că un anunț similar dependenței de atribute de utilizator va fi introdus la unele dintre celelalte metode în "lanț".
Cele mai multe din timp, nu ar considera acest lucru rea de codificare. Nu mă pot gândi de două cazuri, cu toate acestea, în cazul în care acest lucru ar putea fi o bună practică. Deoarece există o mulțime de răspunsuri deja spun de ce-l's prost, am oferi de două ori atunci când ar putea fi bun:
Prima este dacă fiecare apel în secvență logică în dreptul său propriu. Aceasta ar avea sens dacă codul de asteptare might fi schimbat de la true la false sau fals la adevărat, sau dacă numita metodă might fi modificate pentru a utiliza parametru boolean mod direct, mai degrabă decât trece-l pe. Probabilitatea de zece astfel de apeluri într-un rând este mic, dar s-ar putea întâmpla, și dacă s-ar fi bună practică de programare.
Cel de-al doilea caz este un pic de o intindere având în vedere că ne-am're de-a face cu un boolean. Dar dacă programul are mai multe fire sau evenimente, transmiterea parametrilor este cel mai simplu mod de a urmări firul/eveniment de date specifice. De exemplu, un program poate obține de intrare de la două sau mai multe prize. Cod de funcționare pentru un socket ar putea avea nevoie pentru a produce mesaje de avertizare, și candidează pentru un altul poate nu. Atunci (un fel de) are sens pentru un boolean stabilite la un nivel foarte ridicat pentru a obține trecut prin mai multe apeluri de metode la locurile unde mesajele de avertizare ar putea fi generate. Datele pot't fi salvat (cu excepția cu mare dificultate) în orice fel de global pentru mai multe fire sau intercalat evenimente vor avea nevoie de propria lor valoare.
Pentru a fi sigur că, în acest din urmă caz, am'd probabil a crea o clasă/structură al cărei conținut doar a fost un boolean și să treacă that în jurul loc. Am'd fi aproape sigur de a avea nevoie de alte domenii, înainte de prea mult timp ... ca where pentru a trimite mesaje de avertizare, de exemplu.
Contextul este important. Astfel de metode sunt destul de frecvente în iOS. Ca doar unul des folosit exemplu, UINavigationController oferă metoda -pushViewController:animat:
, și "animat" parametru este un BOOL. Metoda îndeplinește, în esență, aceeași funcție, fie un fel, dar se animă trecerea de la un controler de vedere la altul dacă treci într-DA, și nu't dacă treci NR. Acest lucru pare în întregime rezonabil; it'ar fi o prostie să aibă de a oferi două metode în loc de asta doar pentru a putea determina dacă este sau nu de a utiliza animație.
Acesta poate fi mai ușor pentru a justifica acest fel de lucru în Objective-C, în cazul în care metoda de denumire a sintaxa oferă mai mult context pentru fiecare parametru decât în limbaje cum ar fi C și Java. Cu toate acestea, am'd cred că o metodă care are un singur parametru ar putea lua cu ușurință un boolean și încă mai face sens:
file.saveWithEncryption(false); // is there any doubt about the meaning of 'false' here?