2010-03-19 16 views
78

Sto leggendo il codice di McConell completo e discute usando le variabili booleane per documentare il codice. Per esempio, invece di:I programmatori dovrebbero usare le variabili booleane per "documentare" il loro codice?

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
    (elementIndex == lastElementIndex)){ 
     ... 
} 

Egli suggerisce:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

Questo mi sembra logico, le buone prassi, e molto auto-documentazione. Tuttavia, sono riluttante a iniziare a usare questa tecnica regolarmente, come non l'ho quasi mai incontrato; e forse sarebbe fonte di confusione solo in quanto raro. Tuttavia, la mia esperienza non è ancora molto vasta, quindi sono interessato a sentire l'opinione dei programmatori su questa tecnica, e sarei curioso di sapere se qualcuno usa questa tecnica regolarmente o l'ha vista spesso durante la lettura del codice. È una convenzione/stile/tecnica utile da adottare? Gli altri programmatori lo capiranno e lo apprezzeranno, o lo considereranno strano?

+0

@ Paolo R - urla, grazie. Ho iniziato con l'auto-documentazione e mi sono reso conto che non esisteva alcun tag per questo, quindi ho provato a cambiarlo per auto-documentare che esisteva già. :) – froadie

+0

Non sta semplicemente commentando cosa sta succedendo nel test? –

+3

Se appropriato, provo a farlo anche, soprattutto se devo verificare la/le condizione/i in diverse parti del metodo. È anche molto più descrittivo. – geffchang

risposta

54

La divisione di un'espressione troppo nidificata e complicata in sub-espressioni più semplici assegnate a variabili locali, quindi riassemblate, è una tecnica abbastanza comune e diffusa, indipendentemente dalle espressioni secondarie e/o dall'espressione generale sono booleani o di qualsiasi altro tipo. Con nomi ben scelti, una scomposizione raffinata di questo tipo può aumentare la leggibilità, e un buon compilatore non dovrebbe avere problemi a generare codice che sia equivalente all'espressione originale e complicata.

Alcune lingue che non hanno il concetto di "assegnazione" di per sé, come Haskell, introducono anche costrutti specializzati per consentire l'uso della tecnica "dare un nome a una sottoespressione" (la clausola where in Haskell) - - sembra rivelare una certa popolarità per la tecnica in questione! -)

+6

se è più semplice e più facile da leggere, dico che è un caso abbastanza chiaro di win-win :) – djcouchycouch

+0

Sono d'accordo. In molti casi potresti probabilmente farla franca con un breve commento, ma non vedo come questo potrebbe * ferire *. –

9

Cerco di farlo ovunque possibile. Certo, stai usando una "linea extra" di codice, ma allo stesso tempo stai descrivendo il motivo per cui stai facendo un confronto tra due valori.

Nel tuo esempio, guardo il codice e mi chiedo "okay perché la persona che vede il valore è inferiore a 0?" Nel secondo mi stai chiaramente dicendo che alcuni processi sono terminati quando ciò si verifica. Non indovina nel secondo quale fosse il tuo intento.

Quello grande per me è quando vedo un metodo come: DoSomeMethod(true); Perché è impostato automaticamente su true? E 'molto più leggibile come

bool deleteOnCompletion = true; 

DoSomeMethod(deleteOnCompletion); 
+7

Non amo i parametri booleani proprio per questo motivo. Finisci per ricevere chiamate come "createOrder (true, false, true, true, false)" e cosa significa? Preferisco usare enum, quindi dici qualcosa come "createOrder (Source.MAIL_ORDER, BackOrder.NO, CustomOrder.CUSTOM, PayType.CREDIT)". – Jay

+0

Ma se segui l'esempio di Kevin, è equivalente al tuo. Che differenza fa se la variabile può assumere 2 valori o più di 2? –

+2

Con Jay's puoi avere il vantaggio di essere sicuramente più chiaro in certi casi. Ad esempio, in uso del PayType. Se fosse un valore booleano, il parametro sarebbe probabilmente denominato, isPayTypeCredit. Non sai cosa sia l'alterativo.Con enum puoi vedere chiaramente quali sono le opzioni del PayType: Credito, Verifica, Contanti e seleziona quello corretto. – kemiller2002

16

ho usato, anche se normalmente avvolgendo la logica booleana in un metodo riutilizzabile (se chiamato da più postazioni).

Aiuta la leggibilità e quando la logica cambia, basta cambiare in un unico punto.

Altri lo capiranno e non lo troveranno strano (tranne per quelli che scrivono sempre solo migliaia di funzioni di linea, cioè).

+0

Grazie per la risposta! Per quanto riguarda i metodi riutilizzabili, questo ha un altro motivo (non correlato) valido per essere scomposto ... Quindi suppongo che la mia domanda sia davvero se doveste trarre delle espressioni booleane una tantum, quando non c'è altra ragione se non la leggibilità. (Che ovviamente è una ragione abbastanza grande da sola :)) Grazie per averlo sottolineato. – froadie

+0

+1 per ridurre le modifiche al codice necessarie quando la logica cambia e prendendo in giro i programmatori con funzione a mille righe. –

0

Penso, dipende da quale stile preferisci la tua squadra. Il refactoring "introdurre la variabile" potrebbe essere utile, ma a volte no :)

E io non dovrei essere d'accordo con Kevin nel suo post precedente. Il suo esempio, suppongo, utilizzabile nel caso in cui la variabile introdotta può essere cambiata, ma l'introduzione solo per un booleano statico è inutile, perché abbiamo un nome parametro in una dichiarazione di metodo, quindi perché duplicarlo nel codice?

ad esempio:

void DoSomeMethod(boolean needDelete) { ... } 

// useful 
boolean deleteOnCompletion = true; 
if (someCondition) { 
    deleteOnCompletion = false; 
} 
DoSomeMethod(deleteOnCompletion); 

// useless 
boolean shouldNotDelete = false; 
DoSomeMethod(shouldNotDelete); 
3

L'unico modo che ho potuto vedere questo andando male è se il frammento booleana non ha un nome che abbia un senso e un nome viene raccolto in ogni caso.

//No clue what the parts might mean. 
if(price>0 && (customer.IsAlive || IsDay(Thursday))) 

=> 

first_condition = price>0 
second_condition =customer.IsAlive || IsDay(Thursday) 

//I'm still not enlightened. 
if(first_condition && second_condition) 

ho farlo notare perché è un luogo comune per rendere le regole come "lasciare un commento, tutto il codice", "uso di nome booleani per tutte se-i criteri con più di 3 parti" solo per ottenere commenti che sono semanticamente vuoto il seguente tipo

i++; //increment i by adding 1 to i's previous value 
+2

Hai raggruppato in modo errato le condizioni nell'esempio, non è vero? '&&' si lega più stretto di '||' nella maggior parte delle lingue che li usano (gli script di shell sono un'eccezione). –

+0

Parenti aggiunti. Quindi questo sarebbe un altro colpo contro la suddivisione in variabili nominate, introduce l'opportunità di cambiare il raggruppamento in modo non ovvio. – MatthewMartin

2

in questo modo

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

si rimuove il logica dal vostro cervello e metterlo nel codice. Ora il programma sa cosa significava.
Ogni volta che si nome qualcosa si dà rappresentazione fisica. Esiste.
È possibile manipolarlo e riutilizzarlo.

si può anche definire l'intero blocco come un predicato:

bool ElementBlahBlah? (elementIndex, lastElementIndex); 

e fare più cose (in seguito) in quella funzione.

+0

E, ancora più importante, il prossimo dev per vedere il codice saprà cosa intendi! Questa è una grande pratica, e lo faccio sempre. –

+1

Inoltre, il prossimo sviluppatore potrebbe spesso essere te stesso, guardando di nuovo il codice dopo alcuni mesi (o anche poche settimane) ed è contento che sia stato ben documentato. – Louis

2

Se l'espressione è complessa, l'ho spostata su un'altra funzione che restituisce bool, ad esempio isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash) o riconsiderare il codice in modo che non sia necessaria un'espressione complessa.

1

Ricorda che in questo modo calcolhi più del necessario. A causa delle condizioni fuori dal codice, si calcolano sempre entrambi (nessun corto circuito).

In modo che:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) || 
    (elementIndex == lastElementIndex)){ 
    ... 
} 

Dopo la trasformazione diventa:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) | 
    (elementIndex == lastElementIndex)){ 
    ... 
} 

Non è un problema nella maggior parte dei casi, ma ancora in alcuni può significare prestazioni peggiori o altri problemi, ad esempio, quando nella seconda espressione presumi che il primo abbia fallito.

+0

Un compilatore lo risolverà durante l'ottimizzazione. –

+0

Vero - Lo stavo proprio notando ora mentre tornavo ad alcuni dei miei codici per refactoring un paio di istruzioni if ​​usando questo metodo. C'era una dichiarazione if che sfruttava la valutazione del cortocircuito con un && (la seconda parte lancia un NPE se la prima parte è falsa), e il mio codice non funzionava quando l'ho refactored (perché era * sempre * valutare entrambi e archiviarli in variabili booleane). Buon punto, grazie! Ma mi stavo chiedendo come ho provato questo - c'è un modo per memorizzare la * logica * in una variabile e avere una valutazione del ritardo fino all'effettiva istruzione if? – froadie

+2

In realtà dubito che il compilatore lo risolva. Se la seconda chiamata non è efficace, di solito è a causa della chiamata di alcune funzioni e AFAIK nessun compilatore sta cercando di determinare se una funzione chiamata è priva di effetti collaterali. –

5

L'esempio fornito:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
repeatedEntry = (elementIndex == lastElementIndex); 
if(finished || repeatedEntry){ 
    ... 
} 

Può anche essere riscritto per utilizzare metodi, che migliorano la leggibilità e preservare la logica booleana (come Konrad ha sottolineato):

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){ 
    ... 
} 

... 

private bool IsFinished(int elementIndex) { 
    return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex)); 
} 

private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) { 
    return (elementIndex == lastElementIndex); 
} 

Si ha un prezzo , ovviamente, che è due metodi extra. Se lo fai molto, potrebbe rendere il tuo codice più leggibile, ma le tue classi meno trasparenti. Ma poi di nuovo, puoi anche spostare i metodi extra in classi helper.

+0

Se si ha molto rumore di codice con C#, è anche possibile sfruttare le classi parziali e spostare il rumore nel parziale e se le persone sono interessate a ciò che IsFinished sta controllando è facile passare a. –

2

Penso che sia meglio creare funzioni/metodi anziché variabili temporanee. In questo modo la leggibilità aumenta anche perché i metodi si accorciano. Il libro di Martin Fowler Refactoring ha buoni consigli per migliorare la qualità del codice.I refactoring relativi al tuo particolare esempio sono chiamati "Replace Temp with Query" e "Extract Method".

+2

Stai dicendo che ingombrando lo spazio della classe con molte funzioni una tantum, stai aumentando la leggibilità? Spiega per favore. – Zano

+0

È sempre un compromesso. La leggibilità della funzione originale migliorerà. Se la funzione originale è breve, potrebbe non valerne la pena. – mkj

+0

Anche "ingombrare lo spazio della classe" è qualcosa che penso dipende dal linguaggio utilizzato e da come si partiziona il codice. – mkj

2

Personalmente, penso che questa sia una grande pratica. L'impatto sull'esecuzione del codice è minimo, ma la chiarezza che può fornire, se usato correttamente, è inestimabile quando si tratta di mantenere il codice in un secondo momento.

0

Nella mia esperienza, sono tornato spesso ad alcune vecchie sceneggiature e mi chiedevo 'che diavolo stavo pensando allora?'. Per esempio:

Math.p = function Math_p(a) { 
    var r = 1, b = [], m = Math; 
    a = m.js.copy(arguments); 
    while (a.length) { 
     b = b.concat(a.shift()); 
    } 
    while (b.length) { 
     r *= b.shift(); 
    } 
    return r; 
}; 

che non è così intuitivo come:

/** 
* An extension to the Math object that accepts Arrays or Numbers 
* as an argument and returns the product of all numbers. 
* @param(Array) a A Number or an Array of numbers. 
* @return(Number) Returns the product of all numbers. 
*/ 
Math.product = function Math_product(a) { 
    var product = 1, numbers = []; 
    a = argumentsToArray(arguments); 
    while (a.length) { 
     numbers = numbers.concat(a.shift()); 
    } 
    while (numbers.length) { 
     product *= numbers.shift(); 
    } 
    return product; 
}; 
+0

-1. Questo ha un po 'a che fare con la domanda originale, ad essere onesti. La domanda riguarda una cosa diversa, molto specifica, non su come scrivere un buon codice in generale. –

+0

E sì, benvenuto su StackOverflow.com! –

0

raramente creo variabili separate. Quello che faccio quando i test si complicano è annidare i FI e aggiungere commenti. Come

boolean processElement=false; 
if (elementIndex < 0) // Do we have a valid element? 
{ 
    processElement=true; 
} 
else if (elementIndex==lastElementIndex) // Is it the one we want? 
{ 
    processElement=true; 
} 
if (processElement) 
... 

Il difetto ammesso di questa tecnica è che il prossimo programmatore che arriva può cambiare la logica, ma si preoccupa di aggiornare i commenti. Immagino che sia un problema generale, ma ho avuto un sacco di volte che ho visto un commento che dice "convalidare ID cliente" e la riga successiva sta esaminando il numero di parte o alcuni di essi e mi viene chiesto dove sia il cliente id viene in

1

se il metodo richiede una notifica di successo:. (esempi in C#) mi piace usare la

bool success = false; 

per iniziare. il codice è un falure fino a quando lo cambio a:

success = true; 

poi alla fine:

return success; 
Problemi correlati