2010-03-24 6 views
7

Considerare una situazione in cui è necessario chiamare le routine successive e fermarsi non appena si restituisce un valore che può essere valutato come positivo (true, object, 1, str (1)).È considerata una cattiva forma eseguire una funzione all'interno di un'istruzione condizionale?

'molto forte la tentazione di fare questo:

if (fruit = getOrange()) 
elseif (fruit = getApple()) 
elseif (fruit = getMango()) 
else fruit = new Banana(); 

return fruit; 

mi piace, ma questo non è uno stile molto ricorrente in quello che può essere considerato il codice di produzione professionale. Uno è probabile che per vedere un po 'di codice più elaborato come:

fruit = getOrange(); 
if(!fruit){ 
    fruit = getApple(); 
    if(!fruit){ 
     fruit = getMango(); 
     if(!fruit){ 
      fruit = new Banana(); 
     } 
    } 
} 

return fruit; 

Secondo il dogma su strutture di base, è la forma precedente accettabile? Lo consiglieresti?

Edit:

Mi scuso con coloro che ritenevano che queste funzioni dovevano essere fabbriche o costruttori. Non lo sono, sono solo dei segnaposti. La domanda riguarda più la sintassi che "fabbrica". Queste funzioni potrebbero anche essere lambda.

+1

Dipende dalla lingua. – SLaks

risposta

9

Se si desidera una sintassi succinta, diverse lingue consentono di utilizzare "logico o" per questo scopo (C# fornisce esplicitamente un operatore a coalescenza, poiché i valori null non sono falsi).

Python:

fruit = (getOrange() or 
      getApple() or 
      getMango() or 
      Banana()) 

C#:

fruit = getOrange() ?? 
     getApple() ?? 
     getMango() ?? 
     new Banana(); 
+0

Credo che ci siano piani per le versioni future di PHP per rendere facoltativa la seconda parte delle dichiarazioni ternarie in stile C, che consente di usare '?:' In PHP proprio come C# 's. –

+0

l'implementazione python si adatterebbe javascript. l'operatore coalescente sarebbe una bella aggiunta a molte lingue. grazie per questo. –

1

Il problema, a mio avviso, non è la struttura, ma le regole di guida. Perché getOrange viene prima di getApple, ecc.?

si sono probabilmente più probabilità di vedere qualcosa di più su dati:

enum FruitEnum 
{ 
    Orange, Apple, Mango, Banana 
} 

e separatamente,

List<FruitEnum> orderedFruit = getOrderedFruit(); 
int i = 0; 
FruitObj selectedFruit; 
while(selectedFruit == null && i <= orderedFruit.Count) 
{ 
    fruit = FruitFactory.Get(orderedFruit[i++]); 
} 
if(fruit == null) 
{ 
    throw new FruitNotFoundException(); 
} 

Detto questo, per semplificare il codice, è possibile utilizzare un operatore coalesce:

fruit = getOrange() ?? getApple() ?? getMango() ?? new Banana(); 
+0

Come l'enumerazione, poiché si adatta alla visione del mondo esistente dell'OP. La fabbrica di frutta ... Buon Dio. Sembra un sacco di problemi per un sacchetto di frutta. Ma capisco perché lo stai facendo in quel modo. –

+2

State assumendo molto sui costrutti linguistici disponibili che l'OP non pone nella sua domanda. – tvanfosson

+0

inoltre, non c'è motivo di supporre che la sua lingua, o qualsiasi altra, abbia l'operatore di coalizione. So solo di C#, e sicuramente non è C#, molto probabilmente è PHP. C# non consentirà l'assegnazione all'interno di un condizionale. – Tesserex

3

In un linguaggio fortemente tipizzato che non equivale a 0/null a false e non a 0/non null a tr ue, direi che probabilmente è sicuro, ma marginalmente meno leggibile nel caso generale, dove i nomi dei metodi e il numero di parametri potrebbero essere maggiori. Lo eviterei personalmente, tranne che per alcuni idiomi standard, nei casi in cui 0/null equivalgono a false e non-0/non-null a true semplicemente a causa del potenziale pericolo di confondere l'assegnazione con il controllo di uguaglianza nella lettura del codice. Alcune espressioni idiomatiche in lingue debolmente tipizzato, come C, sono così pervasivo che non ha senso per evitarli, .e.g,

while ((line = getline()) != null) { 
    ... 
} 
4

Mi vengono in mente due alternative.

Il primo è consentito solo in lingue come la tua (PHP?), Dove single = in a condizionale è ok.

if ((fruit = getOrange()) != null) 
    elseif ((fruit = getApple()) != null) 
    elseif ((fruit = getMango()) != null) 
    else fruit = new Banana(); 

rende chiaro che si sta facendo un confronto e che i singoli = non sono un errore.

fruit = getOrange(); 
    if(!fruit) fruit = getApple(); 
    if(!fruit) fruit = getMango(); 
    if(!fruit) fruit = new Banana(); 

Proprio come il tuo secondo esempio, ma si libera del brutto nidificazione in più.

0

Per rispondere alla tua domanda direttamente: è solito forma difettosa di avere effetti collaterali in istruzione condizionale.

Come un lavoro in giro, è possibile memorizzare i costruttori di frutta in un array e trovare il primo costruttore che restituisce non nullo (pseudocodice):

let constructors = [getOrange; getApple; getMango; fun() -> new Banana()] 
foreach constructor in constructors 
    let fruit = constructor() 
    if fruit != null 
     return fruit 

E 'come un operatore di null-fondono, ma più generalizzata . In C#, si sarebbe probabilmente utilizzare LINQ come segue:

var fruit = constructors 
    .Select(constructor => constructor()) 
    .Filter(x => x != null) 
    .First(); 

Almeno in questo modo è possibile passare i vostri costruttori in giro come una classe di fabbrica, invece di codificare con l'operatore null-fondono.

1

in C o C++, si potrebbe scrivere:

return (fruit = getOrange()) ? fruit : 
     (fruit = getApple()) ? fruit : 
     (fruit = getMango()) ? fruit : 
     new Banana(); 

La ragione per evitare sia questa e la vostra prima versione non è "un dogma su strutture di base", è che l'assegnazione da solo in una condizione è confusione. Non tutte le lingue lo supportano, per una cosa. Per un altro è facilmente interpretabile come ==, o il lettore potrebbe essere incerto se lo intendevi davvero, o forse lo intendeva ==. Aggiungere != 0 a ciascuna condizione diventa abbastanza denso e prolisso.

GCC ha un'estensione che consente:

return getOrange() ? : getApple() ? : getMango() ? : new Banana(); 

La stessa cosa può spesso essere ottenuto con || o or (ma non in C o C++).

Un'altra possibilità è:

do { 
    fruit = getOrange(); 
    if (fruit) break; 
    fruit = getApple(); 
    if (fruit) break; 
    fruit = getMango(); 
    if (fruit) break; 
    fruit = new Banana(); 
} while (false); 

questo va ancora meglio in una lingua in cui è possibile break da un blocco di base, che è possibile con last in Perl, perché si può fare a meno del do/while(false). Ma probabilmente solo ai programmatori di assemblaggi piacerà davvero.

0

Non penso che nessuno abbia ancora menzionato che la prima versione può essere una sorta di sofferenza da superare in un debugger. La differenza di leggibilità è discutibile, ma in generale, evito assegnazioni e chiamate di funzione in condizionali per facilitare l'analisi dell'esecuzione, quando necessario (anche se non ho mai bisogno di eseguirne il debug, potrebbe essere necessario modificare il codice in un secondo momento).

Problemi correlati