Introduzione ai Code Contracts (parte 5)
Scritto da
Massimo Bonanni
il
mercoledì 29 settembre 2010
Linguaggio:
•
Framework:
•
Livello: 200
Download sorgenti

Questo articolo è la quinta ed ultima puntata (prima, seconda, terza e quarta) della serie dedicata ai Code
Contracts.
In questa puntata vedremo come sia possibile generare
automaticamente la documentazione delle classi che riporti
precondizioni, postcondizioni e invarianti di oggetto.
Riprendiamo la classe Corso costruita negli articoli
precedenti:
Imports System.Diagnostics.Contracts
''' <summary>
''' Corso
''' </summary>
''' <remarks></remarks>
Public Class Corso
''' <summary>
''' Numero massimo alunni ammessi al corso.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property NumeroMassimoAlunni As Integer
''' <summary>
''' Elenco degli alunni iscritti.
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property Alunni As List(Of Alunno)
''' <summary>
''' Costruttore.
''' </summary>
''' <param name="numeroMassimoAlunni">Numero massimo di alunni ammessi al corso.</param>
''' <remarks></remarks>
Public Sub New(ByVal numeroMassimoAlunni As Integer)
Contract.Requires(numeroMassimoAlunni > 0, "Il numero massimo degli alunni deve essere positivo")
Me.NumeroMassimoAlunni = numeroMassimoAlunni
Me.Alunni = New List(Of Alunno)
End Sub
''' <summary>
''' Permette di iscrivere un alunno al corso.
''' </summary>
''' <param name="alunno">Alunno da iscrivere al corso.</param>
''' <returns>Ritorna la posizione dell'alunno nella lista degli iscritti.</returns>
''' <remarks></remarks>
Public Overridable Function IscriviAlunno(ByVal alunno As Alunno) As Integer
Contract.Requires(alunno IsNot Nothing, "L'alunno non può essere Nothing")
Contract.Requires(NumeroMassimoAlunni > Alunni.Count, "Massimo numero di alunni raggiunti")
Contract.Ensures(Me.Alunni.Count = Contract.OldValue(Me.Alunni.Count) + 1)
Contract.Ensures(Contract.Result(Of Integer)() > 0)
Alunni.Add(alunno)
Dim retval = Alunni.Count
Return retval
End Function
<ContractInvariantMethod()> _
Private Sub ObjectInvariant()
Contract.Invariant(Me.Alunni IsNot Nothing)
Contract.Invariant(Me.NumeroMassimoAlunni > 0)
End Sub
End Class
Cominciamo con il generare il normale file XML che contiene la
documentazione "standard".
Per fare questo è sufficiente attivare il
checkbox Generate XML documentation File nel
tab Compile delle proprietà di progetto (come mostrato nella
seguente figura):

Se compiliamo il nostro progetto, nella cartella
Bin\Debug dovremmo trovare il file
CodeContractConsole.xml il cui estratto
riguardante la classe Corso è il seguente:
<member name="P:CodeContractsConsole.Corso.NumeroMassimoAlunni">
<summary>
Numero massimo alunni ammessi al corso.
</summary>
<value></value>
<returns></returns>
<remarks></remarks>
</member>
<member name="P:CodeContractsConsole.Corso.Alunni">
<summary>
Elenco degli alunni iscritti.
</summary>
<value></value>
<returns></returns>
<remarks></remarks>
</member>
<member name="M:CodeContractsConsole.Corso.#ctor(System.Int32)">
<summary>
Costruttore.
</summary>
<param name="numeroMassimoAlunni">Numero massimo di alunni ammessi al corso.</param>
<remarks></remarks>
</member>
<member name="M:CodeContractsConsole.Corso.IscriviAlunno(CodeContractsConsole.Alunno)">
<summary>
Permette di iscrivere un alunno al corso.
</summary>
<param name="alunno">Alunno da iscrivere al corso.</param>
<returns>Ritorna la posizione dell'alunno nella lista degli iscritti.</returns>
<remarks></remarks>
</member>
<member name="T:CodeContractsConsole.Corso">
<summary>
Corso
</summary>
<remarks></remarks>
</member>
Si tratta, cioè, del classico file di documentazione cui siamo
abituati.
Per inserire automaticamente, all'interno di questo file, la
documentazione riguardante i contratti definiti nella classe Corso,
si debbono eseguire alcuni passi.
Innanzitutto è necessario attivare la modifica del file XML
precedente da parte del tool dei Code Contracts deputato alla
generazione della documentazione (ccdocgen.exe) e per fare questo
apriamo il tab Code Contracts delle proprietà di progetto e
modifichiamo le impostazioni come mostrato in figura:

Nel momento in cui compiliamo, il tools di generazione della
documentazione dei Code Contracts dovrebbe modificare il nome
del file XML originale aggiungendo l'estensione .old e creare
un nuovo file XML a partire dall'originale con, all'interno, i
nuovi tag per la documentazione dei contratti.
Se eseguiamo la compilazione su un progetto C# (ad esempio
quello contenuto nei sorgenti allegati), troveremo, al termine
della stessa, entrambi i file all'interno della cartella
bin\Debug.
La versione 1.4.30707.2 dei tools su cui sono state
eseguite le demo di questi articoli, non si comporta esattamente
allo stesso modo per i progetti VB.NET.
A causa di bug che stato corretto nella versione 1.4.30903.0,
infatti, il generatore di documentazione crea correttamente
entrambi i file ma li lascia all'interno della cartella dei file
temporanei di compilazione. Per maggiori informazioni potete
consultare il seguente link.
Se abbiamo installato la nuova versione, la nostra
documentazione è pronta nella cartella degli eseguibili per essere
utilizzata con strumenti di generazione dei file di help quali, ad
esempio, SandCastle.
Se, invece, stiamo utilizzando la versione dei tools di luglio
2010 e apriamo la cartella obj\x86\Debug (che
potrebbe variare in base a configurazione e piattaforma impostate
sul progetto) troviamo file
CodeContractConsole.xml e il file
CodeContractConsole.xml.old. Il primo dei due file
contiene la documentazione completa dei contratti da noi inseriti
per la classe Corso:
<member name="P:CodeContractsConsole.Corso.NumeroMassimoAlunni">
<summary>
Numero massimo alunni ammessi al corso.
</summary>
<value />
<returns />
<remarks />
</member>
<member name="P:CodeContractsConsole.Corso.Alunni">
<summary>
Elenco degli alunni iscritti.
</summary>
<value />
<returns />
<remarks />
</member>
<member name="M:CodeContractsConsole.Corso.#ctor(System.Int32)">
<summary>
Costruttore.
</summary>
<param name="numeroMassimoAlunni">Numero massimo di alunni ammessi al corso.</param>
<remarks />
<requires description="Il numero massimo degli alunni deve essere positivo">numeroMassimoAlunni > 0</requires>
</member>
<member name="M:CodeContractsConsole.Corso.IscriviAlunno(CodeContractsConsole.Alunno)">
<summary>
Permette di iscrivere un alunno al corso.
</summary>
<param name="alunno">Alunno da iscrivere al corso.</param>
<returns>Ritorna la posizione dell'alunno nella lista degli iscritti.</returns>
<remarks />
<requires description="L'alunno non può essere Nothing">alunno IsNot Nothing</requires>
<requires description="Massimo numero di alunni raggiunti">NumeroMassimoAlunni > Alunni.Count</requires>
<ensures>Me.Alunni.Count = Contract.OldValue(Me.Alunni.Count) + 1</ensures>
<ensures>Contract.Result(Of Integer)() > 0</ensures>
</member>
<member name="T:CodeContractsConsole.Corso">
<summary>
Corso
</summary>
<remarks />
<invariant>Me.Alunni IsNot Nothing</invariant>
<invariant>Me.NumeroMassimoAlunni > 0</invariant>
</member>
Sono stati aggiunti, quindi, i tag XML
<invariant>,
<requires> e
<ensures> che rappresentano rispettivamente
le invarianti di oggetto, le precodizioni e le postcondizioni.
L'attributo description conterrà l'eventuale
messaggio da noi impostato e, in ogni caso, è riportata la
condizione del contratto in formato testo.
Una volta aggiunti i tag di documentazione all'XML, possiamo,
come già detto in precedenza, utilizzare SandCastle per generare l'help vero e
proprio. Nella cartella di installazione dei tools dei Code
Contracts è presente un folder in cui è dettagliatamente spiegato
come modificare i file di SandCastle per interpretare i nuovi tag
di documentazione.
Per terminare vediamo com'è possibile aggirare il bug del
generatore di documentazione e fare in modo che, a seguito della
compilazione, entrambi i file di documentazione si trovino nella
cartella bin\Debug come per la versione C#.
Per fare questo utilizzeremo uno script di post-build. Visual
Studio permette di eseguire degli script prima e dopo una
compilazione in modo da intervenire preventivamente o a posteriori
sui file di progetto. Quello che faremo è farein modo che, a
seguito di una compilazione avvenuta con successo, venga eseguita
una copia dei due file di cui sopra (se esistono) all'interno della
cartella di bin attuale.
Iniziamo con il creare il semplice script
PostBuildDoc.bat:
if exist %1.xml xcopy /C /Y %1.xml %2
if exist %1.xml.old xcopy /C /Y %1.xml.old %2
e salviamolo nella cartella dove è presente la soluzione
contenente il nostro progetto.
Attiviamo lo script di post build utilizzando il comando
Build Events.. che troviamo nel tab Compile delle
proprietà di progetto:

La pressione del tasto permette di visualizzare la finestra
degli eventi di compilazione:

Nello spazio relativo ai post-build event inseriamo la seguente
linea di comando:
call "$(SolutionDir)PostBuildDoc.bat"
"$(ProjectDir)obj\$(PlatformName)\$(ConfigurationName)\$(TargetName)"
"$(ProjectDir)$(OutDir)"
nella quale richiamiamo lo script batch precedentemente
creato.
A questo punto il gioco è fatto e, a seguito di una compilazione
eseguita correttamente, troveremo i file di documentazione
all'interno della cartella degli eseguibili.
Riferimenti
Tags: Code Analysis,Code Contracts,SandCastle