Se un getter lancia un'eccezione se il suo oggetto ha uno stato non valido?

54

Spesso mi imbatto in questo problema, specialmente in Java, anche se penso che sia un problema generale di OOP. Ovvero: sollevare un'eccezione rivela un problema di progettazione.

Supponiamo di avere una classe con un campo String name e un campo String surname .

Quindi utilizza questi campi per comporre il nome completo di una persona per visualizzarla su una sorta di documento, ad esempio una fattura.

public void String name;
public void String surname;

public String getCompleteName() {return name + " " + surname;}

public void displayCompleteNameOnInvoice() {
    String completeName = getCompleteName();
    //do something with it....
}

Ora voglio rafforzare il comportamento della mia classe generando un errore se il displayCompleteNameOnInvoice viene chiamato prima che il nome sia stato assegnato. Sembra una buona idea, vero?

Posso aggiungere un codice di aumento delle eccezioni al metodo getCompleteName . Ma in questo modo sto violando un contratto "implicito" con l'utente della classe; in generale, i getter non devono generare eccezioni se i loro valori non sono impostati. Ok, questo non è un getter standard dal momento che non restituisce un singolo campo, ma dal punto di vista dell'utente la distinzione potrebbe essere troppo sottile per pensarci.

O posso lanciare l'eccezione dall'interno di displayCompleteNameOnInvoice . Ma per farlo dovrei testare direttamente i campi name o surname e così facendo violerei l'astrazione rappresentata da getCompleteName . È compito di questo metodo controllare e creare il nome completo. Potrebbe persino decidere, basando la decisione su altri dati, che in alcuni casi sia sufficiente il surname .

Quindi l'unica possibilità sembra cambiare la semantica del metodo getCompleteName in composeCompleteName , che suggerisce un comportamento più 'attivo' e, con esso, la possibilità di lanciare un'eccezione.

Questa è la soluzione di design migliore? Sono sempre alla ricerca del miglior equilibrio tra semplicità e correttezza. C'è un riferimento al design per questo problema?

    
posta AgostinoX 02.11.2014 - 16:00
fonte

6 risposte

149

Non permettere che la tua classe sia costruita senza assegnare un nome.

    
risposta data 02.11.2014 - 16:56
fonte
74

La risposta fornita da DeadMG praticamente lo inchioda, ma lascia che lo esprima in modo un po 'diverso: evita di avere oggetti con stato non valido. Un oggetto dovrebbe essere "intero" nel contesto del compito che soddisfa. O non dovrebbe esistere.

Quindi se vuoi la fluidità, usa qualcosa come Builder Pattern . O qualsiasi altra cosa che sia una reificazione separata di un oggetto in costruzione rispetto alla "cosa reale", dove quest'ultimo è garantito per avere uno stato valido ed è quello che espone effettivamente le operazioni definite su quello stato (ad esempio getCompleteName ).

    
risposta data 02.11.2014 - 18:39
fonte
39

Non avere un oggetto con uno stato non valido.

Lo scopo di un costruttore o costruttore è di impostare lo stato dell'oggetto su qualcosa che sia coerente e utilizzabile. Senza questa garanzia, i problemi si presentano con uno stato non correttamente inizializzato negli oggetti. Questo problema si complica se hai a che fare con la concorrenza e qualcosa può accedere all'oggetto prima che tu abbia completamente finito di configurarlo.

Quindi, la domanda per questa parte è "perché stai permettendo che il nome o il cognome siano nulli?" Se questo non è qualcosa che è valido nella classe perché funzioni correttamente, non permetterlo. Avere un costruttore o un costruttore che validamente convalida la creazione dell'oggetto e, se non è giusto, sollevare il problema a quel punto. Potresti anche utilizzare una delle @NotNull annotazioni esistenti per comunicare che "questo non può essere nullo" e applicare in codifica e analisi.

Con questa garanzia, diventa molto più facile ragionare sul codice, su ciò che fa, e non deve gettare eccezioni in luoghi strani o mettere controlli eccessivi attorno alle funzioni getter.

Getter che fanno di più.

C'è un bel po 'là fuori su questo argomento. Hai Quanta logica in Getters e Cosa dovrebbe essere permesso all'interno di getter e setter? da qui e getter e setter che eseguono una logica aggiuntiva su Overflow dello stack. Questo è un problema che emerge ancora e ancora nel design della classe.

Il nucleo di questo viene da:

in general getters aren't supposed to throw exceptions if their values aren't set

E hai ragione. Non dovrebbero Dovrebbero sembrare "stupidi" nel resto del mondo e fare ciò che ci si aspetta. Mettendo troppa logica ci conduce a questioni in cui viene violata la legge del minimo stupore. E lasciamolo fronteggiare, davvero non vuoi fare il wrapping dei getter con un try catch perché sappiamo quanto ci piace farlo in generale.

Ci sono anche situazioni in cui devi usare un metodo getFoo come il framework JavaBeans e quando hai qualcosa da EL chiamando in attesa di ottenere un bean (in modo che <% bar.foo %> invochi effettivamente getFoo() nella classe, mettendo da parte 'il bean dovrebbe fare la composizione o dovrebbe essere lasciato alla vista? ', perché è facile arrivare a situazioni in cui l'una o l'altra può chiaramente essere la risposta giusta)

Comprendi anche che è possibile specificare un determinato getter in un'interfaccia o essere parte dell'API pubblica precedentemente esposta per la classe che viene sottoposta a refactoring (una versione precedente aveva solo 'completeName' che era stata restituita e un refactoring lo ha suddiviso in due campi).

Alla fine della giornata ...

Fai ciò che è più facile ragionare. Trascorrerai più tempo a conservare questo codice di quanto lo spenderesti a progettarlo (anche se meno tempo dedichi la progettazione, più tempo passerai a mantenere). La scelta ideale è progettarla in modo tale che non ci vorrà più tempo per mantenerla, ma non stare lì a pensarci per giorni - a meno che questa sia davvero una scelta di design che farà ha giorni di implicazioni più tardi.

Cercando di restare con la purezza semantica del getter che è private T foo; T getFoo() { return foo; } ti metterà nei guai a un certo punto. A volte il codice non si adatta a quel modello e le contorsioni che si verificano per cercare di mantenerla in quel modo non hanno senso ... e in ultima analisi rendono più difficile la progettazione.

Accetta a volte che gli ideali del design non possono essere realizzati nel modo in cui li desideri nel codice. Le linee guida sono linee guida, non catene.

    
risposta data 02.11.2014 - 22:20
fonte
5

Non sono un ragazzo di Java, ma questo sembra aderire a entrambi i vincoli che hai presentato.

getCompleteName non lancia un'eccezione se i nomi non sono inizializzati e displayCompleteNameOnInvoice lo fa.

public String getCompleteName()
{
    if (name == null || surname == null)
    {
        return null;
    }
    return name + " " + surname;
}

public void displayCompleteNameOnInvoice() 
{
    String completeName = getCompleteName();
    if (completeName == null)
    {
        //throw an exception.
    }
    //do something with it....
}
    
risposta data 02.11.2014 - 17:22
fonte
4

Sembra che nessuno stia rispondendo alla tua domanda.
A differenza di ciò che piace alla gente, "evitare oggetti non validi" non è spesso una soluzione pratica.

La risposta è:

Sì, dovrebbe.

Esempio semplice: FileStream.Length in C # /. NET , che genera NotSupportedException se il file non è ricercabile.

    
risposta data 04.11.2014 - 08:48
fonte
1

L'intero nome del controllo è capovolto.

getCompleteName() non deve sapere quali funzioni lo utilizzeranno. Pertanto, non avere name quando si chiama displayCompleteNameOnInvoice() non è responsabilità di getCompleteName() s.

Tuttavia, name è un chiaro prerequisito per displayCompleteNameOnInvoice() . Pertanto, dovrebbe assumersi la responsabilità di controllare lo stato (o dovrebbe delegare la responsabilità di controllare un altro metodo, se preferisci).

    
risposta data 05.11.2014 - 10:43
fonte

Leggi altre domande sui tag