Convalida degli argomenti in Costruttore

2

In generale, mi piace favorire l'immutabilità laddove possibile e non consentire che gli oggetti vengano creati in uno stato non valido o entrino in uno stato non valido. A volte però sembra che potrebbe andare troppo lontano.

Esempio: ho un oggetto dominio utente (entità JPA / Hibernate). Autorizzo solo la costruzione dell'oggetto in uno stato valido, tutti i campi obbligatori devono essere utilizzati per creare un'istanza dell'oggetto e non possono essere NULL . Su oggetti più grandi, a volte questo può rendere il costruttore rumoroso con un sacco di convalida in corso. In questo caso, sto solo convalidando che gli argomenti non sono nulli. Non sto verificando che il nome utente sia compreso tra 5 e 30 caratteri o qualsiasi altra regola che potrebbe essere associata alle proprietà della classe.

È ragionevole fare quanto segue? Sarebbe sensato fare un ulteriore passo avanti e garantire che gli argomenti soddisfino anche qualsiasi convalida di lunghezza, min, max, formato? O sta facendo un argomento come @Nonnull abbastanza buono?

Nei casi in cui ci sono setter, dovrebbe essere presente la stessa logica di validazione.

@Entity
@Table(name = "user")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  @NotBlank
  @Column(name = "user_name", unique = true)
  private String userName;

  @Column(name = "password")
  private String password;

  @Email
  @NotBlank
  @Column(name = "email")
  private String email;

  @Column(name = "locked")
  private boolean locked;

  @Enumerated(EnumType.STRING)
  @Column(name = "role")
  private Role role;

  @ManyToOne(optional = true, fetch = FetchType.LAZY)
  @JoinColumn(name = "dealer_id", referencedColumnName = "id")
  private Dealer delaer;

  @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
  private List<EventLog> events;

  protected User() {
    this.events = new ArrayList<>();
  }

  /**
   * Constructor for User.
   *
   * @param userName Username of the user
   * @param password Encrypted password
   * @param email Email of the user
   * @param locked Determines if user account if locked or not
   */
  public User(@Nonnull String userName, @Nonnull String password, @Nonnull String email, boolean locked, @Nonnull Role role) {

    this();

    Preconditions.checkArgument(userName != null);
    Preconditions.checkArgument(password != null);
    Preconditions.checkArgument(email != null);
    Preconditions.checkArgument(role != null);

    this.userName = userName;
    this.password = password;
    this.email = email;
    this.locked = locked;
    this.role = role;
  }
}
    
posta greyfox 24.07.2017 - 19:43
fonte

3 risposte

3

Mi piace anche favorire l'immutabilità. Tuttavia, in alcuni casi, l'immutabilità ha un limite di prestazioni (se gli oggetti devono essere modificati spesso, l'immutabilità significa che deve essere costruito un nuovo oggetto per ogni modifica, causando i sovraccarichi della raccolta di dati inutili). Quindi considera attentamente le prestazioni rispetto agli aspetti di correttezza di verifica del tuo software. I punti caldi del tuo programma dovrebbero utilizzare oggetti mutabili per motivi di prestazioni.

Stai facendo le cose esattamente nello stesso modo in cui vorrei farlo. È molto meglio verificare in costruttore che l'argomento non sia nullo piuttosto che rischiare di avere una NullPointerException più tardi quando la vera sorgente del puntatore nullo è difficile da rintracciare.

Aggiungi controlli di lunghezza, ecc. se usi un tipo di dati varchar a lunghezza limitata per questi campi nel database.

L'unico controllo che tralascio qui è il controllo della sicurezza della password. Ovviamente, dovresti avere un controllo per l'entropia della password apparente, ma il calcolo di tale entropia potrebbe essere così dispendioso in termini di tempo che lo aggiungerei altrove. Non dimenticare di inserire la password entropy check altrove, però! È molto importante!

    
risposta data 24.07.2017 - 19:58
fonte
3

È difficile dirlo con così poco contesto. Ma guardalo da questo punto di vista.

Is it reasonable to do the following? Would it be sensible to take it a step further and ensure the arguments also meet any length, min, max, format validation?

Sarebbe coerente . Se la convalida dei vincoli mancanti determina una violazione dell'integrità dei dati, perché controllare solo il 50% di essi?

Inoltre, se istanziamo il User a mano, al momento in cui Java Le convalide dei bean entrano in gioco , l'istanza è già in uno stato non valido.

A causa del costruttore pubblico, suppongo che potremmo inizializzare utenti non validi da qualsiasi luogo. Dovrai decidere se è un problema o no.

In cases where there are setters the same validation logic would need to be present as well.

Sì. tuttavia, se li rendi final puoi chiamare i setter dal costruttore, ciò che ti consente di posizionare le convalide in un'unica posizione.

Oltre alla risposta di juhist, la convalida del formato della password è ancora possibile dove è ora.

Le convalide dei bean Java ci forniscono i vincoli di convalida incorporati come @Pattern o ci consente di creare i nostri vincoli personalizzati .

    
risposta data 25.07.2017 - 00:10
fonte
0

Eseguire la convalida nel costruttore può rendere difficile fare l'unittesting perché obbliga a creare oggetti completamente validi ogni volta anche se non è necessario per un determinato test unitario.

Esempio: se vuoi testare la funzione Customer.isOlderThan18() (cioè il Cliente è autorizzato ad acquistare sigarette). Pieno valido richiederebbe che il tuo test Cliente debba ottenere una password complicata valida. Ciò rende inutili i test complicati.

Se desideri veramente oggetti immutabili, puoi eseguire la convalida in un builder .

Spesso ci sono definizioni di business multibe diverse valide: isAllowedToLogin (username, password), canBuyCigaretts (age), canDoOrders (ha abbastanza soldi) ....

    
risposta data 25.07.2017 - 13:11
fonte

Leggi altre domande sui tag