Devo eseguire il controllo nullo nella classe base o nella classe derivata c #?

4

Ho una classe base che ha un metodo astratto;

public abstract void DoSomething(ClassA obj);

Ho diverse classi derivate ciascuna che implementa questo metodo e ciascuna verifica obj per null (non c'è caso in nessuna classe derivata in cui questa dovrebbe mai essere nullo) ad es.

public override void DoSomething(ClassA obj)
{
    if(obj == null) throw new NullReferenceException();
    //Do other stuff...
}

Mi è venuto in mente che potevo refactoring questo;

public void DoSomething(ClassA obj)
{
    if(obj == null) throw new NullReferenceException();
    NextThing(obj)
}
protected abstract void NextThing(ClassA obj);

Ora non devo scrivere un controllo nullo per ogni implementazione perché NextThing è "garantito" per avere un obj nulla nullo, ma solo se NextThing viene chiamato sempre solo da DoSomething. È una buona idea o dovrei continuare come sono?

    
posta mark_h 02.08.2017 - 13:33
fonte

3 risposte

4

È una regola generale che i controlli di validità sugli argomenti dovrebbero essere eseguiti in qualsiasi metodo pubblico di qualsiasi classe pubblica (visibile ad altri assembly). Se vuoi essere ancora più difensivo sulla validità degli argomenti, controlla gli argomenti di:

  • metodi protetti di classi pubbliche
  • metodi pubblici delle classi interne
  • metodi protetti delle classi interne

L'approccio che hai suggerito è un ottimo esempio e un uso eccellente di Modello metodo modello . Il motivo principale per cui dico questa è la frase della tua domanda:

I have several derived classes each implementing this method and each checks obj for null (there is no case in any derived class where this should ever be null)

Quindi, hai lo stesso modello in tutte le classi derivate:

  1. Esegui un controllo
  2. Fai qualche azione

Hai semplicemente usato lo schema esistente e separato le responsabilità del metodo pubblico, che è una buona pratica.

L'unico svantaggio di questo approccio è se ti capita di imbattersi in una situazione in cui è giusto che una classe derivata riceva un argomento nullo. Personalmente, l'unica situazione in cui vedo questo come uno scenario valido è se esiste un valore predefinito valido per l'argomento che deve essere fornito dalla classe derivata se l'argomento è null. In tal caso, puoi flirtare con la rottura di YAGNI e fare qualcosa del genere:

public abstract class BaseClass
{  
    public void DoSomething(ClassA obj)
    {
        ArgumentCheck(obj);
        NextThing(obj);
    }

    protected virtual void ArgumentCheck(ClassA obj)
    {
        if(obj == null) 
        {
            throw new ArgumentNullException();
        }
    }

    protected abstract void NextThing(ClassA obj);
}

Quindi, se si presenta tale necessità, puoi sempre sovrascrivere il metodo ArgumentCheck(ClassA obj) per non generare eccezioni in una particolare classe. L'aspetto positivo è che puoi seguire l'approccio originale e, in caso di necessità, puoi eseguire questo tipo di refactoring senza influenzare le altre classi.

    
risposta data 02.08.2017 - 16:16
fonte
6

Una vera risposta alla tua domanda deve occuparsi di altre domande alle quali solo tu puoi rispondere:

  • Qual è l'uso previsto della tua classe base?
  • Il tuo base.DoSomething(someProp) dovrebbe essere chiamato prima?
  • In che modo la classe base gestisce il parametro null?
  • È nullo un valore valido? In alcuni casi lo è, quindi il lancio di eccezioni sarebbe sbagliato.

Dopo fornirò alcune linee guida che aiuteranno durante il debug:

Non devi sempre generare eccezioni

Fornire valori predefiniti può essere un buon modo per mantenere il sistema stabile, in particolare nei casi in cui il parametro potrebbe essere intenzionalmente nullo. In quei casi potrebbe essere meglio semplicemente tornare immediatamente. Le eccezioni sono costose e sono spesso abusate.

A volte si può fare bene con Debug.Assert() per rendere dolorosamente ovvio che c'è qualcosa di sbagliato nella build di sviluppo, che dà anche al programmatore la possibilità di entrare nel codice con il debugger con lo stack corrente. Nella build di produzione questi controlli spariscono, il che può accelerare il tempo di esecuzione - basta fare attenzione che le asserzioni non abbiano effetti collaterali a cui si sta dipendendo in fase di esecuzione.

Pubblica l'eccezione appropriata

Quando si verifica un parametro e si trova null, è necessario throw new System.ArgumentNullException(nameof(parameter)); . Questo aiuta a rimuovere accidentalmente NullReferenceException s dalle cose che hai controllato.

Quando i parametri richiedono più restrizioni del semplice esistente (ovvero devono essere un numero positivo maggiore di 0, ecc.), quindi lanciare l'appropriato ArgumentException che aiuta l'utente a capire cosa non va.

Fornire il nome del parametro a ArgumentNullException accelera notevolmente il debug, in particolare quando ci sono più parametri. L'eccezione genera un messaggio di facile lettura che include il nome del parametro che hai fornito e una traccia stack che porta al metodo chiamato. Tutto ciò che puoi fare per ridurre al minimo il tempo in un debugger ti aiuterà a risolvere i problemi più velocemente.

Assicurati che il parametro non sia null prima di accedervi

Ogni programmatore è responsabile di assicurarsi che il parametro sia valido prima di fare qualcosa con esso. Se fai sapere che la tua classe base è destinata ad essere chiamata per prima e gestisce i parametri nulli, ciò non giustifica i programmatori che creano sottoclassi dal controllo di null se stessi se violano tale aspettativa.

Progetta per il debug

Fai tutto il possibile per garantire che il tuo codice sia facile da eseguire il debug e mantenere il più possibile. Il fatto è che la parte più difficile nel correggere bug è trovare dove si trovano in primo luogo. Spesso questo è un compromesso tra lanciare eccezioni e fare il meglio che può quando il parametro fornito è nullo.

Personalmente ritengo che la sottoclasse sia sovrautilizzata e possa portare a casi d'uso imprevedibili perché i client del codice possono avere accesso a determinati stati di classe e fare cose che non sono mai state pensate per iniziare. Se utilizzi la sottoclasse, imposta i punti di estensione abstract . Se la composizione può ottenere ciò che desideri, potrebbe essere un modello migliore e più prevedibile per estendere le funzionalità di base.

Rendi i punti di estensione abstract anziché virtual

Si vede questo schema dappertutto nell'API WinForms. Hai il metodo che promette di fare qualcosa, come l'aggiornamento e il ridisegno. La classe base ha un puro metodo abstract con il prefisso Do davanti in questo modo:

public abstract class MyBaseClass
{
    public void UpdateSomething(ClassA parameter)
    {
        if (parameter == null) throw new ArgumentNullException(nameof(parameter));

        DoUpdateSomething(parameter);
    }

    protected abstract void DoUpdateSomething(ClassA parameter);
}

Questo approccio ti consente di eseguire tutti i controlli di integrità nel metodo wrapper e di chiamare l'implementazione della sottoclasse solo quando tutto è valido. Questo sarà l'unico modo per garantire che gli opportuni controlli siano fatti nell'ordine giusto e dare agli utenti della tua classe base la certezza che lo stanno usando correttamente.

    
risposta data 02.08.2017 - 15:25
fonte
3

Se si utilizza un riferimento null in modo errato si otterrà comunque l'eccezione, quindi perché controllare? Concentrati per assicurarti che tutte le funzioni gestiscano eccezioni di qualsiasi tipo e non lasciare il tuo sistema in uno stato incoerente o illegale. In questo modo non è necessario controllare i valori nulli o qualsiasi altra cosa come file mancanti ecc. Si generano eccezioni quando lo stato del dominio è in errore, ma non mi preoccuperei di queste banalità. È essenziale la pulizia e la propagazione.

    
risposta data 02.08.2017 - 16:56
fonte

Leggi altre domande sui tag