Quali sono i modi migliori per bilanciare le eccezioni informative e il codice pulito?

9

Con il nostro SDK pubblico, tendiamo a voler fornire messaggi molto istruttivi sul motivo per cui si verifica un'eccezione. Ad esempio:

if (interfaceInstance == null)
{
     string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    throw new InvalidOperationException(errMsg);
}

Tuttavia, questo tende a ingombrare il flusso del codice, in quanto tende a concentrarsi maggiormente sui messaggi di errore piuttosto che sul codice.

Un collega ha iniziato a rifattorizzare parte dell'eccezione lanciata a qualcosa del genere:

if (interfaceInstance == null)
    throw EmptyConstructor();

...

private Exception EmptyConstructor()
{
    string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    return new InvalidOperationException(errMsg);
}

Il che rende la logica del codice più facile da capire, ma aggiunge molti metodi extra per la gestione degli errori.

Quali sono altri modi per evitare il problema della "logica dell'ingombro dei messaggi a lunga eccezione"? Sto principalmente chiedendo di C # /. NET idiomatico, ma anche il modo in cui altri linguaggi lo gestiscono.

[Edit]

Sarebbe bello avere anche i pro ei contro di ciascun approccio.

    
posta FriendlyGuy 12.12.2013 - 17:39
fonte

6 risposte

8

Perché non hai classi speciali di eccezioni?

if (interfaceInstance == null)
{
    throw new ThisParticularEmptyConstructorException(<maybe a couple parameters>);
}

Ciò spinge la formattazione e i dettagli all'eccezione stessa e lascia la classe principale in ordine.

    
risposta data 12.12.2013 - 18:47
fonte
5

Microsoft sembra (guardando la fonte .NET) a volte usa le stringhe risorsa / ambiente. Ad esempio, ParseDecimal :

throw new OverflowException(Environment.GetResourceString("Overflow_Decimal"));

Pro:

  • Centralizzazione dei messaggi di eccezioni, consentendo il riutilizzo
  • Mantenimento del messaggio di eccezione (che non importa codificare) lontano dalla logica dei metodi
  • Il tipo di eccezione che viene lanciata è chiaro
  • I messaggi possono essere localizzati

Contro:

  • Se viene modificato un messaggio di eccezione, cambiano tutti
  • Il messaggio di eccezione non è così facilmente disponibile per il codice che genera l'eccezione.
  • Il messaggio è statico e non contiene informazioni su quali valori sono errati. Se vuoi formattarlo, è più confuso nel codice.
risposta data 12.12.2013 - 17:39
fonte
2

Per lo scenario dell'SDK pubblico, prenderemo seriamente in considerazione l'utilizzo di Contratti Microsoft Code in quanto forniscono informazioni errori, controlli statici e puoi anche generare documentazione da aggiungere ai documenti XML e Sandcastle generati file di aiuto. È supportato in tutte le versioni a pagamento di Visual Studio.

Un ulteriore vantaggio è che se i tuoi clienti utilizzano C #, possono sfruttare gli assembly di riferimento del contratto di codice per rilevare potenziali problemi anche prima di eseguire il loro codice.

La documentazione completa per i Contratti di codice è qui .

    
risposta data 07.02.2015 - 20:38
fonte
2

La tecnica che uso è quella di combinare e esternalizzare la convalida e il lancio di una funzione di utilità.

Il singolo vantaggio più importante è che è ridotto a un solo elemento nella logica di business .

Scommetto che non puoi fare di meglio se non riesci a ridurlo ulteriormente - per eliminare tutte le convalide degli argomenti e le guardie degli oggetti-stato dalla logica aziendale, mantenendo solo le condizioni operative eccezionali.

Esistono, naturalmente, modi per farlo: linguaggio strongmente tipizzato, design "nessun oggetto non valido consentito in qualsiasi momento", Design by Contract , ecc.

Esempio:

internal static class ValidationUtil
{
    internal static void ThrowIfRectNullOrInvalid(int imageWidth, int imageHeight, Rect rect)
    {
        if (rect == null)
        {
            throw new ArgumentNullException("rect");
        }
        if (rect.Right > imageWidth || rect.Bottom > imageHeight || MoonPhase.Now == MoonPhase.Invisible)
        {
            throw new ArgumentException(
                message: "This is uselessly informative",
                paramName: "rect");
        }
    }
}

public class Thing
{
    public void DoSomething(Rect rect)
    {
        ValidationUtil.ThrowIfRectNullOrInvalid(_imageWidth, _imageHeight, rect);
        // rest of your code
    }
}
    
risposta data 08.02.2015 - 01:05
fonte
0

[nota] Ho copiato questo dalla domanda in una risposta nel caso in cui ci siano commenti a riguardo.

Spostare ciascuna eccezione in un metodo della classe, includendo tutti gli argomenti che richiedono la formattazione.

private Exception EmptyConstructor()
{
    string errMsg = string.Format(
          "Construction of Action Argument: {0}, via the empty constructor worked, but type: {1} could not be cast to type {2}.",
          ParameterInfo.Name,
          ParameterInfo.ParameterType,
          typeof(IParameter)
    );

    return new InvalidOperationException(errMsg);
}

Racchiudi tutti i metodi di eccezione nella regione e posizionali alla fine della classe.

Pro:

  • Mantiene il messaggio fuori dalla logica di base del metodo
  • Consente di aggiungere informazioni logiche a ciascun messaggio (puoi passare argomenti al metodo)

Contro:

  • Disordine di metodo. Potenzialmente potresti avere molti metodi che restituiscono solo eccezioni e non sono realmente correlati alla logica di business.
  • Impossibile riutilizzare i messaggi in altre classi
risposta data 12.12.2013 - 18:57
fonte
0

Se riesci a cavartela con errori un po 'più generali, potresti scrivere per te una funzione di generazione generica statica pubblica, che deduce il tipo di sorgente:

public static I CastOrThrow<I,T>(T t, string source)
{
    if (t is I)
        return (I)t;

    string errMsg = string.Format(
          "Failed to complete {0}, because type: {1} could not be cast to type {2}.",
          source,
          typeof(T),
          typeof(I)
        );

    throw new InvalidOperationException(errMsg);
}


/// and then:

var interfaceInstance = SdkHelper.CastTo<IParameter>(passedObject, "Action constructor");

Ci sono variazioni possibili (pensa SdkHelper.RequireNotNull() ), che controlla solo i requisiti sugli input, e lancia se falliscono, ma in questo esempio la combinazione del cast con la produzione del risultato è autodocumentazione e compatta.

Se si è su .net 4.5, ci sono modi per fare in modo che il compilatore inserisca il nome del metodo / file corrente come parametro del metodo (si veda CallerMemberAttibute ). Ma per un SDK, probabilmente non puoi richiedere ai tuoi clienti di passare a 4.5.

    
risposta data 12.12.2013 - 21:03
fonte

Leggi altre domande sui tag