Perché dovrei usare i contratti di codice

23

Recentemente sono incappato nella struttura di Microsoft per i contratti di codice.

Ho letto un po 'di documentazione e mi sono trovato costantemente a chiedere: "Perché dovrei mai volerlo fare, perché non lo fa e spesso non è possibile eseguire un'analisi statica."

Ora, ho già una sorta di stile di programmazione difensivo, con eccezioni di protezione come questa:

if(var == null) { throw new NullArgumentException(); }

Sto usando anche NullObject Pattern e ho raramente problemi. Aggiungi Test unità a questo e sei tutto pronto.

Non ho mai usato asserti e non li ho mai persi. Al contrario. Odio davvero il codice che ha un sacco di asserzioni senza senso, che è solo rumore per me e mi distrae da ciò che voglio veramente vedere. Contratti di codice, almeno il modo Microsoft sono più o meno gli stessi - e anche peggio. Aggiungono un sacco di rumore e complessità al codice. Nel 99% comunque verrà lanciata un'eccezione, quindi non mi interessa se proviene dall'assert / contratto o dal problema reale. Solo pochissimi casi rimangono in cui gli stati del programma diventano realmente corrotti.

Quindi, francamente, qual è il vantaggio di utilizzare i contratti di codice? C'è del tutto? Se usi già test unitari e codice in modo difensivo, ritengo che l'introduzione di contratti non vale il costo e mette il rumore nel tuo codice che un manutentore maledirà quando aggiornerà quel metodo, proprio come faccio quando non riesco a vedere cosa sta facendo il codice a causa di affermazioni inutili. Devo ancora vedere una buona ragione per pagare quel prezzo.

    
posta Falcon 12.09.2013 - 22:30
fonte

4 risposte

39

Potresti anche chiedere quando la digitazione statica è migliore della digitazione dinamica. Un dibattito che imperversa da anni senza fine in vista. Quindi dai un'occhiata a domande come Dinamicamente vs studi di lingue tipizzate staticamente

Ma l'argomento di base potrebbe essere il potenziale per scoprire e risolvere problemi in fase di compilazione che potrebbero altrimenti essere introdotti nella produzione.

Contratti vs. guardie

Anche con le protezioni e le eccezioni ci si trova ancora di fronte al problema che il sistema non riuscirà a eseguire l'attività prevista in alcuni casi. Forse un'istanza che sarà abbastanza critica e costosa da fallire a.

Test di contratti contro unità

Il solito argomento su questo è che i test dimostrano la presenza di bug, mentre i tipi (contratti) dimostrano l'assenza. F.ex. usando un tipo sai che nessun percorso nel programma potrebbe fornire input non validi, mentre un test potrebbe solo dirti che il percorso coperto ha fornito l'input corretto.

Contratti contro il modello oggetto nullo

Ora questo è almeno nello stesso parco giochi. Lingue come Scala e Haskell hanno avuto un grande successo con questo approccio all'eliminazione dei riferimenti null interamente dai programmi. (Anche se Scala accetta formalmente i null, la convenzione non deve mai usarli)

Se usi già questo modello per eliminare le NRE, hai praticamente eliminato la più grande fonte di errori di runtime, in pratica il modo in cui i contratti ti permettono di farlo.

La differenza potrebbe essere che i contratti hanno un'opzione per richiedere automaticamente che tutto il tuo codice sia nullo, e quindi ti costringono a usare questo modello in più posti per passare la compilazione.

Oltre a questi contratti, puoi anche avere la flessibilità di indirizzare le cose oltre il nulla. Quindi se non vedi più NRE nei tuoi bug potresti voler usare i contratti per strangolare il prossimo problema più comune che potresti avere. Fuori da uno? Indice fuori intervallo?

Ma ...

Tutto ciò che viene detto. Sono d'accordo sul fatto che il rumore sintattico (e anche il rumore strutturale) che i contratti aggiungono al codice sia piuttosto sostanziale e l'impatto che l'analisi ha sul tempo di costruzione non dovrebbe essere sottovalutato. Quindi, se decidi di aggiungere contratti al tuo sistema, probabilmente sarebbe saggio farlo con molta attenzione concentrandoti su quale classe di bug si cerca di risolvere.

    
risposta data 13.09.2013 - 00:59
fonte
13

Non so dove provenga l'affermazione secondo cui "non esegue e spesso non è possibile eseguire un'analisi statica". La prima parte dell'asserzione è chiaramente sbagliata. Il secondo dipende da cosa intendi per "spesso". Direi piuttosto che spesso , esegue analisi statiche e raramente fallisce. Nelle normali applicazioni aziendali, raramente diventa molto più vicino a mai .

Quindi ecco qui, il primo vantaggio:

Vantaggio 1: analisi statica

Le asserzioni ordinarie e il controllo degli argomenti hanno uno svantaggio: vengono posticipate fino all'esecuzione del codice. D'altra parte, i contratti di codice si manifestano ad un livello molto più precoce, sia nella fase di codifica che durante la compilazione dell'applicazione. Prima si rileva un errore, meno costoso è ripararlo.

Vantaggio 2: sorta di documentazione sempre aggiornata

I contratti di codice forniscono anche una sorta di documentazione che è sempre aggiornata. Se il commento XML del metodo SetProductPrice(int newPrice) indica che newPrice dovrebbe essere superiore o uguale a zero, potresti sperare che la documentazione sia aggiornata, ma potresti anche scoprire che qualcuno ha cambiato il metodo in modo che newPrice = 0 lanci un ArgumentOutOfRangeException , ma non ha mai modificato la documentazione corrispondente. Data la correlazione tra i contratti di codice e il codice stesso, non hai il problema di documentazione non sincronizzato.

Il tipo di documentazione fornita dai contratti di codice è anche prezioso in un modo che spesso i commenti XML non spiegano bene i valori accettabili. Quante volte mi sono chiesto se null o string.Empty o \r\n è un valore autorizzato per un metodo, e i commenti XML non hanno funzionato!

In conclusione, senza contratti di codice, molte parti di codice sono così:

I'll accept some values but not others, but you'll have to guess or read the documentation, if any. Actually, don't read the documentation: it's outdated. Simply loop through all values and you'll see the ones which make me throw exceptions. You also have to guess the range of values which may be returned, because even if I would tell a bit more to you about them, it may not be true, given the hundreds of changes made to me for the last years.

Con i contratti di codice diventa:

The title argument can be a non-null string with a length of 0..500. The integer which follows is a positive value, which can be zero only when the string is empty. Finally, I'll return a IDefinition object, never null.

Vantaggio 3: contratti di interfacce

Un terzo vantaggio è che i contratti di codice potenziano le interfacce. Diciamo che hai qualcosa del tipo:

public interface ICommittable
{
    public ICollection<AtomicChange> PendingChanges { get; }

    public void CommitChanges();

    ...
}

In che modo, utilizzando solo le asserzioni e le eccezioni, puoi garantire che CommitChanges possa essere chiamato solo quando PendingChanges non è vuoto? Come garantiresti che PendingChanges non sia mai null ?

Benefit 4: applica i risultati di un metodo

Infine, il quarto vantaggio consiste nel riuscire a Contract.Ensure dei risultati. Cosa succede se, quando si scrive un metodo che restituisce un intero, voglio essere sicuro che il valore non sia mai inferiore o uguale a zero? Di cui cinque anni dopo, dopo aver subito molti cambiamenti da molti sviluppatori? Non appena un metodo ha più punti di ritorno, Assert s diventa un incubo di manutenzione per questo.

Considera i contratti di codice non solo come un mezzo di correttezza del codice, ma un modo più rigoroso di scrivere codice. In modo simile, una persona che ha utilizzato esclusivamente linguaggi dinamici può chiedere perché vorresti imporre tipi a livello linguistico, mentre puoi fare la stessa cosa nelle asserzioni quando necessario. Puoi farlo, ma la digitazione statica è più facile da usare, meno soggetta a errori rispetto a una serie di asserzioni e autodocumentazione.

La differenza tra digitazione dinamica e tipizzazione statica è estremamente vicina alla differenza tra programmazione ordinaria e programmazione per contratto.

    
risposta data 13.09.2013 - 09:01
fonte
2

Due cose davvero:

  1. Supporto per gli strumenti, VS può rendere i dati del contratto separati dall'intelligenza in modo che faccia parte della guida automatica completa.
  2. Quando una sottoclasse sovrascrive un metodo, perdi tutti i tuoi assegni (che dovrebbero essere ancora validi se segui l'LSP) con i contratti che seguono automaticamente le loro classi figlie.
risposta data 13.09.2013 - 05:49
fonte
1

So che è un po 'tardi per la domanda, ma c'è un altro vantaggio per i Contratti di Codice che menziona

I contratti vengono controllati al di fuori del metodo

Quando abbiamo delle condizioni di guardia all'interno del nostro metodo, quello è il luogo in cui avviene il controllo e dove vengono segnalati gli errori. Potremmo quindi dover indagare sulla traccia dello stack per scoprire dove si trova la vera fonte dell'errore.

I contratti di codice si trovano all'interno del metodo, ma le precondizioni vengono verificate al di fuori del metodo quando qualcosa tenta di chiamare quel metodo. I contratti diventano parte del metodo e determinano se può essere chiamato o meno. Ciò significa che otteniamo un errore segnalato molto più vicino all'origine effettiva del problema.

Se hai un metodo protetto da contratto che viene chiamato per molti posti, questo può essere un vantaggio reale.

    
risposta data 04.09.2015 - 16:47
fonte

Leggi altre domande sui tag