Replace conditional with...



Conditional statements like "If" and "switch" multiply the number of possible flows the code execution can follows. So they increase the complexity and the number of tests required to verify the code.

=> Conditional statements are time and effort (costs €€€) multipliers






Here is a list of known techniques to eliminate conditionals :



  1. Replace conditional with polymorphism (look also the patterns template method and pluggable selector and  abstract factory)

  2. The patterns Null Object and Special Case

  3. The pattern State

  4. The pattern Pluggable Object

  5. The pattern Strategy

  6. The pattern Composite

  7. The pattern Visitor

  8. Replace conditional with a Dictionary (or with a conversion operator)

  9. Replace Conditional Dispatcher with Command

  10. Linearize the algorithm

Look also  "Replace nested conditionals with guards" to simplify the conditional before eliminating it.

some solutions like 6 and 10 completely eliminate the IF, some like 1 and 2 eliminate the duplication of the IF putting the IF logic in just one place, some like 8 reuse a IF logic tha is already implemented in the Framework so it prevent from creating a new IF.


Now I challenge you, show me an IF that cannot be eliminated !


Edit 5-Sep-2010:

  1. Replaced IF used to emulate break; or continue; in a nested FOR loop with a Return after extracting the nested IF in a new method. See Structured programming, Edsger Dijkstra (1969)
See also an example of code after removing IF and before full of IF here.

Look also: http://www.antiifcampaign.com/

Tags :   |  |  |  |

Print | posted @ martedì 20 aprile 2010 23.58

Comments on this entry:

Gravatar # re: Replace conditional with...
by Massimo at 21/04/2010 9.42

Ciao Luca, alcuni consigli che dai li ho utilizzati con perfetta efficacia, il template method, la factory, lo stategy ed il dictionary, forse di quest'ultimo ne ho pure abusato, in alcuni casi l'ho sostituito ad un semplice if... else...
ho esagerato?
Mi piacerebbe farmi qualche anno di lavoro con te, chissà quante cose potrei imparare!
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 10.28

> Now I challenge you, show me an IF that cannot be eliminated !

l'elenco che hai scritto serve per togliere gli if sul valore di variabili. Ci sono if che non si possono togliere perchè descrivono un comportamento mi spiego meglio:
if queue.IsEmpty
non lo puoi togliere.
Mentre:
If queue == null
if elementType == ...
ecc. si possono togliere.
  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 11.52

Ciao Antonio, se utilizzi automata based programming, penso proprio che tu possa eliminare anche gli if comportamentali in quanto il caso di pila vuota lo rappresenterai attraverso uno stato della fsm...
correggimi se sbaglio ;)
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 12.05

Volendo puoi fare così:
IF(queue.IsEmpty, true actions, false actions)

dove

void IF(bool condition, Action trueAction, Action falseAction)

o qualcosa di simile, così avresti un solo if nel calcolo della complessità ciclomatica, ma secondo me non andresti verso una migliore leggibilità del codice e avresti solo spostato l'if da un altra parte.
Non credo sia questo l'obiettivo del post di Luka ma quello di andare verso una minore complessità effettiva.
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 21/04/2010 12.32

@Massimo

mi sembra che di cose ne sai gia tante di tuo :)



@Antonio

dipende anche da cosa fai nel if.
se ad esempio cicli fli elementi della coda, testare IsEmpty non serve perche il for si comportera bene anche in quel caso.

se invece fai qualcosa, puoi spostare quel qualcosa nel metodo di un oggetto e invece di ritornare pila vuota ritorni una implementazione null-object di quel oggetto che in tal caso non fara assolutamente niente.

questi esempi coprono la casistica a cui pensavi o é una casistica differente ?
  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 12.45

@Antonio and Luka
Il caso di pila vuota, lo puoi affrontare in termini OO come suggerito da Luka, se invece ripeto usi uno stile basato sugli automi a stati finiti, invece che ritornare un null object ti porti in uno stato di pila vuota....
Proprio in un progetto personale che pubblicherò a breve (riguarda le regex e gli automi, ecco perchè adesso vedo tutto come una macchina a stati ;)) voglio effettuare refactoring applicando il pattern command invece che il dispatcher basato su if che sto utilizzando adesso per parsare una regex...
Ottimi consigli in questo post di Luka
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 21/04/2010 12.57

@Luca

quindi se ho capito bene usi il design pattern state e/o il design pattern command
  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 13.16

@Luka
Esatto, per la parte di parsing sicuramente il pattern command, e presumibilmente anche il pattern state al posto dell'implementazione attuale
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 14.03

Prendiamo questo esempio:

If sounds.Contains(keyCode)
{
Play(sounds[keyCode]);
}
else
{
Play(defaultSound);
}

In pratica se ho premuto un certo tasto nella tastiera emetto un suono (tipo un simulatore di tastiera elettronica) altrimenti ne emetto uno di default.
Quella if in qualche maniera va fatta o mi sfugge qualcosa?
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 14.35

@Luca:
Ma non rischi in quel modo di introdurre troppa complessità? E quindi di allontanarti dall'obiettivo?
  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 15.04

@Antonio
Dipende molto dal contesto in cui stai lavorando. Ad esempio ieri (se nn erro) ho letto il tuo post quello sulla malleabilità architetturale... mi trovo d'accordo con quanto hai scritto in quanto lo scenario che descrivi prevede dei possibili cambiamenti... molti però potrebbero trovare cmq complesso l'approccio descritto. Stessa cosa per la possibile soluzione che ho descritto che non ha valenza universale in quanto giustamente per alcuni può essere sovraingegnerizzata ma in alcuni contesti risulta la maggiormente adatta (ad esempio quando si modellano dei sistemi reattivi).
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 15.20

Certamente bisogna definire la complessità altrimenti diventa un opinione :-).
Nel caso di questo post Luka indica come indice di complessità la presenza di istruzioni che possono deviare il flusso di istruzioni e quindi indica dei modi per limitare queste situazioni. Diciamo che una misura potrebbe essere la complessità ciclomatica.
Quello che volevo comunicare e che ci sono alcuni tipo di if che non rendono più complesso il sistema perchè fanno parte della descrizione del comportamento e che quindi non si possono eliminare se non a discapito della leggibilità / manutenibilità anche se magari abbassi un pochino mccabe. Siete d'accordo con questa osservazione?
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 21/04/2010 15.53

@Antonio

quel "If sounds.Contains(keyCode)" andrebbe spostato dentro l'indexer sounds[keyCode] facendo in modo che restituisca il defaultSound per i tasti/keyCode non esplicitamente definiti

cosi facendo l'if lo incapsuli e qunidi non lo duplichi in giro per il codice.

o meglio ancora inizializzi la collezione sounds per tutti i keycode col suono di default e cosi non hai nemmeno un if.

riguardo il tuo ultimo quisito, personalmente preferisco eliminare l'if che scrivere meno righe di codice , questo post analizza bene l'argomento e mappa bene quanto ho visto nella pratica sull'argomento: http://blog.objectmentor.com/articles/2009/02/17/the-bloat-at-the-edge-of-duplication-removal-the-orange-model
  
Gravatar # re: Replace conditional with...
by LudovicoVan at 21/04/2010 15.59

Personalmente sono daccordo: a volte il branching davvero denota una scarsa familiarita' con -per esempio- il poliformismo e persino le piu' elementari tecniche di programmazione strutturata; d'altra parte anche gli if/switch ecc. hanno un loro ruolo basilare nell'implementazione degli algoritmi in un linguaggio imperativo, e in quel caso fare a meno degli if adottando tecniche piu' di alto livello aggiunge solo complessita' non necessaria, mentre sicuramente toglie alla leggibilita'.

Fra parentesi, noterei che gli automi a stati finiti rappresentano un modello computazionale relativamente semplice, che non risolve le nostre esigenze di programmazione in generale: per esempio un parser vero e proprio si puo' fare con gli automi a stati finiti (o le sole regular expressions).

-LV
  
Gravatar # re: Replace conditional with...
by LudovicoVan at 21/04/2010 16.01

> per esempio un parser vero e proprio si puo' fare con gli automi a stati finiti

Pardon, ovviamente intendo *non* si puo' fare...

-LV
  
Gravatar # re: Replace conditional with...
by LudovicoVan at 21/04/2010 16.09

> quel "If sounds.Contains(keyCode)" andrebbe spostato dentro l'indexer sounds[keyCode] facendo in modo che restituisca il defaultSound per i tasti/keyCode non esplicitamente definiti

Ti risponderei che dipende: metti che e' un view-model, magari ha davvero senso restituire un default (ma comunque dipenderebbe dai requisiti), se invece fosse un model o una data class che ci sta sotto, restituirei il null e lascerei al client decidere se e come gestire la situazione, intercettare eccezioni, sollevare errori, e quant'altro.

Piu' in generale, mi sembra che il problema come al solito sia riuscire di volta in volta a capire dove sta quella soglia oltre la quale l'utile e dilettevole diventa inutile e addirittura dannoso.

My 2c, interessante dicussione.

-LV
  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 16.10

@Antonio and Luka
Personalmente sono pià vicino ad Antonio nel senso che in alcuni contesti preferisco tenere un if, non tanto per tenere meno righe di codice quanto piuttosto per rendere più human friendly la logica applicativa ad esempio dell'algoritmo che implemento...
il tutto IMVVVVHO

  
Gravatar # re: Replace conditional with...
by Luca Del Tongo at 21/04/2010 16.18

@LudovicoVan
Ho scritto una cosa molto simile alla tua sull'utilizzo degli if negli algoritmi, quindi li ci troviamo d'accordo :)
Quando mi riferivo ad un parser nel mio caso dicevo un parser di regex...
  
Gravatar # re: Replace conditional with...
by Antonio Ganci at 21/04/2010 16.35

@Antonio
quel "If sounds.Contains(keyCode)" andrebbe spostato dentro l'indexer sounds[keyCode] facendo in modo che restituisca il defaultSound per i tasti/keyCode non esplicitamente definiti

Si ok, sono d'accordo l'ho scritto perchè si capisse all'interno di un commento del blog.
Comunque mccabe rimarrebbe uguale ;-).

> o meglio ancora inizializzi la collezione sounds per tutti i keycode col suono di default e cosi non hai nemmeno un if.

Non sono d'accordo perchè non beccheresti le combinazioni con shift + tasto, ecc.
  
Gravatar # re: Replace conditional with...
by vic at 23/04/2010 21.33

int max( int a, int b )
{
if( a > b )
return a ;
else
return b ;
}

come lo togli?
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 24/04/2010 12.31

@vic

in parte definire la funziona max é gia un modo di eliminare IF perché incapsula l'IF e quindi ne previene la duplicazione.

dall'altra, nel mondo reale semplicemente ri-usi Math.Max definito dal framework cosi non aggiungi neanche il primo IF.

infine guardando il codice che usa il max nella tua applicazione, magari scopri che l'algoritmo'e linearizzabile senza bisogno di max
  
Gravatar # re: Replace conditional with...
by vic at 25/04/2010 9.27

ma quindi non lo sai togliere, sfida vinta, ok : )

ovviamente ho messo max perche' era un esempio di 2 righe. Casi del tutto analoghi ne trovo a quintali, e non sono tutti in libreria.

se voglio una lista di tutti i multipli di 3 tra N ed M che siano maggiori di 21 lo faccio senza if?
ecc ecc...

e dai, non dirmi che se guardo meglio il codice posso fare a meno di max... se ci ho messo un max e' perche' mi serve un max, suvvia... non siamo all'asilo : )

se da specifica un numero arriva da un canale non controllabile (utente, file, ecc ecc), ma se supera una soglia devo prendere la soglia, o ci metto un if o ci metto un max. questo e' un requisito, non una questione implementativa... non lo togli con la forza di volonta'.

non dico di non usare tecniche alternative agli if, dico che ci sono if non eliminabili nel famoso mondo reale senza *peggiorare* il codice in modo insostenibile...
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 14.12

@vic

gli esempi che porti sono reali come esercizi scolastici di programmazione, non si trovano negli applicativi commerciali che si scrivono quotidianamente.
praticamente tutti i linguaggi/compilatori implementano il max, e anche ordinamenti e mappe/dictionary.
mentre l'esempio "multipli di 3 tra N ed M che siano maggiori di 21" si implementa linearizzando l'algoritmo con un mod e un for.
al lavoro potrai osservare che tutti i programmatori si lamentano quando devono modificare una lunga sequenza di if magari nidificata scrutta da qualcun'altro, ma poi paradossalmente solo pochi di questi cercano di evitare di scrivere IF quando scrivono nuovo codice.

ti invito a riflettere:
quando scrivi codice usi piú spesso le tecniche per evitare gli IF o scrivi piú spesso IF ?
la scelta di un programmatore professionista é tra usare la scusa degli if scolastici per continuare ad aggiungere IF al codice o invece imparare ad applicare correntemente le tecniche che evitano la grande maggioranza di IF.
Quello che posso fare é proporti la pillola rossa e quella blu, ma la scelta poi é la tua :)
  
Gravatar # re: Replace conditional with...
by mic at 25/04/2010 15.05

come risolvereste nel normale caso del load di una page asp.net?
protected void Page_Load(object sender, EventArgs e)
{
if(IsPostBack)return;

......
}
  
Gravatar # re: Replace conditional with...
by vic at 25/04/2010 15.24

ma dai... e' ovvio che faccio esempi elementari perche' in un commento lo spazio e' quello che e'.

comunque tu non togli gli if, li sposti in libreria... sperando di avere tutti gli algoritmi pronti...

proviamo quest'altra, cosi' vediamo chi deve riflettere : )

supponi di avere la solita classe persona... ora tu vuoi fare uno sconto solo alle donne, con eta' minore di 21 anni, bionde, che hanno gia' comprato un prodotto piripicchio.

Diciamo che hai gli oggetti in memoria e non in un db sql, come trovi gli oggetti che rispondono a TUTTE le condizioni?
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 15.37

@mic

in questo caso torna utile il pattern State (preferibilmente implementato estendendo il PageHandlerFactory del aspx)

guarda il pattern, se hai domande specifiche legate ad aspx non esitare a chiedere



  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 15.48

@vic

se nella tua code-base quel if implementa quella logica "solo alle donne, con eta' minore di 21 anni, bionde, che..." compare _una_e_una_volta_sola_ in tutta la code-base sei giá apposto. non devi eliminarlo oltre.
se invece trovi la stessa logica condizionale in piú posti della code-base puoi usare le soluzioni 1,2,5,7 o 9 per eliminare le duplicazioni.


per evitare sin dall'inizio di creare le duplicazioni di quella logica condizionale, le stesse soluzioni 1,2,5,7 o 9 ti tornano utili.
  
Gravatar # re: Replace conditional with...
by miv at 25/04/2010 15.57

piu' che altro vorrei capire se in un caso come questo vale la pena implementare altre classi/metodi per evitare 'una' condizione
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 17.00

@mic/miv

bella domanda.
presuppongo che si usi ASP.NET senza pattern mvc quindi pagine testate a mano.

nelle pagine in cui c'é del codice di inizializzazione quando IsPostBack == false quindi serve uno sforzo maggiore per testarle (2 test cioé uno per ogni caso).

cercherei di vedere le pagine dove quel IF c´é, probabilmente in alcune pagine hanno una logica di inizializzazione (tipo per sempio cache o lazy-load per un drop down).
quindi proverei a incapsulare quella logica condizionale in una classe che quelle pagine possono ri-usare. facendo in modo che con l'IF li in quella classe quello IF IsPostBack non serva piú in quelle pagine. cosi si puo testare quella classe una volta sola e le pagine che la usano ora ogniuna ha bisogno di un test in meno.

quanto conviene?
eliminare o incapsulare a basso livello il primo IF richiede un certo tempo/sforzo, se non lo faccio tuttavia al prossimo IF che si é spinti ad aggiungere li, lo sforzo richiesto per eliminarlo non sarebbe doppio ma sarebbe molto maggiore, e i casi da testare con 2 IF questa volta diventano 4 e la prossima volta 8 con un crescendo esponenziale.

quindi imho si, conviene provarci ad eliminarlo appena si presenta. e ripaga col risparmio di tempo di test o di bug da fissare e piu il codice si evolve nel tempo piu il guadagno cresce.


per citare un caso pratico, in 2 doce-base ho visto miriadi di IF per gestire i diversi metodi di pagamento aggiunti al sito. Testarlo ora é un bagno di sangue e aggiungere un nuovo metodo di pagamento é contoso in termini di tempo ed é difficile evitare bug.
il tutto é iniziato col primo IF :(
  
Gravatar # re: Replace conditional with...
by mic/miv at 25/04/2010 18.56

quindi il tuo consiglio finale e' quello di sforzarsi comunque sempre a cercare di rimuovere codice condizionale?
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 20.50

esatto. dallo sforzo nasce l'idea di come fare in efficace e conveniente. oppure domande che poi aiutano ad approfondire gli skil necessari ad evitare gli IF
  
Gravatar # re: Replace conditional with...
by vic at 25/04/2010 22.18

ok, se cambiamo la questione originale:

Now I challenge you, show me an IF that cannot be eliminated !


in "gli if non si devono ripetere ma isolare"
(come peraltro ogni altro tipo di logica)

direi che siamo tutti d'accordo...

poi, sicuramente, alcuni if si possono realmente eliminare, ma non tutti; questi possono di norma essere isolati.
  
Gravatar # re: Replace conditional with...
by Luca Minudel at 25/04/2010 23.14

concordo che questa frase é corretta.

quanti siano gli "alcuni if" che si possono eliminare é anche questo un punto importante su cui concordare.

in una code-base di un team é comune osservare che presi 10 if, tra quelli che sono duplicazione e quelli che sono del tutto eliminabili riisulta che 9 possono essere tolti.

per farlo serve conoscere i pattern relativi, riconoscere dove-quando applicarli e saperlo fare bene e in fretta in modo che sia sostenibile il tempo e sforzo. cioé la sfida é sugli skill da acquisire e allenare. quel IF che non puo essere eliminato non puo peró nemmeno essere usato per giustificare gli altri 9 che vengono lasciati li.

  
Gravatar # re: Replace conditional with...
by Tonino at 26/04/2010 15.26

Interessante discussione.

Piccola esperienza:
Mi sono trovato a gestire una serie classi che applicavano regole di validazione, precedentemente gestite con degli "if", che venivano a volte rimesse in discussione, come requisiti, o in base al tipo di utente.
(un dato e' obbligatorio per qualcuno, non obbligatorio per qualcun'altro, e in un dato momento le cose cambiano)

Le regole di validazione erano codificata un po' come nel seguente modo:
if (string.IsNullOrEmpty(Nazionalita)) strErrore += ....


e di "if" ve ne erano un po' tanti, per ogni classe e per ogni tipo di dato coinvolto in quella classe.

Ho sostituito gli if (non tutti) con chiamate a un generico "constraintchecker" basato su dictionary nel quale vengono "iniettate" le logiche di validazione (con la tecnica dei delegates).

Quindi ora gli if sono sostituititi con la chiamata:
CheckConstraintForObject(this);

che a sua volta scandisce ed invoca i delegates, verificando prima se, da file di configurazione, e' stato definito che la regola deve essere attiva o meno.

Mi piaca abbastanza, anche nel senso del single responsability principle (la validazione, ed essa sola, e' affidata a...)

Non e' che ho ridotto di molto la complessita', pero' almeno e' stata separata e centralizzata una certa logica, ed e' stata consentita la possibilita' di decidere per diverse regole intervenendo in configurazione.

my 2 cents.

(p.s. sono stati comunque introdotti due if, che sono tra quelli che secondo me dovrebbero restare, un po' come nell'esempio fatto da Antonio. Se ne puo' parlare)

Ciao.
  

Your comment:

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 3 and 1 and type the answer here: