Commento corretto da inserire per argomenti della funzione booleana che sono "falsi"?


19

Da alcuni progetti open source, ho raccolto il seguente stile di codifica

void someFunction(bool forget);

void ourFunction() {
  someFunction(false /* forget */);
}    

Ho sempre dei dubbi su cosa falsesignifichi qui. Significa "dimenticare", oppure "dimenticare" si riferisce al parametro corrispondente (come nel caso sopra), e "falso" intende negarlo?

Quale stile viene utilizzato più spesso e qual è il modo migliore (o alcuni dei modi migliori) per evitare l'ambiguità?


38
usa enums (anche se ci sono solo 2 opzioni) invece di
bool

21
Alcune lingue supportano argomenti denominati. In tali lingue, potresti usaresomeFunction(forget: true);
Brian,

3
Mi sento in dovere di fornire le argomentazioni di Martin Fowler su Flag Arguments (parla anche di setter booleani). In generale, cerca di evitarli.
FGreg,

3
Per sostenere l'ovvio, i commenti possono mentire. Così è sempre meglio per rendere il codice auto-documentazione, perché alcuni cambierà trueper falsee non aggiornare il commento. Se non riesci a modificare l'API, il modo migliore per commentare questo èsomeFunction( false /* true=forget, false=remember */)
Mark Lakata,

1
@Bakuriu - in realtà, probabilmente avrei ancora l'API pubblica con due metodi separati ( sortAscendinge sortDescending, o simili). Ora, all'interno , entrambi possono chiamare lo stesso metodo privato, che potrebbe avere questo tipo di parametro. In realtà, se la lingua lo supportasse, probabilmente ciò che passerei sarebbe una funzione lambda che conteneva la direzione di ordinamento ...
Clockwork-Muse

Risposte:


33

Nel codice di esempio che hai pubblicato, sembra che forgetsia un argomento flag. (Non posso esserne certo perché la funzione è puramente ipotetica.)

Gli argomenti flag sono un odore di codice. Indicano che una funzione fa più di una cosa e una buona funzione dovrebbe fare solo una cosa.

Per evitare l'argomento flag, suddividere la funzione in due funzioni che spiegano la differenza nel nome della funzione.

Argomento bandiera

serveIceCream(bool lowFat)

Nessun argomento flag

serveTraditionalIceCream()
serveLowFatIceCream()

modifica: Idealmente, non è necessario mantenere una funzione con il parametro flag. Ci sono casi sulla falsariga di ciò che Fowler chiama un'implementazione aggrovigliata in cui la separazione completa delle funzioni crea codice duplicato. Tuttavia, maggiore è la complessità ciclomatica della funzione parametrizzata, più forte è l'argomento per sbarazzarsene.


Questo è solo un sospetto, ma un parametro chiamato forgetsuona come invidia delle funzionalità. Perché un chiamante dovrebbe dire a un altro oggetto di dimenticare qualcosa? Potrebbe esserci un problema di progettazione più grande.


4
+17, casco indossato. Che cosa fanno i corpi di serveTraditionalIceCreame serveLowFatIceCreamassomigliare? Ho un enum con 14 tipi di gelato.
JohnMark13,

13
Per i metodi pubblici, questa convenzione è buona, ma come allude JohnMark, l'altra metà di SRP è "un buon metodo dovrebbe essere l'unico metodo che fa quello che fa". Vedo che ci sono varianti N + 1 di questo metodo; N pubblico senza parametri, tutti che chiamano un metodo privato con il parametro che stai cercando di evitare di esporre. Ad un certo punto, basta rinunciare ed esporre quel dannato parametro. Gli odori di codice non significano necessariamente che il codice debba essere refactored; sono solo cose che meritano una seconda occhiata in una recensione di codice.
KeithS,

@Aaron Per "invidia delle caratteristiche" cosa intendevi?
Geek,

27

Nelle parole di laici:

  • false è un letterale.
  • stai passando un letterale false
  • stai dicendo someFunctiondi non dimenticare
  • stai dicendo someFunctionche il parametro dimenticare èfalse
  • stai dicendo someFunctiondi ricordare

Secondo me sarebbe meglio se la funzione fosse così:

void someFunction(bool remember);

puoi chiamarlo

void ourFunction() {
  someFunction(true);
} 

o mantieni il vecchio nome ma modifica la tua funzione wrapper in

void ourFunctionWithRemember() {
  someFunction(false);
} 

MODIFICARE:

Come affermato da @Vorac, cerca sempre di usare parole positive. La doppia negazione è confusa.


15
+1 per l'idea di cercare sempre di usare parole positive. La doppia negazione è confusa.
Vorac,

1
D'accordo, ottima idea. Dire al sistema di non fare qualcosa è confuso. Se si accetta un parametro booleano, esprimerlo in modo positivo.
Brandon,

Nella maggior parte dei casi, penso che vorresti anche essere più specifico di remember, a meno che il nome della funzione non abbia reso il significato remember molto ovvio. rememberToCleanUp* or *persisto qualcosa.
itsbruce,

14

Il parametro può essere ben definito; è difficile dirlo senza conoscere il nome della funzione. Suppongo che il commento è stato scritto dall'autore originale della funzione, ed è stato un promemoria di ciò che passa falsein someFunctionmezzo, ma per chiunque venendo in seguito, è un po 'poco chiaro a prima vista.

L'uso di un nome di variabile positivo (suggerito in Codice completo ) può essere la modifica più semplice che renderebbe più semplice la lettura di questo frammento, ad es.

void someFunction(boolean remember);

quindi ourFunctiondiventa:

void ourFunction() {
    someFunction(true /* remember */);
}

Tuttavia, l'uso di un enum rende la chiamata di funzione ancora più facile da capire, a scapito di alcuni codici di supporto:

public enum RememberFoo {
    REMEMBER,
    FORGET
}

...

void someFunction(RememberFoo remember);

...

void ourFunction() {
    someFunction(RememberFoo.REMEMBER);
}

Se non riesci a modificare la firma di someFunctionper qualsiasi motivo, l'utilizzo di una variabile temporanea semplifica anche la lettura del codice, un po 'come semplificare i condizionali introducendo una variabile per nessun altro motivo se non per rendere il codice più facile da analizzare dagli umani .

void someFunction(boolean remember);

...

void ourFunction() {
    boolean remember = false;
    someFunction(remember);
}

1
Impostare remembersu vero significa dimenticare (nel tuo esempio di someFunction(true /* forget */);)?
Brian,

2
Il enumè di gran lunga la soluzione migliore. Solo perché un tipo può essere rappresentato come bool—ie, sono isomorfi — non significa che debba essere rappresentato come tale. Lo stesso argomento vale stringe anche int.
Jon Purdy,

10

Rinomina la variabile in modo che abbia un valore bool.

Questo un milione di volte meglio dell'aggiunta di un commento per spiegare argomenti a una funzione perché il nome è ambiguo.


3
Questo non risponde alla domanda. Tutto è ovvio quando il metodo è definito e chiamato nello stesso file a 4 righe di distanza. Ma cosa succede se tutto ciò che vedi al momento è il chiamante? E se avesse più booleani? A volte un semplice commento in linea può fare molto.
Brandon,

@Brandon Questo non è un argomento contro la chiamata al booleano doNotPersist (o, meglio, Persist). Chiamarlo "dimenticare" senza dire cosa dimenticare è francamente inutile. Oh, e un metodo che prende diversi booleani come opzione puzza in alto paradiso.
itsbruce,

5

Crea un valore booleano locale con un nome più descrittivo e assegna il valore ad esso. In questo modo il significato sarà più chiaro.

void ourFunction() {
    bool takeAction = false;  /* false means to forget */
    someFunction( takeAction );
}    

Se non riesci a rinominare la variabile, il commento dovrebbe essere un po 'più espressivo:

void ourFunction() {
    /* false means that the method should forget what was requested */
    someFunction( false );
}    

1
Sicuramente un buon consiglio, ma non penso che affronti il ​​problema che il /* forget */commento è lì per affrontare, che è che senza la dichiarazione di funzione proprio di fronte a te, può essere difficile ricordare ciò che viene impostato false. (Ecco perché penso che il consiglio di Esailija di aggiungere un enum sia migliore, e perché amo i linguaggi che consentono parametri nominati.)
Gort the Robot

@StevenBurnap - grazie! Hai ragione nel dire che la mia vecchia risposta non era abbastanza chiara per rispondere alla domanda del PO. L'ho modificato per renderlo più chiaro.

3

C'è un buon articolo che menziona questa situazione esatta in quanto si riferisce alle API di Qt-Style. Lì, si chiama The Boolean Parameter Trap e merita una lettura.

L'essenza è:

  1. Sovraccaricare la funzione in modo che il bool non sia necessario è meglio
  2. Usare un enum (come suggerisce Esailija) è il migliore

2

Questo è un commento bizzarro.

Dal punto di vista del compilatore, someFunction(false /* forget */);è in realtà someFunction(false);(il commento viene rimosso). Quindi, tutta quella linea fa chiamata someFunctioncon il primo (e unico) argomento impostato su false.

/* forget */è solo il nome del parametro. Probabilmente non è altro che un promemoria rapido (e sporco), che non ha davvero bisogno di essere lì. Usa solo un nome di parametro meno ambiguo e non avrai bisogno del commento.


1

Uno dei consigli di Clean code è di ridurre al minimo il numero di commenti non necessari 1 (perché tendono a marcire) e di nominare correttamente funzioni e metodi.

Successivamente, rimuoverei semplicemente il commento. Dopotutto, gli IDE moderni (come l'eclissi) aprono una casella con il codice quando si posiziona il mouse sulla funzione. Vedere il codice dovrebbe chiarire l'ambiguità.


1 Commentare un codice complesso è ok.


btw Chi ha detto una cosa del genere: "il peggior problema del programmatore è come nominare la variabile e compensarla di una"?
BЈовић,

4
Probabilmente stai cercando martinfowler.com/bliki/TwoHardThings.html come fonte di esso. Ho sentito ottimizzato in "Ci sono solo due cose difficili in Informatica: invalidazione della cache, denominazione delle cose e via da un errore".

1

Per sostenere l'ovvio, i commenti possono mentire. Così è sempre meglio per rendere il codice auto-documentazione senza ricorrere ai commenti per spiegare, perché qualche persona (forse) cambierà trueper falsenon aggiornare il commento.

Se non riesci a modificare l'API, ricorro a 2 opzioni

  • Modifica il commento in modo che sia sempre vero, indipendentemente dal codice. Se lo chiami solo una volta, questa è una buona soluzione, poiché mantiene la documentazione locale.
     someFunction (false / * true = dimentica, false = ricorda * /); `
  • Usa #define, specialmente se lo chiami più di una volta.
     #define DIMENTICARE vero
     #define RICORDA falso
     someFunction (ricordo);

1

Mi piace la risposta su come rendere il commento sempre vero , ma mentre è buono penso che manchi il problema fondamentale con questo codice: viene chiamato con un valore letterale.

Dovresti evitare di usare i letterali quando chiami i metodi. Variabili locali, parametri opzionali, parametri nominati, enumerazioni: il modo in cui evitarli meglio dipenderà dalla lingua e da ciò che è disponibile, ma cerca di evitarli. I letterali hanno valori, ma non hanno significato.


-1

In C #, ho usato i parametri nominati per rendere questo più chiaro

someFunction(forget: false);

Oppure enum:

enum Memory { Remember, Forget };

someFunction(Memory.Forget);

O sovraccarichi:

someFunctionForget();

O polimorfismo`:

var foo = new Elephantine();

foo.someFunction();

-2

La denominazione dovrebbe sempre risolvere l'ambiguità per i booleani. Io chiamo sempre booleano qualcosa come "isThis" o "shouldDoThat", ad esempio:

void printTree(Tree tree, bool shouldPrintPretty){ ... }

e così via. Ma quando fai riferimento al codice di qualcun altro, è meglio lasciare un commento quando si passano i valori.


come risponde alla domanda posta?
moscerino del

@gnat Stavo rispondendo alla sua domanda sulla risoluzione dell'ambiguità con parametri booleani. Forse ho letto la sua domanda in modo sbagliato.
dchhetri,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.