Linee guida
Gli strumenti per distribuire gli assembly e gestirne il versioning sono molti.
È utile vedere quali sono le linee guida per un uso corretto degli strumenti disponibili.
Linee guida per l’uso di strong name
La decisione di fornire o meno uno strong name ad un assembly in alcuni casi è automatica, in altri pone qualche dubbio.
Vediamo quali sono i casi in cui sicuramente si usa uno strong name:
- Libreria usata da molte applicazioni – sia che si producano librerie da fornire a terze parti, sia che si producano per uso interno, il solo meccanismo di controllo delle versioni degli assembly è un motivo che giustifica lo strong name
- Codice distribuito via internet/intranet – un assembly può essere scaricato da remoto solo se è dotato di strong name; questo non garantisce l’identità dell’autore, ma semplifica il riconoscimento dell’autenticità dell’assembly richiesto (chi chiede l’assembly lo fa tramite lo strong name, e ottiene esattamente ciò che ha chiesto)
- Installazione side-by-side – se si vogliono installare contemporaneamente più versioni dello stesso componente, è indispensabile installarle nella GAC, e per fare questo è necessario uno strong name
- Ridirezione delle versioni – per controllare la versione dell’assembly utilizzato da un’applicazione in maniera amministrativa, senza intervenire nel codice dell’applicazione stessa, lo strong name è indispensabile.
- Garanzia da tampering (manomissioni) – se un assembly ha uno strong name, la validazione dell’assembly fallisce se è stato modificato in seguito alla compilazione. Questa caratteristica è indispensabile quando si scarica un assembly da Internet, anche se sposta il problema dell’autenticità sull’assembly chiamante: chi garantisce che ad essere stato modificato non sia proprio quest’ultimo?
Non è invece necessario usare uno strong name in questi casi:
- L’assembly è privato – se l’assembly è usato solo come assembly privato da un’applicazione installata fisicamente sul disco locale di una macchina, il fatto di avere uno strong name implica una validazione dell’assembly ad ogni caricamento. Questo implica un certo overhead per l’operazione di lettura dell’intero file e di calcolo necessario alla verifica della firma digitale. Tutto ciò non significa che sia meglio non usare lo strong name sugli assembly privati, ma solo che non è strettamente necessario farlo.
- È richiesta l’autenticazione dell’autore – la firma digitale di uno strong name non garantisce l’identità dell’autore, quindi lo strong name non va considerato come mezzo di autenticazione. Questo non significa che lo strong name non debba essere usato in un caso simile, ma andrebbe affiancato all’uso di Authenticode. In un contesto simile lo strong name resta indispensabile per supportare i meccanismi di gestione delle versioni che abbiamo esaminato.
Linee guida per il versioning
La definizione di una versione per un assembly non è un problema secondario.
La versione di un assembly è un insieme di 4 numeri a 16 bit (i valori ammessi per ciascuno di essi vanno da 0 a 65534), secondo questo formato:
<major version>.<minor version>.<build>.<revision>
In realtà questi 4 numeri potrebbero avere un significato qualsiasi, perché come abbiamo visto il meccanismo di ridirezione delle versioni non assegna un significato particolare a ciascuna di queste parti. L’unico vincolo è dato dal fatto che il publisher policy file viene associato solo in funzione dei primi due numeri che compongono la versione.
Al di là di questo aspetto, è possibile definire delle linee guida da usare per assegnare il numero di versione ad un assembly.
- <major version> – Numero di versione principale, il cambio di questo numero di solito segnala un incompatibilità con altre versioni.
- <minor version> – Numero di versione minore, il cambio di questo numero di solito segnala un’incompatibilità con le altre versioni. In realtà potrebbe avere senso adottare una convenzione per cui l’incremento di <minor version> segnala l’aggiunta di nuove funzionalità che in linea di massima non modificano il comportamento di quelle già esistenti, al contrario di ciò che succede normalmente per un cambio di <majoir version>
- <build> – Questo numero viene tipicamente usato per contraddistinguere le build giornaliere. Il significato può anche essere quello di piccole modifiche rispetto a versioni precedenti, che mantengono piena compatibilità.
- <revision> – Numero di revisione, tipicamente usato per distinguere compilazioni incrementali all’interno di una build giornaliera. Può anche avere il significato di distinguere una versione che corregge un singolo bug rispetto ad una versione esistente. Ad es. la versione 1.2.332.1 si può intendere come una correzione di un bug che affliggeva la versione 1.2.332.0.
I compilatori offrono, tramite lo l’attributo [AssemblyVersion], un servizio di numerazione automatica delle versioni. In realtà si tratta di uno pseudo-attributo, perché in fase di compilazione ha l’effetto di intervenire direttamente nei metadati dell’assembly.
In questo attributo, <major version> va sempre specificato; tutti gli altri valori, se non specificati, assumono il valore 0.
Dunque [AssemblyVersion(“1”)] produce la versione 1.0.0.0, così come [AssemblyVersion(“1.2”)] produce la versione 1.2.0.0.
I numeri di<build> e <revision> possono essere generati automaticamente.
Si può quindi usare [AssemblyVersion(“1.2.*”)] per generare automaticamente sia <build> che <revision>, oppure [AssemblyVersion(“1.2.3.*”)] per generare automaticamente solo <revision>.
Non è possibile usare una sintassi come [AssemblyVersion(“1.2.*.4”)] per generare automaticamente <build> lasciando inalterato <revision>.
Vediamo con che logica vengono generati i numeri richiesti:
- <build> corrisponde al numero di giorni trascorsi dal 1 gennaio 2000. 0 sta per 01/01/2000, 1 sta per 02/01/2000 e così via. Degno di nota il fatto che con questo meccanismo i numeri disponibili arrivano al 5 giugno 2179. La data considerata è quella locale.
- <revision> corrisponde alla metà del numero di secondi trascorsi dalla mezzanotte (rispetto all’ora locale). Il numero di secondi viene diviso per due solo perché in un giorno ci sono 86.400 secondi, numero che non è rappresentabile in un intero a 16 bit quale è <revision>. Anche qui va evidenziato come due compilazioni distanti meno di due secondi possono portare alla generazione dello stesso numero di versione dell’assembly, cosa che può creare qualche problema se nel frattempo è stata apportata qualche modifica al codice.
La generazione di <build> e <revision> con questa tecnica porta ad avere dei numeri univoci di versione, indipendentemente dal valore di <major version> e <minor version>. È comunque consigliabile cercare di modificare manualmente questi numeri per segnalare al mondo esterno quando due versioni di un componente differiscono per funzionalità contenute e possono essere incompatibili tra loro. Come abbiamo detto, si assume che una modifica ai soli numeri di <build> e <revision> non dovrebbe creare incompatibilità con le versioni precedenti, a parità di <major version> e <minor version>.
Una controindicazione (o per lo meno un’avvertenza) rispetto alla generazione automatica di <build> e <revision> è data dalle problematiche che sorgono nel contesto di uno sviluppo di componenti in team. Se gli assembly sono dotati di strong name, tutte le volte che un assembly viene ricompilato è necessario ricompilare anche tutti gli assembly che lo referenziano: se cambia la versione, cambia lo strong name, e tutti i riferimenti esistenti vanno aggiornati al nuovo strong name. Abbiamo visto come ciò possa avvenire con file di configurazione esterni, ma ovviamente questo non fa che complicare le cose, specialmente se nella quotidianità si compilano sovente nuove versioni dei componenti.
Non è detto che definire manualmente le versioni sia una soluzione migliore, perché aumenta la probabilità di avere involontariamente assembly diversi che mantengono la stessa versione (e quindi lo stesso strong name).
Una soluzione ragionevole è quella di mantenere le compilazioni frequenti dei componenti in locale rispetto alle proprie macchine di sviluppo, rilasciando in un repository centralizzato solo le versioni che raggiungono una certa “stabilità”. Queste versioni dovranno avere un publisher policy file, in modo che se sono installate nella GAC dispongano già di una definizione delle ridirezioni compatibili. Se invece sono copiate da altri sviluppatori che li usano come assembly privati, essi ricompileranno la propria applicazione in modo che faccia riferimento alla nuova versione del componente, che non cambierà più fino al successivo riallineamento volontario da parte del programmatore.
Qualsiasi strada si prenda, è bene definire chiaramente degli standard interni per evitare la proliferazione indiscriminata di versioni diverse dei componenti: anche se abbiamo tutti gli strumenti per gestire qualsiasi situazione, non è saggio creare i presupposti perché si avverino le situazioni più intricate.
