Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Eccezioni in C#: Critiche Progettuali

Posted by Ricibald on January 23rd, 2008

In generale apprezzo come C# è stato concepito, ma una cosa che veramente non capisco è la gestione delle eccezioni.

In Java esiste la distinzione utilissima tra eccezioni checked e non. Le eccezioni checked consentono di forzare l’utilizzatore di un metodo a gestire o rilanciare solo le eccezioni dichiarate: niente di più, niente di meno. In C#, invece, tutte le eccezioni sono unchecked e questo pone il problema:

  • quali eccezioni devo gestire?
  • come forzare a sollevare solo determinati tipi di eccezione?

Si consideri infatti il codice Java:

public class Firefox {
      public void activatePlugins() {
            for(FirefoxPluginInterface plugin in this.plugins) {
                  try {
                        plugin.activate();
                  } catch (FirefoxPluginActivationException myEx) {
                        FirefoxHelper.manageActivationFromError(myEx);
                  } catch (Exception ex) {
                        FirefoxHelper.showError(ex);
                  }
           }
      }
}

public interface FirefoxPluginInterface {
     public void activate() throws FirefoxPluginActivationException;
}

Si consideri invece il codice C# analogo:

public class Firefox
{
      public void ActivatePlugins()
      {
            foreach(FirefoxPluginInterface plugin : _plugins)
            {
                  try
                  {
                        plugin.Activate();
                  }
                  catch (FirefoxPluginActivationException myEx)
                  {
                        FirefoxHelper.ManageActivationFromError(myEx);
                  }
                  catch (Exception ex)
                  {
                        FirefoxHelper.ShowError(ex);
                  }
           }
      }
}

public interface FirefoxPluginInterface {
     public void Activate();
}

Non siamo in grado di essere sicuri che tutte le persone che scriveranno plugin faranno buon uso della nostra eccezione. Speriamo in qualche modo nella bontà di chi scriverà… Una buona pezza era stata fornita nel framework 1.1 di .NET, inserendo la classe ApplicationException: in teoria l’utente che creava le proprie personali eccezioni doveva ereditare da questa classe, e non da Exception. In questo modo si era in grado di discriminare se l’eccezione apparteneva alle standard o se era una che avevamo lanciato noi. Successivamente, come riporta MSDN:

Per la maggior parte delle applicazioni, derivare le eccezioni personalizzate dalla classe Exception . L’idea iniziale di far derivare le eccezioni personalizzate dalla classe ApplicationException non ha prodotto alcun valore significativo in termini pratici.

Questa frase e questa organizzazione delle eccezioni mi lascia un po’ interdetto…

Esiste anche il problema della propagazione delle eccezioni. L’architettura di un sistema software è infatti normalmente definita a livelli o Layer: ogni livello rappresenta un diverso livello di astrazione. Il messaggio viene quindi passato attraverso vari livelli, in cui ad ogni livello eventuali errori devono essere riscritti a seconda del livello di astrazione. Ad esempio, un eccezione SQL al livello “Database” deve essere avvolta in una eccezione di livello più alto nel livello “Dominio” (ad es. una generica eccezione nella memorizzazione dei dati). In questa comunissima pratica ingegneristica abbiamo però alcuni problemi. Immaginiamo questa situazione:

try
{
}
catch (SQLException sqlEx)
{
    throw new PersistenceException("Errore nel salvataggio", sqlEx);
}
catch (Exception ex)
{
    throw new UnknownException("Errore sconosciuto durante il salvataggio nell'inserimento", ex);
}

L’eccezione PersistenceException viene ricatturata nel blocco “catch(Exception)”, comportamento assurdo in un codice Java, dove l’eccezione non sarebbe stata mai ricatturata. Per correggere questo comportamento si può eseguire un unico catch e il resto rilanciare:

try
{
}
catch (SQLException sqlEx)
{
    throw new PersistenceException("Errore nel salvataggio", sqlEx);
}
catch (Exception ex)
{
    // throw ex; NON UTILIZZARE: PERDO LO STACKTRACE!!
    throw; // OK!
}

Notare inoltre un altro problema: il “throw ex” causa la perdita di informazioni nello stacktrace: non parte più dall’origine dell’errore ma dall’origine del “throw ex”.

Realizzato il comportamento appena descritto nel codice, sarà il livello superiore che al momento della stampa del messaggio:

try
{
}
catch (Exception ex)
{
    string message = null;
    if(ex is PersistenceException) 
    {
        message = ex.Message;
    } 
    else if (ex is Exception)
    {
        message = "Errore sconosciuto durante il salvataggio nell'inserimento";
    }
    Trace.WriteLine(message);
}

Che cose articolate stranamente per realizzare un comportamento del tutto normale…

Leave a Reply

You must be logged in to post a comment.