quanto dovrebbe essere complesso un costruttore

18

Sto discutendo con il mio collega su quanto lavoro può fare un costruttore. Ho una classe, B che richiede internamente un altro oggetto A. L'oggetto A è uno dei pochi membri che la classe B deve fare il suo lavoro. Tutti i suoi metodi pubblici dipendono dall'oggetto interno A. Le informazioni sull'oggetto A sono memorizzate nel DB, quindi cerco di convalidarlo e ottenerlo esaminandolo sul DB nel costruttore. Il mio collega ha sottolineato che il costruttore non dovrebbe fare molto lavoro se non quello di acquisire i parametri del costruttore. Poiché tutti i metodi pubblici fallirebbero comunque se l'oggetto A non viene trovato usando gli input per il costruttore, ho sostenuto che invece di consentire a un'istanza di essere creata e successivamente fallire, in realtà è meglio lanciare presto il costruttore. Non tutte le classi lo fanno, ma ho scoperto che il costruttore StreamWriter genera anche se ha problemi ad aprire un file e non ritarda la convalida fino alla prima chiamata a Write.

Cosa pensano gli altri? Sto usando C # se questo fa alcuna differenza.

Lettura C'è mai un motivo per fare tutto il lavoro di un oggetto in un costruttore? mi chiedo se il recupero dell'oggetto A andando a DB sia parte di" qualsiasi altra inizializzazione necessaria per rendere l'oggetto pronto per l'uso "perché se l'utente ha superato con valori errati per il costruttore, non sarei in grado di utilizzare nessuno dei suoi metodi pubblici.

Constructors should instantiate the fields of an object and do any other initialization necessary to make the object ready to use. This is generally means constructors are small, but there are scenarios where this would be a substantial amount of work.

    
posta Taein Kim 13.01.2014 - 19:45
fonte

3 risposte

19

La tua domanda è composta da due parti completamente separate:

Devo gettare un'eccezione dal costruttore, o dovrei avere un metodo fallito?

Questa è chiaramente l'applicazione del principio Fail-fast . Fare fallire il costruttore è molto più facile eseguire il debug rispetto al dover trovare il motivo per cui il metodo non funziona. Ad esempio, è possibile ottenere l'istanza già creata da un'altra parte del codice e si verificano errori durante la chiamata dei metodi. È ovvio che l'oggetto è stato creato in modo errato? No.

Come per il problema "wrap the call in try / catch". Le eccezioni devono essere eccezionali . Se si conosce che un codice genererà un'eccezione, non si esegue il wrapping del codice in try / catch, ma si convalidano i parametri di quel codice prima di eseguire il codice che può generare un'eccezione. Le eccezioni sono intese solo come modo per garantire che il sistema non entri in stato non valido. Se si sa che alcuni parametri di input possono portare a uno stato non valido, ci si assicura che tali parametri non si verifichino mai. In questo modo, devi solo provare / catturare in posti, dove puoi gestire logicamente l'eccezione, che di solito è al limite del sistema.

Posso accedere a "altre parti del sistema, come DB" dal costruttore.

Penso che ciò vada contro principio di minimo stupore . Non molte persone si aspetterebbero che il costruttore acceda a un DB. Quindi no, non dovresti farlo.

    
risposta data 13.01.2014 - 19:51
fonte
16

Ugh.

Un costruttore dovrebbe fare il meno possibile - il problema è che il comportamento delle eccezioni nei costruttori è scomodo. Pochissimi programmatori sanno qual è il comportamento corretto (compresi gli scenari di ereditarietà) e costringere gli utenti a provare / catturare ogni singola istanziazione è ... doloroso nella migliore delle ipotesi.

Ci sono due parti in questo, in il costruttore e usando il costruttore. È scomodo nel costruttore perché non si può fare molto lì quando si verifica un'eccezione. Non puoi restituire un tipo diverso. Non puoi restituire nulla. In pratica puoi rilanciare l'eccezione, restituire un oggetto danneggiato (cattivo constuctor) o (caso migliore) sostituire una parte interna con un predefinito corretto. Usare il costruttore è scomodo perché allora le tue istanziazioni (e le istanze di tutti i tipi derivati!) possono essere lanciate. Quindi non è possibile utilizzarli negli inizializzatori dei membri. Finisci utilizzando le eccezioni per la logica per vedere "la mia creazione ha avuto successo?", Oltre alla leggibilità (e fragilità) causata da try / catch ovunque.

Se il tuo costruttore sta facendo cose non banali, o cose che possono ragionevolmente aspettarsi di fallire, considera un metodo Create statico (con un costruttore non pubblico). È molto più utilizzabile, più facile da eseguire il debug e ti consente comunque di ottenere un'istanza completamente costruita quando ha esito positivo.

    
risposta data 13.01.2014 - 19:55
fonte
1

Costruttore - l'equilibrio della complessità del metodo è davvero una questione di discussione sul buon stile. Molto spesso viene utilizzato il costruttore Class() {} vuoto, con un metodo Init(..) {..} per cose più complicate. Tra gli argomenti da prendere in considerazione:

  • Possibilità di serializzazione della classe
  • Con il metodo di separazione Init del costruttore, spesso è necessario ripetere la stessa sequenza Class x = new Class(); x.Init(..); molte volte, parzialmente in Test unità. Non così buono, tuttavia, chiaro per la lettura. A volte, li inserisco in una sola riga di codice.
  • Quando l'obiettivo del test unitario è solo un test di inizializzazione della classe, meglio avere il proprio init, per eseguire azioni più complicate soddisfatte una per una, con gli Assert intermedi.
risposta data 10.11.2017 - 08:43
fonte

Leggi altre domande sui tag