2011-12-05 18 views
7

Ho diverse domande relative ai contratti di codice e alle best practice per il loro utilizzo. Diciamo che abbiamo una classe, con diverse proprietà (vedi sotto per esempio):Best practice del contratto di codice

class Class1 
{ 
    // Fields 
    private string _property1;  //Required for usage 
    private List<object> _property2; //Not required for usage 

    // Properties 
    public string Property1 
    { 
     get 
     { 
      return this._property1; 
     }    
     set 
     { 
      Contract.Requires(value != null); 
      this._property1 = value; 
     } 
    } 
    public List<object> Property2 
    { 
     get 
     { 
      return this._property2; 
     } 
     set 
     { 
      Contract.Requires(value != null); 
      this._property2 = value; 
     } 
    } 

    public Class1(string property1, List<object> property2) 
    { 
     Contract.Requires(property1 != null); 
     Contract.Requires(property2 != null); 

     this.Property1 = property1; 
     this.Property2 = property2; 
    } 

    public Class1(string property1) 
     : this(property1, new List<object>()) 
    { } 
} 

Qualche spiegazione su ciò che voglio raggiungere:

(a) proprietà1 è un campo obbligatorio. property2 non è richiesto esplicitamente per l'utilizzo normale dell'oggetto.

Ho le seguenti domande:

  1. Devo ancora perdere tempo con i contratti per Property2; perché property2 non è un campo obbligatorio, dovrebbe avere un contratto. Il collocamento di un contratto sulla proprietà2 indica che è in effetti richiesto per il normale utilizzo dell'oggetto;

  2. Anche se la proprietà2 non è richiesta esplicitamente, non vi è alcuna ragione possibile per essere nullo, quindi il contratto definito al setter. Non definire il contratto sulla proprietà2 riduce i controlli nulli nel codice chiamante? Questo dovrebbe ridurre i bug e migliorare la manutenibilità del codice: questa supposizione è corretta?

  3. Se è giusto, come faccio a garantire il codice di chiamata che proprietà2 non sarà mai nullo? Devo usare Contract.Invariant (property2! = Null); o Contract.Ensures (proprietà2! = null) nel costruttore, o Contract.Ensures (proprietà2! = null) in Init(), o Contract.Ensures (proprietà! = null) nel setter? (Ad esempio, se si utilizza Contract.Ensures (proprietà2! = null), dove viene inserito)?

Le mie scuse se le domande sembrano semplici. Sto solo cercando pensieri sull'argomento, e cosa voi considerate la migliore pratica.

+0

Sono d'accordo con il contratto sulla proprietà 2, per le ragioni che hai elencato. –

risposta

3

Questo è quello che mi sento di raccomandare per quanto Contratti:

class Class1 
    { 
     // Fields 
     private string _property1;  //Required for usage 
     private List<object> _property2; //Not required for usage 

     // Properties 
     public string Property1 
     { 
      get 
      { 
       Contract.Ensures(Contract.Result<string>() != null); 
       return this._property1; 
      } 
      set 
      { 
       Contract.Requires(value != null); 
       this._property1 = value; 
      } 
     } 

     public List<object> Property2 
     { 
      get 
      { 
       Contract.Ensures(Contract.Result<List<object>>() != null); 
       return this._property2; 
      } 
      set 
      { 
       Contract.Requires(value != null); 
       this._property2 = value; 
      } 
     } 

     public Class1(string property1, List<object> property2) 
     { 
      Contract.Requires(property1 != null); 
      Contract.Requires(property2 != null); 

      this.Property1 = property1; 
      this.Property2 = property2; 
     } 

     public Class1(string property1) 
      : this(property1, new List<object>()) 
     { 
      Contract.Requires(property1 != null); 
     } 

     [ContractInvariantMethod] 
     private void ContractInvariants() 
     { 
      Contract.Invariant(_property1 != null); 
      Contract.Invariant(_property2 != null); 
     } 
    } 

Le proprietà hanno i loro contratti comportamentali pubblici e gli invarianti prenderò eventuali errori che potrebbero introdurre in seguito come si aggiunge la logica per Class1 che potrebbe modificare i valori del campo e quindi violare gli appalti pubblici. In alternativa, se i campi possono essere fatti in sola lettura (e i setter rimossi), non sono necessari gli invarianti.

+0

Sarei d'accordo sul fatto che ce ne siano i setter. Dopo aver letto la documentazione negli esempi di contratto del codice, sembrerebbe che questo fosse il modo in cui gli sviluppatori lo intendevano. E sì agli invarianti che catturano le violazioni in seguito. Grazie, hai davvero aiutato la mia comprensione di questo. –

2

Penso che ci sia un sacco di preferenze personali a qui, ma i miei 2 cents ...

1) lo farei, e sarebbe forse la tentazione di regolare i costruttori di avere Property2 come argomento opzionale:

Class1(string property1, List<object> property2 = new List<object>())  
{  
    Contract.Requires(property1 != null);  
    Contract.Requires(property2 != null);  

    this.Property1 = property1;  
    this.Property2 = property2;  
} 

2) vedi 3

3) Prima che io sarei felice di ridurre i controlli nulli nel chiamare codice, io personalmente preferirei vedere un

Contract.Ensures(property2 != null) 

sul getter - Ho visto VS11 CTP mostra i contratti nelle descrizioni dei comandi per le definizioni, in modo da poter vedere questo vorrei quindi sapere che non ho bisogno di verificare la presenza di null. Terrò il Requires sul setter.

+0

Eccellente. Grazie –

1

Spesso quando si dispone di un elenco in un oggetto, l'oggetto si prenderà cura della creazione. Quindi, se un consumatore desidera aggiungere a una lista, deve prima ottenerlo. A seconda dell'utilizzo di questa classe, questa potrebbe essere la via migliore.

class Class1 
{ 
    // Fields 
    private string _property1;  //Required for usage 
    private List<object> _property2 = new List<object>(); //Not required for usage 

    // Properties 
    public string Property1 
    { 
     get 
     { 
      return this._property1; 
     }    
     set 
     { 
      Contract.Requires(value != null); 
      this._property1 = value; 
     } 
    } 
    public List<object> Property2 
    { 
     get 
     { 
      return this._property2; 
     } 
     //We don't have a setter. 
    } 

    public Class1(string property1) 
    { 
     Contract.Requires(property1 != null); 

     this.Property1 = property1; 
    } 
} 
+0

Grazie per le informazioni sull'aspetto dell'elenco. Era una delle mie preoccupazioni, è per questo che l'ho incluso come mia proprietà non richiesta. Cercavo principalmente consigli su come gestire i contratti per una proprietà "non essenziale", indipendentemente dal fatto che si tratti di un elenco o meno. Qualche commento? –

+0

Nel caso in cui non sia una "Collezione" seguirò il consiglio di Smudge. Creando un valore predefinito in Constructor e richiedendo che non sia nullo, indica al consumatore dell'API che, se lo forniscono, devono fornire un valore non nullo. –

1

1/2: Avere un contratto sulla proprietà2 è appropriato se questo è il comportamento che si desidera applicare vale a dire non dovrebbe essere nullo e ridurrà sicuramente la necessità di verifiche nulle e potenziali bug attorno a valori nulli.

3: Per rispondere a questa domanda che ho riscritto la tua classe come segue

class Class1 
{ 
    //Properties 
    public string Property1 { get; set; } 
    public List<object> Property2 { get; set; } 

    public Class1(string property1, List<object> property2) 
    { 
     Contract.Requires(property1 != null); 
     Contract.Requires(property2 != null); 

     Property1 = property1; 
     Property2 = property2; 
    } 

    public Class1(string property1) 
     : this(property1, new List<object>()) 
    { 
     Contract.Requires(property1 != null); 
    } 

    [ContractInvariantMethod] 
    private void ObjectInvariant() 
    { 
     Contract.Invariant(Property1 != null); 
     Contract.Invariant(Property2 != null); 
    } 
} 

Quando si dispone di auto implementato immobili a tua classe è possibile utilizzare Contract.Invariant(). Ciò rende i contratti espliciti nella proprietà ridondanti, quindi non è necessario il seguente codice ora.

public string Property1 
{ 
    get 
    { 
     Contract.Ensures(Contract.Result<string>() != null); 
     return this._property1; 
    }    
    set 
    { 
     Contract.Requires(value != null); 
     this._property1 = value; 
    } 
} 

Che si prenderà cura della protezione della proprietà. Per assicurarti che la proprietà non sia mai nulla, allora aggiungi un Contract.Requires (proprietà1! = Null) nel costruttore.

So che questa risposta è di 3 anni in ritardo ma potrebbe essere di qualche utilità per voi!

Fonte

: http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf