AntonioGanci

Il blog di Antonio Ganci
posts - 201, comments - 420, trackbacks - 31

Ridurre la complessità del codice

Prendo spunto dal post di Luka su come ridurre la complessità del codice eliminando i conditional statement come if, swicth ecc. per fare qualche considerazione.

Premetto che ho aderito alla campagna anti-if e che sono pienamente d'accordo ad avere un codice con una complessità ciclomatica molto bassa.

Ci sono if che secondo me non è conveniente eliminare.

Partiamo da un esempio:

Ho un software che simula una tastiera elettronica con la tastiera del PC e ad ogni tasto premuto corrisponde un suono. Nel caso in cui premo un tasto che non fa parte della tastiera musicale deve essere emesso un suono di default.

Il codice più semplice e manuntenibile che mi viene in mente è questo:

  public class Sounds

  {

    private readonly Dictionary<Keys, Sound> m_sounds;

    private readonly Sound m_defaultSound;

 

    public Sounds(Sound defaultSound, Dictionary<Keys, Sound> sounds)

    {

      m_defaultSound = defaultSound;

      m_sounds = sounds;

    }

 

    public void Play(Keys keyCode)

    {

      if (m_sounds.ContainsKey(keyCode))

      {

        m_sounds[keyCode].Play();

      }

      else

      {

        m_defaultSound.Play();

      }

    }

  }

Nei commenti del Post di Luka ho visto delle idee per eliminare questo If:

Aggiungere tutti i possibili valori di Keys all'interno del Dictionary, in questo modo verrebbe associato il default sound a tutti i tasti che ora andrebbero nell'esle. Questa idea ha un pericoloso side effect: cosa succede se premo ad esempio ctrl+a? Avrei un eccezione. Inoltre è piuttosto laborioso inizializzare il dictionary con tutti i valory di keys anche usando la reflection.

Altra alternativa: descrivere una macchina a stati. Mi sembra uno strumento troppo sofisticato per questo semplice scenario.

Notare che queste due alternative sarebbero anche più complicate da testare. Mentre nel codice riportato sopra sono sufficienti due test.

Arriviamo quindi al punto. Quali sono gli if che conviene eliminare e quali no?

Gli che dipendono dal valore che assume una variabile vanno tolti, esempi che mi vengono in mente:

if sounds == null

if movieType == ...

Le condizioni che descrivono il comportamento del sistema vanno valutate caso per caso magari si valutano diverse alternative e si sceglie quella più mantenibile. Ora la domanda sorge spontanea cosa intendiamo per mantenibile?

In questo caso è la località dei comportamenti correlati. Immaginate di dover ricevere una richiesta di modifica in cui quando si preme shift devo suonare la nota un ottavo più alto quando tempo impiego ad individuare dove fare la modifica?

Print | posted on giovedì 22 aprile 2010 19:18 | Filed Under [ Tips ]

Feedback

Gravatar

# re: Ridurre la complessità del codice

nel mio piccolo condivido appieno.
Una piccola nota (ah ah) si dice ottava... a menco che non intendessi un ottavo di tono :D
22/04/2010 20:03 | Giancarlo
Gravatar

# re: Ridurre la complessità del codice

Io quell'if lo toglierei facendo in modo che il dizionario restituisca l'oggetto di default quando non c'è la chiave.

Di fatto sposterei l'if nel dizionario.
22/04/2010 20:47 | Riccardo
Gravatar

# re: Ridurre la complessità del codice

@Luca: credo intendessi senza if -->

private readonly Dictionary<Key, Sound> m_sounds;
private readonly KeyValuePair<Key, Sound> m_defaultSound;

public Sounds(Sound defaultSound, Dictionary<Key, Sound> sounds)
{
m_defaultSound = new KeyValuePair<Key, Sound>(null, defaultSound);
m_sounds = sounds;
}



public void Play(Key keyCode)
{
var sound = m_sounds.DefaultIfEmpty(m_defaultSound).FirstOrDefault(s => s.Key == keyCode);
sound.Value.Play();
}
22/04/2010 22:09 | Alessandro Scardova
Gravatar

# re: Ridurre la complessità del codice

Che poi io lo farei senza "l'odioso" Dictionary.

private readonly IEnumerable<Sound> m_sounds;

public Sounds(Sound defaultSound, IEnumerable<Sound> sounds)
{
m_sounds = sounds.DefaultIfEmpty(defaultSound);
}

public void Play(Key keyCode)
{
var sound = m_sounds.FirstOrDefault(s => s.Key == keyCode);
sound.Play();
}

22/04/2010 22:29 | Alessandro Scardova
Gravatar

# re: Ridurre la complessità del codice

Attenzione: Il codice che ho postato NON FUNZIONA, la DefaultIfEmpty va usata ovviamente dopo la where :D,
ecco la soluzione funzionante... sorry.

public class Sounds
{
private readonly IEnumerable<Sound> m_sounds;
private readonly Sound m_defaultSound;

public Sounds(Sound defaultSound, IEnumerable<Sound> sounds)
{
m_defaultSound = defaultSound;
m_sounds = sounds;
}

public void Play(ConsoleKey key)
{
var qry = m_sounds.Where(s => s.Key == key);
var sound = qry.DefaultIfEmpty(m_defaultSound).Single();
sound.Play();
}
}

l'idea di fondo era quella di usare Linq al posto della if.



22/04/2010 23:06 | Alessandro Scardova
Gravatar

# re: Ridurre la complessità del codice

A difesa di Linq però va detto che la prima esecuzione del programma di test è nettamente favorevole alla query Linq:

ContainsKey ->Time:5881
FirstOrDefault ->Time:1465
TryGetValue ->Time3:2628

nelle successive esecuzioni Linq rimane sostanzialmente sugli setessi valori, mentre i metodi nativi del Dictionary danno il meglio di sè:

ContainsKey ->Time:621
FirstOrDefault ->Time:1381
TryGetValue ->Time:259







23/04/2010 05:06 | Alessandro Scardova
Gravatar

# re: Ridurre la complessità del codice

>poi è vero dovresti voler maggior bene al dictionary che usando hashing è perfetto per operazioni di lookup ;)
Vero, l'amore trionfa sempre ;)
23/04/2010 11:13 | Alessandro Scardova
Gravatar

# re: Ridurre la complessità del codice

Apro il blog solo questa mattina non mi aspettavo così tanti commenti. :-)
Attendo la soluzione di Luka e poi pubblico le soluzioni mettendole a confronto.
23/04/2010 12:10 | Antonio Ganci
Gravatar

# re: Ridurre la complessità del codice

Io propongo questa architettura:

Sounds non è altro che una lista di suoni, per cui avrà come unica responsabilità quella di sapere selezionare tra i suoni:

public class Sounds
  {
    private readonly Dictionary<Keys, Sound> m_sounds;
    private readonly Sound m_defaultSound;

    public Sounds(Sound defaultSound, Dictionary<Keys, Sound> sounds)
    {
      m_defaultSound = defaultSound;
      m_sounds = sounds;
    }
 
    public void GetSoundBy(Keys keyCode)
    {
      if (m_sounds.ContainsKey(keyCode))
      {
        return m_sounds[keyCode];
      }

      return m_defaultSound.Play();
    }
  }

All'interno del software, chi prima chiamava play su sounds, chiamerà GetSoundBy e chiamerà play su quello che restituisce.

In questo modo si separano meglio le responsabilità; per esempio, chi chiama play non sa che esiste un suono di default.
Di fatto Sounds è un dizionario con un valore di default.
23/04/2010 12:48 | Riccardo
Gravatar

# re: Ridurre la complessità del codice

C'è un refuso: naturalmente l;ultimo return è

return m_defaultSound;

senza .Play...

23/04/2010 12:53 | Riccardo
Gravatar

# re: Ridurre la complessità del codice

Luca dai con sta soluzione, sono curioso!
Oltre al dictionary mi era venuto in mente di sfruttare le eccezioni, ma suppongo non valga.
23/04/2010 15:43 | Massimo
Gravatar

# re: Ridurre la complessità del codice

Luka ho provato ad implementare la tua soluzione ma non riesco a farla funzionare.
Il problema penso sia nella struct.
La classe Sound l'ho implementata così:
public class Sound
{
private readonly string m_note;

public Sound(string note)
{
m_note = note;
}

public void Play()
{
Console.WriteLine(m_note);
}
}
Ora non so bene come trasformarla in struct per farla funzionare perchè vorrei dare un default value a note.
24/04/2010 18:08 | Antonio Ganci
Gravatar

# re: Ridurre la complessità del codice

Luca, secondo me quel codice è troppo complicato.
Se per togliere un if devo fare codice più complesso, allora preferisco lasciare l'if, ovviamente isolato a dovere.

Quindi ha ragione Antonio! :)
25/04/2010 14:39 | Riccardo
Gravatar

# LESS IS BETTER

Come sempre: ...Less is better!
29/04/2010 12:56 | fremsoft
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET